@@ -16,16 +13,16 @@ This example focuses on a generic use case - chat with your own data, generate a
**Note:** With any AI solutions you create using these templates, you are responsible for assessing all associated risks and for complying with all applicable laws and safety standards. Learn more in the transparency documents for [Agent Service](https://learn.microsoft.com/en-us/azure/ai-foundry/responsible-ai/agents/transparency-note) and [Agent Framework](https://github.com/microsoft/agent-framework/blob/main/TRANSPARENCY_FAQ.md).
-
+
Solution overview
-It leverages Azure OpenAI Service and Azure AI Search, to identify relevant documents, summarize unstructured information, and generate document templates.
+This solution leverages Microsoft Foundry, Azure AI Search, Azure Cosmos DB, and Azure Blob Storage to interpret creative briefs, retrieve product information, generate marketing content, and validate brand compliance.
-The sample data is sourced from generic AI-generated promissory notes. The documents are intended for use as sample data only.
+The sample data includes synthetic product catalogs and brand guidelines. The data is intended for use as sample data only.
### Solution architecture
-||
+||
|---|
@@ -35,7 +32,9 @@ The sample data is sourced from generic AI-generated promissory notes. The docum
[Azure OpenAI Service](https://learn.microsoft.com/en-us/azure/ai-services/openai/)
-[Azure AI Search](https://learn.microsoft.com/en-us/azure/search/)
+[Microsoft Agent Framework](https://github.com/microsoft/agent-framework)
+
+[Azure Cosmos DB](https://learn.microsoft.com/en-us/azure/cosmos-db/)
[Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-studio/)
@@ -46,21 +45,22 @@ The sample data is sourced from generic AI-generated promissory notes. The docum
Click to learn more about the key features this solution enables
- - **Semantic search**
- Azure AI Search to enable RAG and grounding of the application on the processed dataset.
-
- - **Summarization**
- Azure OpenAI Service and GPT models to help summarize the search content and answer questions.
+ - **Creative Brief Interpretation**
+ Parse free-text creative briefs into structured fields (overview, objectives, target audience, key message, tone/style, deliverable, timelines, visual guidelines, CTA).
- - **Content generation**
- Azure OpenAI Service and GPT models to help generate relevant content with Prompt Flow.
-
-
+ - **Multimodal Content Generation**
+ Generate marketing copy and images using GPT models and DALL-E 3 grounded in enterprise product data.
+ - **Brand Compliance Validation**
+ Validate all generated content against brand guidelines with severity-categorized feedback (Error, Warning, Info).
+ - **Specialized Agent Orchestration**
+ Uses Microsoft Agent Framework with HandoffBuilder to coordinate Triage, Planning, Research, Text Content, Image Content, and Compliance agents.
+
+
-
+
Quick deploy
@@ -69,13 +69,10 @@ Follow the quick deploy steps on the deployment guide to deploy this solution to
> **Note:** This solution accelerator requires **Azure Developer CLI (azd) version 1.18.0 or higher**. Please ensure you have the latest version installed before proceeding with deployment. [Download azd here](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd).
-[Click here to launch the deployment guide](./docs/DeploymentGuide.md)
+[Click here to launch the deployment guide](./content-gen/docs/DEPLOYMENT.md)
-**For Local Development**
-- [Local Development Setup Guide](docs/LocalDevelopmentSetup.md) - Comprehensive setup instructions for Windows, Linux, and macOS
-
-| [](https://codespaces.new/microsoft/document-generation-solution-accelerator) | [](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/document-generation-solution-accelerator) | [&message=Open&color=blue&logo=visualstudiocode&logoColor=white)](https://vscode.dev/azure/?vscode-azure-exp=foundry&agentPayload=eyJiYXNlVXJsIjogImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9taWNyb3NvZnQvZG9jdW1lbnQtZ2VuZXJhdGlvbi1zb2x1dGlvbi1hY2NlbGVyYXRvci9yZWZzL2hlYWRzL21haW4vaW5mcmEvdnNjb2RlX3dlYiIsICJpbmRleFVybCI6ICIvaW5kZXguanNvbiIsICJ2YXJpYWJsZXMiOiB7ImFnZW50SWQiOiAiIiwgImNvbm5lY3Rpb25TdHJpbmciOiAiIiwgInRocmVhZElkIjogIiIsICJ1c2VyTWVzc2FnZSI6ICIiLCAicGxheWdyb3VuZE5hbWUiOiAiIiwgImxvY2F0aW9uIjogIiIsICJzdWJzY3JpcHRpb25JZCI6ICIiLCAicmVzb3VyY2VJZCI6ICIiLCAicHJvamVjdFJlc291cmNlSWQiOiAiIiwgImVuZHBvaW50IjogIiJ9LCAiY29kZVJvdXRlIjogWyJhaS1wcm9qZWN0cy1zZGsiLCAicHl0aG9uIiwgImRlZmF1bHQtYXp1cmUtYXV0aCIsICJlbmRwb2ludCJdfQ==) |
+| [](https://codespaces.new/hunterjam/content-generation-solution-accelerator) | [](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/hunterjam/content-generation-solution-accelerator) | [&message=Open&color=blue&logo=visualstudiocode&logoColor=white)](https://vscode.dev/azure/?vscode-azure-exp=foundry&agentPayload=eyJiYXNlVXJsIjogImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9odW50ZXJqYW0vY29udGVudC1nZW5lcmF0aW9uLXNvbHV0aW9uLWFjY2VsZXJhdG9yL3JlZnMvaGVhZHMvbWFpbi9jb250ZW50LWdlbi9pbmZyYS92c2NvZGVfd2ViIiwgImluZGV4VXJsIjogIi9pbmRleC5qc29uIiwgInZhcmlhYmxlcyI6IHsiYWdlbnRJZCI6ICIiLCAiY29ubmVjdGlvblN0cmluZyI6ICIiLCAidGhyZWFkSWQiOiAiIiwgInVzZXJNZXNzYWdlIjogIiIsICJwbGF5Z3JvdW5kTmFtZSI6ICIiLCAibG9jYXRpb24iOiAiIiwgInN1YnNjcmlwdGlvbklkIjogIiIsICJyZXNvdXJjZUlkIjogIiIsICJwcm9qZWN0UmVzb3VyY2VJZCI6ICIiLCAiZW5kcG9pbnQiOiAiIn0sICJjb2RlUm91dGUiOiBbImFpLXByb2plY3RzLXNkayIsICJweXRob24iLCAiZGVmYXVsdC1henVyZS1hdXRoIiwgImVuZHBvaW50Il19) |
|---|---|---|
@@ -104,14 +101,13 @@ _Note: This is not meant to outline all costs as selected SKUs, scaled use, cust
| Product | Description | Cost |
|---|---|---|
| [Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-foundry/) | Free tier. Build generative AI applications on an enterprise-grade platform. | [Pricing](https://azure.microsoft.com/pricing/details/ai-studio/) |
-| [Azure AI Search](https://learn.microsoft.com/en-us/azure/search/) | Standard tier, S1. Pricing is based on the number of documents and operations. Information retrieval at scale for vector and text content in traditional or generative search scenarios. | [Pricing](https://azure.microsoft.com/pricing/details/search/) |
-| [Azure Storage Account](https://learn.microsoft.com/en-us/azure/storage/blobs/) | Standard tier, LRS. Pricing is based on storage and operations. Blob storage in the clopud, optimized for storing massive amounts of unstructured data. | [Pricing](https://azure.microsoft.com/pricing/details/storage/blobs/) |
-| [Azure Key Vault](https://learn.microsoft.com/en-us/azure/key-vault/) | Standard tier. Pricing is based on the number of operations. Maintain keys that access and encrypt your cloud resources, apps, and solutions. | [Pricing](https://azure.microsoft.com/pricing/details/key-vault/) |
-| [Azure AI Services](https://learn.microsoft.com/en-us/azure/ai-services/) | S0 tier, defaults to gpt-4.1 and text-embedding-ada-002 models. Pricing is based on token count. | [Pricing](https://azure.microsoft.com/pricing/details/cognitive-services/) |
-| [Azure Container App](https://learn.microsoft.com/en-us/azure/container-apps/) | Consumption tier with 0.5 CPU, 1GiB memory/storage. Pricing is based on resource allocation, and each month allows for a certain amount of free usage. Allows you to run containerized applications without worrying about orchestration or infrastructure. | [Pricing](https://azure.microsoft.com/pricing/details/container-apps/) |
+| [Azure Storage Account](https://learn.microsoft.com/en-us/azure/storage/blobs/) | Standard tier, LRS. Pricing is based on storage and operations. Blob storage for product images and generated content. | [Pricing](https://azure.microsoft.com/pricing/details/storage/blobs/) |
+| [Azure AI Services](https://learn.microsoft.com/en-us/azure/ai-services/) | S0 tier, defaults to gpt-5.1 (GPT) and gpt-image-1 (DALL-E 3) models. Pricing is based on token count. | [Pricing](https://azure.microsoft.com/pricing/details/cognitive-services/) |
+| [Azure Container Instance](https://learn.microsoft.com/en-us/azure/container-instances/) | Backend API hosting with private VNet integration. Pricing is based on resource allocation. | [Pricing](https://azure.microsoft.com/pricing/details/container-instances/) |
+| [Azure App Service](https://learn.microsoft.com/en-us/azure/app-service/) | B1 tier. Frontend hosting with Node.js proxy server. | [Pricing](https://azure.microsoft.com/pricing/details/app-service/) |
| [Azure Container Registry](https://learn.microsoft.com/en-us/azure/container-registry/) | Basic tier. Build, store, and manage container images and artifacts in a private registry for all types of container deployments | [Pricing](https://azure.microsoft.com/pricing/details/container-registry/) |
| [Log analytics](https://learn.microsoft.com/en-us/azure/azure-monitor/) | Pay-as-you-go tier. Costs based on data ingested. Collect and analyze on telemetry data generated by Azure. | [Pricing](https://azure.microsoft.com/pricing/details/monitor/) |
-| [Azure Cosmos DB](https://learn.microsoft.com/en-us/azure/cosmos-db/) | Fully managed, distributed NoSQL, relational, and vector database for modern app development. | [Pricing](https://azure.microsoft.com/en-us/pricing/details/cosmos-db/autoscale-provisioned/) |
+| [Azure Cosmos DB](https://learn.microsoft.com/en-us/azure/cosmos-db/) | Serverless tier. Product catalog and conversation history storage. | [Pricing](https://azure.microsoft.com/en-us/pricing/details/cosmos-db/autoscale-provisioned/) |
@@ -121,17 +117,17 @@ _Note: This is not meant to outline all costs as selected SKUs, scaled use, cust
either by deleting the resource group in the Portal or running `azd down`.
-
+
Business Scenario
-||
+||
|---|
-Put your data to work by reducing blank page anxiety, speeding up document drafting, improving draft document quality, and reference information quickly - keeping experts in their expertise. Draft document templates for your organization including Invoices, End-user Contracts, Purchase Orders, Investment Proposals, and Grant Submissions.
+Accelerate your marketing content creation by leveraging AI to interpret creative briefs and generate on-brand, multimodal content. The solution helps marketing teams reduce time-to-market for campaigns by automating the creation of compliant marketing copy and images grounded in product data.
⚠️ The sample data used in this repository is synthetic and generated using Azure OpenAI Service. The data is intended for use as sample data only.
@@ -140,42 +136,42 @@ Put your data to work by reducing blank page anxiety, speeding up document draft
Click to learn more about what value this solution provides
- - **Draft templates quickly**
- Put your data to work to create any kind of document that is supported by a large data library.
+ - **Interpret creative briefs**
+ Parse unstructured creative briefs into structured fields automatically, ensuring all campaign requirements are captured.
- - **Share**
- Share with co-authors, contributors and approvers quickly.
+ - **Generate multimodal content**
+ Create marketing copy and images that align with your brand voice and product catalog using GPT and DALL-E 3.
- - **Contextualize information**
- Provide context using natural language. Primary and secondary queries allow for access to supplemental detail – reducing cognitive load, increasing efficiency, and enabling focus on higher value work.
+ - **Ensure brand compliance**
+ Validate all generated content against brand guidelines with severity-categorized feedback before publication.
- - **Gain confidence in responses**
- Trust responses to queries by customizing how data is referenced and returned to users, reducing the risk of hallucinated responses.
Access reference documents in the same chat window to get more detail and confirm accuracy.
+ - **Ground in enterprise data**
+ Leverage product information, images, and brand guidelines stored in Azure to ensure accurate, relevant content.
- - **Secure data and responsible AI for innovation**
- Improve data security to minimize breaches, fostering a culture of responsible AI adoption, maximize innovation opportunities, and sustain competitive edge.
+ - **Secure data and responsible AI**
+ Maintain data security with managed identities and private networking while fostering responsible AI adoption.
We would like to inform you that ${{ env.accelerator_name }} accelerator test automation process has encountered issues and failed to complete successfully.
The ${{ env.accelerator_name }} pipeline executed against the specified Target URL and the test automation has encountered issues and failed to complete successfully.
Failure Details: • Target URL: ${EXISTING_URL} ${TEST_REPORT_URL:+• Test Report: View Report} • Test Suite: ${TEST_SUITE_NAME} • Deployment: Skipped
",
+ "subject": "${{ env.accelerator_name }} Pipeline - Test Automation Failed"
+ }
+ EOF
+ )
+
+ curl -X POST "${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}" \
+ -H "Content-Type: application/json" \
+ -d "$EMAIL_BODY" || echo "Failed to send existing URL test failure notification"
diff --git a/.github/workflows/node.js.yml b/archive-doc-gen/.github/workflows/node.js.yml
similarity index 100%
rename from .github/workflows/node.js.yml
rename to archive-doc-gen/.github/workflows/node.js.yml
diff --git a/.github/workflows/pr-title-checker.yml b/archive-doc-gen/.github/workflows/pr-title-checker.yml
similarity index 100%
rename from .github/workflows/pr-title-checker.yml
rename to archive-doc-gen/.github/workflows/pr-title-checker.yml
diff --git a/.github/workflows/pylint.yml b/archive-doc-gen/.github/workflows/pylint.yml
similarity index 100%
rename from .github/workflows/pylint.yml
rename to archive-doc-gen/.github/workflows/pylint.yml
diff --git a/.github/workflows/python-app.yml b/archive-doc-gen/.github/workflows/python-app.yml
similarity index 100%
rename from .github/workflows/python-app.yml
rename to archive-doc-gen/.github/workflows/python-app.yml
diff --git a/.github/workflows/stale-bot.yml b/archive-doc-gen/.github/workflows/stale-bot.yml
similarity index 100%
rename from .github/workflows/stale-bot.yml
rename to archive-doc-gen/.github/workflows/stale-bot.yml
diff --git a/.github/workflows/telemetry-template-check.yml b/archive-doc-gen/.github/workflows/telemetry-template-check.yml
similarity index 100%
rename from .github/workflows/telemetry-template-check.yml
rename to archive-doc-gen/.github/workflows/telemetry-template-check.yml
diff --git a/archive-doc-gen/.github/workflows/test-automation-v2.yml b/archive-doc-gen/.github/workflows/test-automation-v2.yml
new file mode 100644
index 000000000..637a79fa6
--- /dev/null
+++ b/archive-doc-gen/.github/workflows/test-automation-v2.yml
@@ -0,0 +1,196 @@
+name: Test Automation DocGen-v2
+
+on:
+ workflow_call:
+ inputs:
+ DOCGEN_URL:
+ required: true
+ type: string
+ description: "Web URL for DocGen"
+ TEST_SUITE:
+ required: false
+ type: string
+ default: "GoldenPath-Testing"
+ description: "Test suite to run: 'Smoke-Testing', 'GoldenPath-Testing' "
+ outputs:
+ TEST_SUCCESS:
+ description: "Whether tests passed"
+ value: ${{ jobs.test.outputs.TEST_SUCCESS }}
+ TEST_REPORT_URL:
+ description: "URL to test report artifact"
+ value: ${{ jobs.test.outputs.TEST_REPORT_URL }}
+
+env:
+ url: ${{ inputs.DOCGEN_URL }}
+ accelerator_name: "DocGen"
+ test_suite: ${{ inputs.TEST_SUITE }}
+permissions:
+ contents: read
+ actions: read
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ outputs:
+ TEST_SUCCESS: ${{ steps.test1.outcome == 'success' || steps.test2.outcome == 'success' || steps.test3.outcome == 'success' }}
+ TEST_REPORT_URL: ${{ steps.upload_report.outputs.artifact-url }}
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v5
+
+ - name: Set up Python
+ uses: actions/setup-python@v6
+ with:
+ python-version: '3.13'
+
+ - name: Login to Azure
+ run: |
+ az login --service-principal -u ${{ secrets.AZURE_CLIENT_ID }} -p ${{ secrets.AZURE_CLIENT_SECRET }} --tenant ${{ secrets.AZURE_TENANT_ID }}
+ az account set --subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }}
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r tests/e2e-test/requirements.txt
+
+ - name: Ensure browsers are installed
+ run: python -m playwright install --with-deps chromium
+
+ - name: Validate URL
+ run: |
+ if [ -z "${{ env.url }}" ]; then
+ echo "ERROR: No URL provided for testing"
+ exit 1
+ fi
+ echo "Testing URL: ${{ env.url }}"
+ echo "Test Suite: ${{ env.test_suite }}"
+
+
+ - name: Wait for Application to be Ready
+ run: |
+ echo "Waiting for application to be ready at ${{ env.url }} "
+ max_attempts=10
+ attempt=1
+
+ while [ $attempt -le $max_attempts ]; do
+ echo "Attempt $attempt: Checking if application is ready..."
+ if curl -f -s "${{ env.url }}" > /dev/null; then
+ echo "Application is ready!"
+ break
+
+ fi
+
+ if [ $attempt -eq $max_attempts ]; then
+ echo "Application is not ready after $max_attempts attempts"
+ exit 1
+ fi
+
+ echo "Application not ready, waiting 30 seconds..."
+ sleep 30
+ attempt=$((attempt + 1))
+ done
+
+ - name: Run tests(1)
+ id: test1
+ run: |
+ if [ "${{ env.test_suite }}" == "GoldenPath-Testing" ]; then
+ xvfb-run pytest -m goldenpath --html=report/report.html --self-contained-html
+ else
+ xvfb-run pytest --html=report/report.html --self-contained-html
+ fi
+ working-directory: tests/e2e-test
+ continue-on-error: true
+
+ - name: Sleep for 30 seconds
+ if: ${{ steps.test1.outcome == 'failure' }}
+ run: sleep 30s
+ shell: bash
+
+ - name: Run tests(2)
+ id: test2
+ if: ${{ steps.test1.outcome == 'failure' }}
+ run: |
+ if [ "${{ env.test_suite }}" == "GoldenPath-Testing" ]; then
+ xvfb-run pytest -m goldenpath --html=report/report.html --self-contained-html
+ else
+ xvfb-run pytest --html=report/report.html --self-contained-html
+ fi
+ working-directory: tests/e2e-test
+ continue-on-error: true
+
+ - name: Sleep for 60 seconds
+ if: ${{ steps.test2.outcome == 'failure' }}
+ run: sleep 60s
+ shell: bash
+
+ - name: Run tests(3)
+ id: test3
+ if: ${{ steps.test2.outcome == 'failure' }}
+ run: |
+ if [ "${{ env.test_suite }}" == "GoldenPath-Testing" ]; then
+ xvfb-run pytest -m goldenpath --html=report/report.html --self-contained-html
+ else
+ xvfb-run pytest --html=report/report.html --self-contained-html
+ fi
+ working-directory: tests/e2e-test
+
+ - name: Upload test report
+ id: upload_report
+ uses: actions/upload-artifact@v4
+ if: ${{ !cancelled() }}
+ with:
+ name: test-report
+ path: |
+ tests/e2e-test/report/*
+ tests/e2e-test/screenshots/*
+
+ - name: Generate E2E Test Summary
+ if: always()
+ run: |
+ # Determine test suite type for title
+ if [ "${{ env.test_suite }}" == "GoldenPath-Testing" ]; then
+ echo "## 🧪 E2E Test Job Summary : Golden Path Testing" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "## 🧪 E2E Test Job Summary : Smoke Testing" >> $GITHUB_STEP_SUMMARY
+ fi
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
+ echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
+
+ # Determine overall test result
+ OVERALL_SUCCESS="${{ steps.test1.outcome == 'success' || steps.test2.outcome == 'success' || steps.test3.outcome == 'success' }}"
+ if [[ "$OVERALL_SUCCESS" == "true" ]]; then
+ echo "| **Job Status** | ✅ Success |" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "| **Job Status** | ❌ Failed |" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ echo "| **Target URL** | [${{ env.url }}](${{ env.url }}) |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Test Suite** | \`${{ env.test_suite }}\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Test Report** | [Download Artifact](${{ steps.upload_report.outputs.artifact-url }}) |" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ echo "### 📋 Test Execution Details" >> $GITHUB_STEP_SUMMARY
+ echo "| Attempt | Status | Notes |" >> $GITHUB_STEP_SUMMARY
+ echo "|---------|--------|-------|" >> $GITHUB_STEP_SUMMARY
+ echo "| **Test Run 1** | ${{ steps.test1.outcome == 'success' && '✅ Passed' || '❌ Failed' }} | Initial test execution |" >> $GITHUB_STEP_SUMMARY
+
+ if [[ "${{ steps.test1.outcome }}" == "failure" ]]; then
+ echo "| **Test Run 2** | ${{ steps.test2.outcome == 'success' && '✅ Passed' || steps.test2.outcome == 'failure' && '❌ Failed' || '⏸️ Skipped' }} | Retry after 30s delay |" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ if [[ "${{ steps.test2.outcome }}" == "failure" ]]; then
+ echo "| **Test Run 3** | ${{ steps.test3.outcome == 'success' && '✅ Passed' || steps.test3.outcome == 'failure' && '❌ Failed' || '⏸️ Skipped' }} | Final retry after 60s delay |" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ if [[ "$OVERALL_SUCCESS" == "true" ]]; then
+ echo "### ✅ Test Results" >> $GITHUB_STEP_SUMMARY
+ echo "- End-to-end tests completed successfully" >> $GITHUB_STEP_SUMMARY
+ echo "- Application is functioning as expected" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "### ❌ Test Results" >> $GITHUB_STEP_SUMMARY
+ echo "- All test attempts failed" >> $GITHUB_STEP_SUMMARY
+ echo "- Check the e2e-test/test job for detailed error information" >> $GITHUB_STEP_SUMMARY
+ fi
\ No newline at end of file
diff --git a/.github/workflows/test-automation.yml b/archive-doc-gen/.github/workflows/test-automation.yml
similarity index 100%
rename from .github/workflows/test-automation.yml
rename to archive-doc-gen/.github/workflows/test-automation.yml
diff --git a/.github/workflows/tests.yml b/archive-doc-gen/.github/workflows/tests.yml
similarity index 100%
rename from .github/workflows/tests.yml
rename to archive-doc-gen/.github/workflows/tests.yml
diff --git a/archive-doc-gen/.gitignore b/archive-doc-gen/.gitignore
new file mode 100644
index 000000000..0abb7a034
--- /dev/null
+++ b/archive-doc-gen/.gitignore
@@ -0,0 +1,15 @@
+.venv
+
+.env
+.azure/
+__pycache__/
+.ipynb_checkpoints/
+
+
+venv
+myenv
+
+scriptsenv/
+
+scriptenv
+pdf
\ No newline at end of file
diff --git a/.vscode/launch.json b/archive-doc-gen/.vscode/launch.json
similarity index 100%
rename from .vscode/launch.json
rename to archive-doc-gen/.vscode/launch.json
diff --git a/.vscode/settings.json b/archive-doc-gen/.vscode/settings.json
similarity index 100%
rename from .vscode/settings.json
rename to archive-doc-gen/.vscode/settings.json
diff --git a/archive-doc-gen/CODE_OF_CONDUCT.md b/archive-doc-gen/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..f9ba8cf65
--- /dev/null
+++ b/archive-doc-gen/CODE_OF_CONDUCT.md
@@ -0,0 +1,9 @@
+# Microsoft Open Source Code of Conduct
+
+This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
+
+Resources:
+
+- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
+- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
+- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
diff --git a/archive-doc-gen/CONTRIBUTING.md b/archive-doc-gen/CONTRIBUTING.md
new file mode 100644
index 000000000..c282e9a1a
--- /dev/null
+++ b/archive-doc-gen/CONTRIBUTING.md
@@ -0,0 +1,14 @@
+# Contributing
+
+This project welcomes contributions and suggestions. Most contributions require you to
+agree to a Contributor License Agreement (CLA) declaring that you have the right to,
+and actually do, grant us the rights to use your contribution. For details, visit
+https://cla.microsoft.com.
+
+When you submit a pull request, a CLA-bot will automatically determine whether you need
+to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the
+instructions provided by the bot. You will only need to do this once across all repositories using our CLA.
+
+This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
+For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
+or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
\ No newline at end of file
diff --git a/archive-doc-gen/LICENSE b/archive-doc-gen/LICENSE
new file mode 100644
index 000000000..9e841e7a2
--- /dev/null
+++ b/archive-doc-gen/LICENSE
@@ -0,0 +1,21 @@
+ MIT License
+
+ Copyright (c) Microsoft Corporation.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE
diff --git a/archive-doc-gen/README.md b/archive-doc-gen/README.md
new file mode 100644
index 000000000..4db342838
--- /dev/null
+++ b/archive-doc-gen/README.md
@@ -0,0 +1,228 @@
+# Document generation solution accelerator
+
+This solution accelerator is a powerful tool that helps you create your own AI assistant for document generation. The accelerator can be used by any customer looking for reusable architecture and code snippets to build an AI assistant to generate a sample template and content grounded on their own enterprise data.
+
+This example focuses on a generic use case - chat with your own data, generate a document template using your own data, and exporting the document in a docx format.
+
+
+
+
+
+
+**Note:** With any AI solutions you create using these templates, you are responsible for assessing all associated risks and for complying with all applicable laws and safety standards. Learn more in the transparency documents for [Agent Service](https://learn.microsoft.com/en-us/azure/ai-foundry/responsible-ai/agents/transparency-note) and [Agent Framework](https://github.com/microsoft/agent-framework/blob/main/TRANSPARENCY_FAQ.md).
+
+
+
+Solution overview
+
+
+It leverages Azure OpenAI Service and Azure AI Search, to identify relevant documents, summarize unstructured information, and generate document templates.
+
+The sample data is sourced from generic AI-generated promissory notes. The documents are intended for use as sample data only.
+
+### Solution architecture
+||
+|---|
+
+
+
+
+### Additional resources
+
+[Azure OpenAI Service](https://learn.microsoft.com/en-us/azure/ai-services/openai/)
+
+[Azure AI Search](https://learn.microsoft.com/en-us/azure/search/)
+
+[Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-studio/)
+
+
+
+
+### Key features
+
+ Click to learn more about the key features this solution enables
+
+ - **Semantic search**
+ Azure AI Search to enable RAG and grounding of the application on the processed dataset.
+
+ - **Summarization**
+ Azure OpenAI Service and GPT models to help summarize the search content and answer questions.
+
+ - **Content generation**
+ Azure OpenAI Service and GPT models to help generate relevant content with Prompt Flow.
+
+
+
+
+
+
+
+Quick deploy
+
+
+### How to install or deploy
+Follow the quick deploy steps on the deployment guide to deploy this solution to your own Azure subscription.
+
+> **Note:** This solution accelerator requires **Azure Developer CLI (azd) version 1.18.0 or higher**. Please ensure you have the latest version installed before proceeding with deployment. [Download azd here](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd).
+
+[Click here to launch the deployment guide](./docs/DeploymentGuide.md)
+
+
+**For Local Development**
+- [Local Development Setup Guide](docs/LocalDevelopmentSetup.md) - Comprehensive setup instructions for Windows, Linux, and macOS
+
+| [](https://codespaces.new/microsoft/document-generation-solution-accelerator) | [](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/document-generation-solution-accelerator) | [&message=Open&color=blue&logo=visualstudiocode&logoColor=white)](https://vscode.dev/azure/?vscode-azure-exp=foundry&agentPayload=eyJiYXNlVXJsIjogImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9taWNyb3NvZnQvZG9jdW1lbnQtZ2VuZXJhdGlvbi1zb2x1dGlvbi1hY2NlbGVyYXRvci9yZWZzL2hlYWRzL21haW4vaW5mcmEvdnNjb2RlX3dlYiIsICJpbmRleFVybCI6ICIvaW5kZXguanNvbiIsICJ2YXJpYWJsZXMiOiB7ImFnZW50SWQiOiAiIiwgImNvbm5lY3Rpb25TdHJpbmciOiAiIiwgInRocmVhZElkIjogIiIsICJ1c2VyTWVzc2FnZSI6ICIiLCAicGxheWdyb3VuZE5hbWUiOiAiIiwgImxvY2F0aW9uIjogIiIsICJzdWJzY3JpcHRpb25JZCI6ICIiLCAicmVzb3VyY2VJZCI6ICIiLCAicHJvamVjdFJlc291cmNlSWQiOiAiIiwgImVuZHBvaW50IjogIiJ9LCAiY29kZVJvdXRlIjogWyJhaS1wcm9qZWN0cy1zZGsiLCAicHl0aG9uIiwgImRlZmF1bHQtYXp1cmUtYXV0aCIsICJlbmRwb2ludCJdfQ==) |
+|---|---|---|
+
+
+
+> ⚠️ **Important: Check Azure OpenAI Quota Availability**
+ To ensure sufficient quota is available in your subscription, please follow [quota check instructions guide](./docs/QuotaCheck.md) before you deploy the solution.
+
+
+
+### Prerequisites and costs
+
+To deploy this solution accelerator, ensure you have access to an [Azure subscription](https://azure.microsoft.com/free/) with the necessary permissions to create **resource groups, resources, app registrations, and assign roles at the resource group level**. This should include Contributor role at the subscription level and Role Based Access Control role on the subscription and/or resource group level. Follow the steps in [Azure Account Set Up](./docs/AzureAccountSetUp.md).
+
+Check the [Azure Products by Region](https://azure.microsoft.com/en-us/explore/global-infrastructure/products-by-region/?products=all®ions=all) page and select a **region** where the following services are available.
+
+Pricing varies per region and usage, so it isn't possible to predict exact costs for your usage. The majority of the Azure resources used in this infrastructure are on usage-based pricing tiers. However, Azure Container Registry has a fixed cost per registry per day.
+
+Use the [Azure pricing calculator](https://azure.microsoft.com/en-us/pricing/calculator) to calculate the cost of this solution in your subscription.
+
+Review a [sample pricing sheet](https://azure.com/e/2402502429fc46429e395e0bb93d0711) in the event you want to customize and scale usage.
+
+_Note: This is not meant to outline all costs as selected SKUs, scaled use, customizations, and integrations into your own tenant can affect the total consumption of this sample solution. The sample pricing sheet is meant to give you a starting point to customize the estimate for your specific needs._
+
+
+
+| Product | Description | Cost |
+|---|---|---|
+| [Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-foundry/) | Free tier. Build generative AI applications on an enterprise-grade platform. | [Pricing](https://azure.microsoft.com/pricing/details/ai-studio/) |
+| [Azure AI Search](https://learn.microsoft.com/en-us/azure/search/) | Standard tier, S1. Pricing is based on the number of documents and operations. Information retrieval at scale for vector and text content in traditional or generative search scenarios. | [Pricing](https://azure.microsoft.com/pricing/details/search/) |
+| [Azure Storage Account](https://learn.microsoft.com/en-us/azure/storage/blobs/) | Standard tier, LRS. Pricing is based on storage and operations. Blob storage in the clopud, optimized for storing massive amounts of unstructured data. | [Pricing](https://azure.microsoft.com/pricing/details/storage/blobs/) |
+| [Azure Key Vault](https://learn.microsoft.com/en-us/azure/key-vault/) | Standard tier. Pricing is based on the number of operations. Maintain keys that access and encrypt your cloud resources, apps, and solutions. | [Pricing](https://azure.microsoft.com/pricing/details/key-vault/) |
+| [Azure AI Services](https://learn.microsoft.com/en-us/azure/ai-services/) | S0 tier, defaults to gpt-4.1 and text-embedding-ada-002 models. Pricing is based on token count. | [Pricing](https://azure.microsoft.com/pricing/details/cognitive-services/) |
+| [Azure Container App](https://learn.microsoft.com/en-us/azure/container-apps/) | Consumption tier with 0.5 CPU, 1GiB memory/storage. Pricing is based on resource allocation, and each month allows for a certain amount of free usage. Allows you to run containerized applications without worrying about orchestration or infrastructure. | [Pricing](https://azure.microsoft.com/pricing/details/container-apps/) |
+| [Azure Container Registry](https://learn.microsoft.com/en-us/azure/container-registry/) | Basic tier. Build, store, and manage container images and artifacts in a private registry for all types of container deployments | [Pricing](https://azure.microsoft.com/pricing/details/container-registry/) |
+| [Log analytics](https://learn.microsoft.com/en-us/azure/azure-monitor/) | Pay-as-you-go tier. Costs based on data ingested. Collect and analyze on telemetry data generated by Azure. | [Pricing](https://azure.microsoft.com/pricing/details/monitor/) |
+| [Azure Cosmos DB](https://learn.microsoft.com/en-us/azure/cosmos-db/) | Fully managed, distributed NoSQL, relational, and vector database for modern app development. | [Pricing](https://azure.microsoft.com/en-us/pricing/details/cosmos-db/autoscale-provisioned/) |
+
+
+
+
+
+>⚠️ **Important:** To avoid unnecessary costs, remember to take down your app if it's no longer in use,
+either by deleting the resource group in the Portal or running `azd down`.
+
+
+
+Business Scenario
+
+
+
+||
+|---|
+
+
+
+Put your data to work by reducing blank page anxiety, speeding up document drafting, improving draft document quality, and reference information quickly - keeping experts in their expertise. Draft document templates for your organization including Invoices, End-user Contracts, Purchase Orders, Investment Proposals, and Grant Submissions.
+
+⚠️ The sample data used in this repository is synthetic and generated using Azure OpenAI Service. The data is intended for use as sample data only.
+
+
+### Business value
+
+ Click to learn more about what value this solution provides
+
+ - **Draft templates quickly**
+ Put your data to work to create any kind of document that is supported by a large data library.
+
+ - **Share**
+ Share with co-authors, contributors and approvers quickly.
+
+ - **Contextualize information**
+ Provide context using natural language. Primary and secondary queries allow for access to supplemental detail – reducing cognitive load, increasing efficiency, and enabling focus on higher value work.
+
+ - **Gain confidence in responses**
+ Trust responses to queries by customizing how data is referenced and returned to users, reducing the risk of hallucinated responses.
Access reference documents in the same chat window to get more detail and confirm accuracy.
+
+ - **Secure data and responsible AI for innovation**
+ Improve data security to minimize breaches, fostering a culture of responsible AI adoption, maximize innovation opportunities, and sustain competitive edge.
+
+
+
+
+
+
+
+Supporting documentation
+
+
+### Security guidelines
+
+This template uses Azure Key Vault to store all connections to communicate between resources.
+
+This template also uses [Managed Identity](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview) for local development and deployment.
+
+To ensure continued best practices in your own repository, we recommend that anyone creating solutions based on our templates ensure that the [Github secret scanning](https://docs.github.com/code-security/secret-scanning/about-secret-scanning) setting is enabled.
+
+You may want to consider additional security measures, such as:
+
+* Enabling Microsoft Defender for Cloud to [secure your Azure resources](https://learn.microsoft.com/azure/defender-for-cloud).
+* Protecting the Azure Container Apps instance with a [firewall](https://learn.microsoft.com/azure/container-apps/waf-app-gateway) and/or [Virtual Network](https://learn.microsoft.com/azure/container-apps/networking?tabs=workload-profiles-env%2Cazure-cli).
+
+
+
+### Cross references
+Check out similar solution accelerators
+
+| Solution Accelerator | Description |
+|---|---|
+| [Chat with your data](https://github.com/Azure-Samples/chat-with-your-data-solution-accelerator) | Chat with their own data by combining Azure Cognitive Search and Large Language Models (LLMs) to create a conversational search experience. It enables increased user efficiency by minimizing endpoints required to access internal company knowledgebases. |
+| [Document knowledge mining](https://github.com/microsoft/Document-Knowledge-Mining-Solution-Accelerator) | Built on Azure OpenAI Service and Azure AI Document Intelligence to process and extract summaries, entities, and metadata from unstructured, multi-modal documents and enable searching and chatting over this data. |
+| [Build your own copilot](https://github.com/microsoft/Build-your-own-copilot-Solution-Accelerator) | Helps client advisors to save time and prepare relevant discussion topics for scheduled meetings with overviews, client profile views, and chatting with structured data. |
+
+
+
+
+
+## Provide feedback
+
+Have questions, find a bug, or want to request a feature? [Submit a new issue](https://github.com/microsoft/document-generation-solution-accelerator/issues) on this repo and we'll connect.
+
+
+
+## Responsible AI Transparency FAQ
+Please refer to [Transparency FAQ](./docs/TRANSPARENCY_FAQ.md) for responsible AI transparency details of this solution accelerator.
+
+
+
+## Disclaimers
+
+This release is an artificial intelligence (AI) system that generates text based on user input. The text generated by this system may include ungrounded content, meaning that it is not verified by any reliable source or based on any factual data. The data included in this release is synthetic, meaning that it is artificially created by the system and may contain factual errors or inconsistencies. Users of this release are responsible for determining the accuracy, validity, and suitability of any content generated by the system for their intended purposes. Users should not rely on the system output as a source of truth or as a substitute for human judgment or expertise.
+
+This release only supports English language input and output. Users should not attempt to use the system with any other language or format. The system output may not be compatible with any translation tools or services, and may lose its meaning or coherence if translated.
+
+This release does not reflect the opinions, views, or values of Microsoft Corporation or any of its affiliates, subsidiaries, or partners. The system output is solely based on the system's own logic and algorithms, and does not represent any endorsement, recommendation, or advice from Microsoft or any other entity. Microsoft disclaims any liability or responsibility for any damages, losses, or harms arising from the use of this release or its output by any user or third party.
+
+This release does not provide any financial advice, and is not designed to replace the role of qualified client advisors in appropriately advising clients. Users should not use the system output for any financial decisions or transactions, and should consult with a professional financial advisor before taking any action based on the system output. Microsoft is not a financial institution or a fiduciary, and does not offer any financial products or services through this release or its output.
+
+This release is intended as a proof of concept only, and is not a finished or polished product. It is not intended for commercial use or distribution, and is subject to change or discontinuation without notice. Any planned deployment of this release or its output should include comprehensive testing and evaluation to ensure it is fit for purpose and meets the user's requirements and expectations. Microsoft does not guarantee the quality, performance, reliability, or availability of this release or its output, and does not provide any warranty or support for it.
+
+This Software requires the use of third-party components which are governed by separate proprietary or open-source licenses as identified below, and you must comply with the terms of each applicable license in order to use the Software. You acknowledge and agree that this license does not grant you a license or other right to use any such third-party proprietary or open-source components.
+
+To the extent that the Software includes components or code used in or derived from Microsoft products or services, including without limitation Microsoft Azure Services (collectively, “Microsoft Products and Services”), you must also comply with the Product Terms applicable to such Microsoft Products and Services. You acknowledge and agree that the license governing the Software does not grant you a license or other right to use Microsoft Products and Services. Nothing in the license or this ReadMe file will serve to supersede, amend, terminate or modify any terms in the Product Terms for any Microsoft Products and Services.
+
+You must also comply with all domestic and international export laws and regulations that apply to the Software, which include restrictions on destinations, end users, and end use. For further information on export restrictions, visit https://aka.ms/exporting.
+
+You acknowledge that the Software and Microsoft Products and Services (1) are not designed, intended or made available as a medical device(s), and (2) are not designed or intended to be a substitute for professional medical advice, diagnosis, treatment, or judgment and should not be used to replace or as a substitute for professional medical advice, diagnosis, treatment, or judgment. Customer is solely responsible for displaying and/or obtaining appropriate consents, warnings, disclaimers, and acknowledgements to end users of Customer’s implementation of the Online Services.
+
+You acknowledge the Software is not subject to SOC 1 and SOC 2 compliance audits. No Microsoft technology, nor any of its component technologies, including the Software, is intended or made available as a substitute for the professional advice, opinion, or judgment of a certified financial services professional. Do not use the Software to replace, substitute, or provide professional financial advice or judgment.
+
+BY ACCESSING OR USING THE SOFTWARE, YOU ACKNOWLEDGE THAT THE SOFTWARE IS NOT DESIGNED OR INTENDED TO SUPPORT ANY USE IN WHICH A SERVICE INTERRUPTION, DEFECT, ERROR, OR OTHER FAILURE OF THE SOFTWARE COULD RESULT IN THE DEATH OR SERIOUS BODILY INJURY OF ANY PERSON OR IN PHYSICAL OR ENVIRONMENTAL DAMAGE (COLLECTIVELY, “HIGH-RISK USE”), AND THAT YOU WILL ENSURE THAT, IN THE EVENT OF ANY INTERRUPTION, DEFECT, ERROR, OR OTHER FAILURE OF THE SOFTWARE, THE SAFETY OF PEOPLE, PROPERTY, AND THE ENVIRONMENT ARE NOT REDUCED BELOW A LEVEL THAT IS REASONABLY, APPROPRIATE, AND LEGAL, WHETHER IN GENERAL OR IN A SPECIFIC INDUSTRY. BY ACCESSING THE SOFTWARE, YOU FURTHER ACKNOWLEDGE THAT YOUR HIGH-RISK USE OF THE SOFTWARE IS AT YOUR OWN RISK.
diff --git a/archive-doc-gen/SECURITY.md b/archive-doc-gen/SECURITY.md
new file mode 100644
index 000000000..96d73bc27
--- /dev/null
+++ b/archive-doc-gen/SECURITY.md
@@ -0,0 +1,41 @@
+
+
+## Security
+
+Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin).
+
+If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below.
+
+## Reporting Security Issues
+
+**Please do not report security vulnerabilities through public GitHub issues.**
+
+Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report).
+
+If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp).
+
+You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
+
+Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
+
+ * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
+ * Full paths of source file(s) related to the manifestation of the issue
+ * The location of the affected source code (tag/branch/commit or direct URL)
+ * Any special configuration required to reproduce the issue
+ * Step-by-step instructions to reproduce the issue
+ * Proof-of-concept or exploit code (if possible)
+ * Impact of the issue, including how an attacker might exploit the issue
+
+This information will help us triage your report more quickly.
+
+If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs.
+
+## Preferred Languages
+
+We prefer all communications to be in English.
+
+## Policy
+
+Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd).
+
+
\ No newline at end of file
diff --git a/archive-doc-gen/SUPPORT.md b/archive-doc-gen/SUPPORT.md
new file mode 100644
index 000000000..2c42db0f8
--- /dev/null
+++ b/archive-doc-gen/SUPPORT.md
@@ -0,0 +1,13 @@
+# Support
+
+## How to file issues and get help
+
+This project uses GitHub Issues to track bugs and feature requests. Please search the existing
+issues before filing new issues to avoid duplicates. For new issues, file your bug or
+feature request as a new Issue.
+
+For help and questions about using this project, please submit an issue on this repository.
+
+## Microsoft Support Policy
+
+Support for this repository is limited to the resources listed above.
\ No newline at end of file
diff --git a/app-azure.yaml b/archive-doc-gen/app-azure.yaml
similarity index 100%
rename from app-azure.yaml
rename to archive-doc-gen/app-azure.yaml
diff --git a/azure.yaml b/archive-doc-gen/azure.yaml
similarity index 100%
rename from azure.yaml
rename to archive-doc-gen/azure.yaml
diff --git a/archive-doc-gen/azure_custom.yaml b/archive-doc-gen/azure_custom.yaml
new file mode 100644
index 000000000..af8bae654
--- /dev/null
+++ b/archive-doc-gen/azure_custom.yaml
@@ -0,0 +1,48 @@
+environment:
+ name: document-generation
+ location: eastus
+
+name: document-generation
+metadata:
+ template: document-generation@1.0
+
+requiredVersions:
+ azd: '>= 1.18.0'
+
+parameters:
+ solutionPrefix:
+ type: string
+ default: bs-azdtest
+ otherLocation:
+ type: string
+ default: eastus2
+ baseUrl:
+ type: string
+ default: 'https://github.com/microsoft/document-generation-solution-accelerator'
+
+services:
+ webapp:
+ project: ./src
+ language: py
+ host: appservice
+ dist: ./dist
+ hooks:
+ prepackage:
+ windows:
+ shell: pwsh
+ run: ../infra/scripts/package_webapp.ps1
+ interactive: true
+ continueOnError: false
+ posix:
+ shell: sh
+ run: bash ../infra/scripts/package_webapp.sh
+ interactive: true
+ continueOnError: false
+
+deployment:
+ mode: Incremental
+ template: ./infra/main.bicep # Path to the main.bicep file inside the 'deployment' folder
+ parameters:
+ solutionPrefix: ${parameters.solutionPrefix}
+ otherLocation: ${parameters.otherLocation}
+ baseUrl: ${parameters.baseUrl}
diff --git a/docs/ACRBuildAndPushGuide.md b/archive-doc-gen/docs/ACRBuildAndPushGuide.md
similarity index 100%
rename from docs/ACRBuildAndPushGuide.md
rename to archive-doc-gen/docs/ACRBuildAndPushGuide.md
diff --git a/docs/AppAuthentication.md b/archive-doc-gen/docs/AppAuthentication.md
similarity index 100%
rename from docs/AppAuthentication.md
rename to archive-doc-gen/docs/AppAuthentication.md
diff --git a/docs/AzureAccountSetUp.md b/archive-doc-gen/docs/AzureAccountSetUp.md
similarity index 100%
rename from docs/AzureAccountSetUp.md
rename to archive-doc-gen/docs/AzureAccountSetUp.md
diff --git a/docs/AzureGPTQuotaSettings.md b/archive-doc-gen/docs/AzureGPTQuotaSettings.md
similarity index 100%
rename from docs/AzureGPTQuotaSettings.md
rename to archive-doc-gen/docs/AzureGPTQuotaSettings.md
diff --git a/docs/AzureSemanticSearchRegion.md b/archive-doc-gen/docs/AzureSemanticSearchRegion.md
similarity index 100%
rename from docs/AzureSemanticSearchRegion.md
rename to archive-doc-gen/docs/AzureSemanticSearchRegion.md
diff --git a/docs/CustomizingAzdParameters.md b/archive-doc-gen/docs/CustomizingAzdParameters.md
similarity index 100%
rename from docs/CustomizingAzdParameters.md
rename to archive-doc-gen/docs/CustomizingAzdParameters.md
diff --git a/docs/DeleteResourceGroup.md b/archive-doc-gen/docs/DeleteResourceGroup.md
similarity index 100%
rename from docs/DeleteResourceGroup.md
rename to archive-doc-gen/docs/DeleteResourceGroup.md
diff --git a/docs/DeploymentGuide.md b/archive-doc-gen/docs/DeploymentGuide.md
similarity index 100%
rename from docs/DeploymentGuide.md
rename to archive-doc-gen/docs/DeploymentGuide.md
diff --git a/archive-doc-gen/docs/LocalDevelopmentSetup.md b/archive-doc-gen/docs/LocalDevelopmentSetup.md
new file mode 100644
index 000000000..4635b89e8
--- /dev/null
+++ b/archive-doc-gen/docs/LocalDevelopmentSetup.md
@@ -0,0 +1,506 @@
+# Local Development Setup Guide
+
+This guide provides comprehensive instructions for setting up the Document Generation Solution Accelerator for local development across Windows and Linux platforms.
+
+## Important Setup Notes
+
+### Multi-Service Architecture
+
+This application consists of **two separate services** that run independently:
+
+1. **Backend API** - REST API server for the frontend
+2. **Frontend** - React-based user interface
+
+> **⚠️ Critical: Each service must run in its own terminal/console window**
+>
+> - **Do NOT close terminals** while services are running
+> - Open **2 separate terminal windows** for local development
+> - Each service will occupy its terminal and show live logs
+
+
+### Path Conventions
+
+**All paths in this guide are relative to the repository root directory:**
+
+```bash
+document-generation-solution-accelerator/ ← Repository root (start here)
+├── src/
+│ ├── backend/
+│ │ ├── api/ ← API endpoints and routes
+│ │ ├── auth/ ← Authentication modules
+│ │ ├── helpers/ ← Utility and helper functions
+│ │ ├── history/ ← Chat/session history management
+│ │ ├── security/ ← Security-related modules
+│ │ └── settings.py ← Backend configuration
+│ ├── frontend/
+│ │ ├── src/ ← React/TypeScript source
+│ │ └── package.json ← Frontend dependencies
+│ ├── static/ ← Static web assets
+│ ├── tests/ ← Unit and integration tests
+│ ├── app.py ← Main Flask application entry point
+│ ├── .env ← Main application config file
+│ └── requirements.txt ← Python dependencies
+├── scripts/
+│ ├── prepdocs.py ← Document processing script
+│ ├── auth_init.py ← Authentication setup
+│ ├── data_preparation.py ← Data pipeline scripts
+│ └── config.json ← Scripts configuration
+├── infra/
+│ ├── main.bicep ← Main infrastructure template
+│ ├── scripts/ ← Infrastructure scripts
+│ └── main.parameters.json ← Deployment parameters
+├── docs/ ← Documentation (you are here)
+└── tests/ ← End-to-end tests
+ └── e2e-test/
+```
+
+**Before starting any step, ensure you are in the repository root directory:**
+
+```bash
+# Verify you're in the correct location
+pwd # Linux/macOS - should show: .../document-generation-solution-accelerator
+Get-Location # Windows PowerShell - should show: ...\document-generation-solution-accelerator
+
+# If not, navigate to repository root
+cd path/to/document-generation-solution-accelerator
+```
+
+## Step 1: Prerequisites - Install Required Tools
+
+Install these tools before you start:
+- [Visual Studio Code](https://code.visualstudio.com/) with the following extensions:
+ - [Azure Tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-node-azure-pack)
+ - [Bicep](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep)
+ - [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python)
+- [Python 3.11](https://www.python.org/downloads/). **Important:** Check "Add Python to PATH" during installation.
+- [PowerShell 7.0+](https://github.com/PowerShell/PowerShell#get-powershell).
+- [Node.js (LTS)](https://nodejs.org/en).
+- [Git](https://git-scm.com/downloads).
+- [Azure Developer CLI (azd) v1.18.0+](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd).
+- [Microsoft ODBC Driver 17](https://learn.microsoft.com/en-us/sql/connect/odbc/download-odbc-driver-for-sql-server?view=sql-server-ver16) for SQL Server.
+
+
+### Windows Development
+
+#### Option 1: Native Windows (PowerShell)
+
+```powershell
+# Install Python 3.11+ and Git
+winget install Python.Python.3.11
+winget install Git.Git
+
+# Install Node.js for frontend
+winget install OpenJS.NodeJS.LTS
+
+# Install uv package manager
+py -3.11 -m pip install uv
+```
+
+**Note**: On Windows, use `py -3.11 -m uv` instead of `uv` for all commands to ensure you're using Python 3.11.
+
+#### Option 2: Windows with WSL2 (Recommended)
+
+```bash
+# Install WSL2 first (run in PowerShell as Administrator):
+# wsl --install -d Ubuntu
+
+# Then in WSL2 Ubuntu terminal:
+sudo apt update && sudo apt install python3.11 python3.11-venv git curl nodejs npm -y
+
+# Install uv
+curl -LsSf https://astral.sh/uv/install.sh | sh
+source ~/.bashrc
+```
+
+### Linux Development
+
+#### Ubuntu/Debian
+
+```bash
+# Install prerequisites
+sudo apt update && sudo apt install python3.11 python3.11-venv git curl nodejs npm -y
+
+# Install uv package manager
+curl -LsSf https://astral.sh/uv/install.sh | sh
+source ~/.bashrc
+```
+
+#### RHEL/CentOS/Fedora
+
+```bash
+# Install prerequisites
+sudo dnf install python3.11 python3.11-devel git curl gcc nodejs npm -y
+
+# Install uv
+curl -LsSf https://astral.sh/uv/install.sh | sh
+source ~/.bashrc
+```
+
+
+## Step 2: Clone the Repository
+
+Choose a location on your local machine where you want to store the project files. We recommend creating a dedicated folder for your development projects.
+
+#### Using Command Line/Terminal
+
+1. **Open your terminal or command prompt. Navigate to your desired directory and Clone the repository:**
+ ```bash
+ git clone https://github.com/microsoft/document-generation-solution-accelerator.git
+ ```
+
+2. **Navigate to the project directory:**
+ ```bash
+ cd document-generation-solution-accelerator
+ ```
+
+3. **Open the project in Visual Studio Code:**
+ ```bash
+ code .
+ ```
+
+
+## Step 3: Development Tools Setup
+
+### Visual Studio Code (Recommended)
+
+#### Required Extensions
+
+Create `.vscode/extensions.json` in the workspace root and copy the following JSON:
+
+```json
+{
+ "recommendations": [
+ "ms-python.python",
+ "ms-python.pylint",
+ "ms-python.black-formatter",
+ "ms-python.isort",
+ "ms-vscode-remote.remote-wsl",
+ "ms-vscode-remote.remote-containers",
+ "redhat.vscode-yaml",
+ "ms-vscode.azure-account",
+ "ms-python.mypy-type-checker"
+ ]
+}
+```
+
+VS Code will prompt you to install these recommended extensions when you open the workspace.
+
+#### Settings Configuration
+
+Create `.vscode/settings.json` and copy the following JSON:
+
+```json
+{
+ "python.defaultInterpreterPath": "./.venv/bin/python",
+ "python.terminal.activateEnvironment": true,
+ "python.formatting.provider": "black",
+ "python.linting.enabled": true,
+ "python.linting.pylintEnabled": true,
+ "python.testing.pytestEnabled": true,
+ "python.testing.unittestEnabled": false,
+ "files.associations": {
+ "*.yaml": "yaml",
+ "*.yml": "yaml"
+ }
+}
+```
+
+## Step 4: Azure Authentication Setup
+
+Before configuring services, authenticate with Azure:
+
+```bash
+# Login to Azure CLI
+az login
+
+# Set your subscription
+az account set --subscription "your-subscription-id"
+
+# Verify authentication
+az account show
+```
+
+## Step 5: Local Setup/Deployment
+
+Follow these steps to set up and run the application locally:
+
+## Local Deployment:
+
+You can refer the local deployment guide here: [Local Deployment Guide](https://github.com/microsoft/document-generation-solution-accelerator/blob/main/docs/DeploymentGuide.md)
+
+### 5.1. Open the App Folder
+Navigate to the `src` directory of the repository using Visual Studio Code.
+
+### 5.2. Configure Environment Variables
+- Copy the `.env.sample` file to a new file named `.env`.
+- Update the `.env` file with the required values from your Azure resource group in Azure Portal App Service environment variables.
+- You can get all env value in your deployed resource group under App Service:
+
+- Alternatively, if resources were
+provisioned using `azd provision` or `azd up`, a `.env` file is automatically generated in the `.azure//.env`
+file. To get your `` run `azd env list` to see which env is default.
+
+> **Note**: After adding all environment variables to the .env file, update the value of **'APP_ENV'** from:
+```
+APP_ENV="Prod"
+```
+**to:**
+```
+APP_ENV="Dev"
+```
+
+This change is required for running the application in local development mode.
+
+
+### 5.3. Required Azure RBAC Permissions
+
+To run the application locally, your Azure account needs the following role assignments on the deployed resources:
+
+#### 5.3.1. App Configuration Access
+```bash
+# Get your principal ID
+PRINCIPAL_ID=$(az ad signed-in-user show --query id -o tsv)
+
+# Assign App Configuration Data Reader role
+az role assignment create \
+ --assignee $PRINCIPAL_ID \
+ --role "App Configuration Data Reader" \
+ --scope "/subscriptions//resourceGroups//providers/Microsoft.AppConfiguration/configurationStores/"
+```
+
+#### 5.3.2. Cosmos DB Access
+```bash
+# Assign Cosmos DB Built-in Data Contributor role
+az cosmosdb sql role assignment create \
+ --account-name \
+ --resource-group \
+ --role-definition-name "Cosmos DB Built-in Data Contributor" \
+ --principal-id $PRINCIPAL_ID \
+ --scope "/"
+```
+> **Note**: After local deployment is complete, you need to execute the post-deployment script so that all the required roles will be assigned automatically.
+
+### 5.4. Running with Automated Script
+
+For convenience, you can use the provided startup scripts that handle environment setup and start both services:
+
+**Windows:**
+```cmd
+cd src
+.\start.cmd
+```
+
+**macOS/Linux:**
+```bash
+cd src
+chmod +x start.sh
+./start.sh
+```
+### 5.5. Start the Application
+- Run `start.cmd` (Windows) or `start.sh` (Linux/Mac) to:
+ - Install backend dependencies.
+ - Install frontend dependencies.
+ - Build the frontend.
+ - Start the backend server.
+- Alternatively, you can run the backend in debug mode using the VS Code debug configuration defined in `.vscode/launch.json`.
+
+
+## Step 6: Running Backend and Frontend Separately
+
+> **📋 Terminal Reminder**: This section requires **two separate terminal windows** - one for the Backend API and one for the Frontend. Keep both terminals open while running. All commands assume you start from the **repository root directory**.
+
+### 6.1. Create Virtual Environment (Recommended)
+
+Open your terminal and navigate to the root folder of the project, then create the virtual environment:
+
+```bash
+# Navigate to the project root folder
+cd document-generation-solution-accelerator
+
+# Create virtual environment in the root folder
+python -m venv .venv
+
+# Activate virtual environment (Windows)
+.venv/Scripts/activate
+
+# Activate virtual environment (macOS/Linux)
+source .venv/bin/activate
+```
+
+> **Note**: After activation, you should see `(.venv)` in your terminal prompt indicating the virtual environment is active.
+
+### 6.2. Install Dependencies and Run
+
+To develop and run the backend API locally:
+
+```bash
+# Navigate to the API folder (while virtual environment is activated)
+cd src/
+
+# Upgrade pip
+python -m pip install --upgrade pip
+
+# Install Python dependencies
+pip install -r requirements.txt
+
+# Install Frontend Packages
+cd frontend
+
+npm install
+npm run build
+
+# Run the backend API (Windows)
+cd ..
+
+start http://127.0.0.1:50505
+call python -m uvicorn app:app --port 50505 --reload
+
+# Run the backend API (MacOs)
+cd ..
+
+open http://127.0.0.1:50505
+python -m uvicorn app:app --port 50505 --reload
+
+# Run the backend API (Linux)
+cd ..
+
+xdg-open http://127.0.0.1:50505
+python -m uvicorn app:app --port 50505 --reload
+
+```
+
+> **Note**: Make sure your virtual environment is activated before running these commands. You should see `(.venv)` in your terminal prompt when the virtual environment is active.
+
+The App will run on `http://127.0.0.1:50505/#/` by default.
+
+## Step 7: Verify All Services Are Running
+
+Before using the application, confirm all services are running correctly:
+
+### 7.1. Terminal Status Checklist
+
+| Terminal | Service | Command | Expected Output | URL |
+|----------|---------|---------|-----------------|-----|
+| **Terminal 1** | Backend API | `python -m uvicorn app:app --port 50505 --reload` | `INFO: Application startup complete` | http://127.0.0.1:50505 |
+| **Terminal 2** | Frontend (Dev) | `npm run dev` | `Local: http://localhost:5173/` | http://localhost:5173 |
+
+### 7.2. Quick Verification
+
+**1. Check Backend API:**
+```bash
+# In a new terminal
+curl http://127.0.0.1:50505/health
+# Expected: {"status":"healthy"} or similar JSON response
+```
+
+**2. Check Frontend:**
+- Open browser to http://127.0.0.1:50505 (production build) or http://localhost:5173 (dev server)
+- Should see the Document Generation UI
+- If authentication is configured, you'll be redirected to Azure AD login
+
+### 7.3. Common Issues
+
+**Service not starting?**
+- Ensure you're in the correct directory (`src/` for backend)
+- Verify virtual environment is activated (you should see `(.venv)` in prompt)
+- Check that port is not already in use (50505 for API, 5173 for frontend dev)
+- Review error messages in the terminal
+
+**Can't access services?**
+- Verify firewall isn't blocking ports 50505 or 5173
+- Try `http://localhost:port` instead of `http://127.0.0.1:port`
+- Ensure services show "startup complete" messages
+
+## Step 8: Next Steps
+
+Once all services are running (as confirmed in Step 7), you can:
+
+1. **Access the Application**: Open `http://127.0.0.1:50505` in your browser to explore the Document Generation UI
+2. **Explore Sample Questions**: Follow [SampleQuestions.md](SampleQuestions.md) for example prompts and use cases
+3. **Understand the Architecture**: Review the codebase starting with `src/backend/` directory
+
+## Troubleshooting
+
+### Common Issues
+
+#### Python Version Issues
+
+```bash
+# Check available Python versions
+python3 --version
+python3.11 --version
+
+# If python3.11 not found, install it:
+# Ubuntu: sudo apt install python3.11
+# macOS: brew install python@3.11
+# Windows: winget install Python.Python.3.11
+```
+
+#### Virtual Environment Issues
+
+```bash
+# Recreate virtual environment
+rm -rf .venv # Linux/macOS
+# or Remove-Item -Recurse .venv # Windows PowerShell
+
+uv venv .venv
+# Activate and reinstall
+source .venv/bin/activate # Linux/macOS
+# or .\.venv\Scripts\Activate.ps1 # Windows
+uv sync --python 3.11
+```
+
+#### Permission Issues (Linux/macOS)
+
+```bash
+# Fix ownership of files
+sudo chown -R $USER:$USER .
+
+# Fix uv permissions
+chmod +x ~/.local/bin/uv
+```
+
+#### Windows-Specific Issues
+
+```powershell
+# PowerShell execution policy
+Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
+
+# Long path support (Windows 10 1607+, run as Administrator)
+New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force
+
+# SSL certificate issues
+python -m pip install uv
+```
+
+### Azure Authentication Issues
+
+```bash
+# Login to Azure CLI
+az login
+
+# Set subscription
+az account set --subscription "your-subscription-id"
+
+# Test authentication
+az account show
+```
+
+### Environment Variable Issues
+
+```bash
+# Check environment variables are loaded
+env | grep AZURE # Linux/macOS
+Get-ChildItem Env:AZURE* # Windows PowerShell
+
+# Validate .env file format
+cat .env | grep -v '^#' | grep '=' # Should show key=value pairs
+```
+
+## Related Documentation
+
+- [Deployment Guide](DeploymentGuide.md) - Instructions for production deployment.
+- [Delete Resource Group](DeleteResourceGroup.md) - Steps to safely delete the Azure resource group created for the solution.
+- [App Authentication Setup](AppAuthentication.md) - Guide to configure application authentication and add support for additional platforms.
+- [Powershell Setup](PowershellSetup.md) - Instructions for setting up PowerShell and required scripts.
+- [Quota Check](QuotaCheck.md) - Steps to verify Azure quotas and ensure required limits before deployment.
diff --git a/archive-doc-gen/docs/LogAnalyticsReplicationDisable.md b/archive-doc-gen/docs/LogAnalyticsReplicationDisable.md
new file mode 100644
index 000000000..f4379a84a
--- /dev/null
+++ b/archive-doc-gen/docs/LogAnalyticsReplicationDisable.md
@@ -0,0 +1,28 @@
+# 🛠 Handling Log Analytics Workspace Deletion with Replication Enabled
+
+If redundancy (replication) is enabled for your Log Analytics workspace, you must disable it before deleting the workspace or resource group. Otherwise, deletion will fail.
+
+## ✅ Steps to Disable Replication Before Deletion
+Run the following Azure CLI command. Note: This operation may take about 5 minutes to complete.
+
+```bash
+az resource update --ids "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{logAnalyticsName}" --set properties.replication.enabled=false
+```
+
+Replace:
+- `{subscriptionId}` → Your Azure subscription ID
+- `{resourceGroupName}` → The name of your resource group
+- `{logAnalyticsName}` → The name of your Log Analytics workspace
+
+Optional: Verify replication disabled (should output `false`):
+```bash
+az resource show --ids "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{logAnalyticsName}" --query properties.replication.enabled -o tsv
+```
+
+## ✅ After Disabling Replication
+You can safely delete:
+- The Log Analytics workspace (manual)
+- The resource group (manual), or
+- All provisioned resources via `azd down`
+
+Return to: [Deployment Guide](./DeploymentGuide.md)
diff --git a/docs/PowershellSetup.md b/archive-doc-gen/docs/PowershellSetup.md
similarity index 100%
rename from docs/PowershellSetup.md
rename to archive-doc-gen/docs/PowershellSetup.md
diff --git a/docs/QuotaCheck.md b/archive-doc-gen/docs/QuotaCheck.md
similarity index 100%
rename from docs/QuotaCheck.md
rename to archive-doc-gen/docs/QuotaCheck.md
diff --git a/docs/README_LOCAL.md b/archive-doc-gen/docs/README_LOCAL.md
similarity index 100%
rename from docs/README_LOCAL.md
rename to archive-doc-gen/docs/README_LOCAL.md
diff --git a/docs/SampleQuestions.md b/archive-doc-gen/docs/SampleQuestions.md
similarity index 100%
rename from docs/SampleQuestions.md
rename to archive-doc-gen/docs/SampleQuestions.md
diff --git a/docs/TRANSPARENCY_FAQ.md b/archive-doc-gen/docs/TRANSPARENCY_FAQ.md
similarity index 100%
rename from docs/TRANSPARENCY_FAQ.md
rename to archive-doc-gen/docs/TRANSPARENCY_FAQ.md
diff --git a/docs/TroubleShootingSteps.md b/archive-doc-gen/docs/TroubleShootingSteps.md
similarity index 100%
rename from docs/TroubleShootingSteps.md
rename to archive-doc-gen/docs/TroubleShootingSteps.md
diff --git a/docs/container_registry_migration.md b/archive-doc-gen/docs/container_registry_migration.md
similarity index 100%
rename from docs/container_registry_migration.md
rename to archive-doc-gen/docs/container_registry_migration.md
diff --git a/docs/create_new_app_registration.md b/archive-doc-gen/docs/create_new_app_registration.md
similarity index 100%
rename from docs/create_new_app_registration.md
rename to archive-doc-gen/docs/create_new_app_registration.md
diff --git a/docs/images/AddDetails.png b/archive-doc-gen/docs/images/AddDetails.png
similarity index 100%
rename from docs/images/AddDetails.png
rename to archive-doc-gen/docs/images/AddDetails.png
diff --git a/docs/images/AddPlatform.png b/archive-doc-gen/docs/images/AddPlatform.png
similarity index 100%
rename from docs/images/AddPlatform.png
rename to archive-doc-gen/docs/images/AddPlatform.png
diff --git a/docs/images/AddRedirectURL.png b/archive-doc-gen/docs/images/AddRedirectURL.png
similarity index 100%
rename from docs/images/AddRedirectURL.png
rename to archive-doc-gen/docs/images/AddRedirectURL.png
diff --git a/docs/images/AppAuthIdentityProvider.png b/archive-doc-gen/docs/images/AppAuthIdentityProvider.png
similarity index 100%
rename from docs/images/AppAuthIdentityProvider.png
rename to archive-doc-gen/docs/images/AppAuthIdentityProvider.png
diff --git a/docs/images/AppAuthIdentityProviderAdd.png b/archive-doc-gen/docs/images/AppAuthIdentityProviderAdd.png
similarity index 100%
rename from docs/images/AppAuthIdentityProviderAdd.png
rename to archive-doc-gen/docs/images/AppAuthIdentityProviderAdd.png
diff --git a/docs/images/AppAuthIdentityProviderAdded.png b/archive-doc-gen/docs/images/AppAuthIdentityProviderAdded.png
similarity index 100%
rename from docs/images/AppAuthIdentityProviderAdded.png
rename to archive-doc-gen/docs/images/AppAuthIdentityProviderAdded.png
diff --git a/docs/images/AppAuthentication.png b/archive-doc-gen/docs/images/AppAuthentication.png
similarity index 100%
rename from docs/images/AppAuthentication.png
rename to archive-doc-gen/docs/images/AppAuthentication.png
diff --git a/docs/images/AppAuthenticationIdentity.png b/archive-doc-gen/docs/images/AppAuthenticationIdentity.png
similarity index 100%
rename from docs/images/AppAuthenticationIdentity.png
rename to archive-doc-gen/docs/images/AppAuthenticationIdentity.png
diff --git a/docs/images/AppServiceContainer.png b/archive-doc-gen/docs/images/AppServiceContainer.png
similarity index 100%
rename from docs/images/AppServiceContainer.png
rename to archive-doc-gen/docs/images/AppServiceContainer.png
diff --git a/docs/images/Appregistrations.png b/archive-doc-gen/docs/images/Appregistrations.png
similarity index 100%
rename from docs/images/Appregistrations.png
rename to archive-doc-gen/docs/images/Appregistrations.png
diff --git a/docs/images/Archimage.png b/archive-doc-gen/docs/images/Archimage.png
similarity index 100%
rename from docs/images/Archimage.png
rename to archive-doc-gen/docs/images/Archimage.png
diff --git a/docs/images/AzureHomePage.png b/archive-doc-gen/docs/images/AzureHomePage.png
similarity index 100%
rename from docs/images/AzureHomePage.png
rename to archive-doc-gen/docs/images/AzureHomePage.png
diff --git a/docs/images/ContainerApp.png b/archive-doc-gen/docs/images/ContainerApp.png
similarity index 100%
rename from docs/images/ContainerApp.png
rename to archive-doc-gen/docs/images/ContainerApp.png
diff --git a/docs/images/DeleteRG.png b/archive-doc-gen/docs/images/DeleteRG.png
similarity index 100%
rename from docs/images/DeleteRG.png
rename to archive-doc-gen/docs/images/DeleteRG.png
diff --git a/docs/images/DocGen_Azure_AI_Foundry_Architecture.png b/archive-doc-gen/docs/images/DocGen_Azure_AI_Foundry_Architecture.png
similarity index 100%
rename from docs/images/DocGen_Azure_AI_Foundry_Architecture.png
rename to archive-doc-gen/docs/images/DocGen_Azure_AI_Foundry_Architecture.png
diff --git a/archive-doc-gen/docs/images/Enviorment_variables.png b/archive-doc-gen/docs/images/Enviorment_variables.png
new file mode 100644
index 000000000..f5539fb1d
Binary files /dev/null and b/archive-doc-gen/docs/images/Enviorment_variables.png differ
diff --git a/docs/images/GenerateDraft.png b/archive-doc-gen/docs/images/GenerateDraft.png
similarity index 100%
rename from docs/images/GenerateDraft.png
rename to archive-doc-gen/docs/images/GenerateDraft.png
diff --git a/docs/images/MicrosoftEntraID.png b/archive-doc-gen/docs/images/MicrosoftEntraID.png
similarity index 100%
rename from docs/images/MicrosoftEntraID.png
rename to archive-doc-gen/docs/images/MicrosoftEntraID.png
diff --git a/docs/images/NewRegistration.png b/archive-doc-gen/docs/images/NewRegistration.png
similarity index 100%
rename from docs/images/NewRegistration.png
rename to archive-doc-gen/docs/images/NewRegistration.png
diff --git a/docs/images/Web.png b/archive-doc-gen/docs/images/Web.png
similarity index 100%
rename from docs/images/Web.png
rename to archive-doc-gen/docs/images/Web.png
diff --git a/docs/images/WebAppURL.png b/archive-doc-gen/docs/images/WebAppURL.png
similarity index 100%
rename from docs/images/WebAppURL.png
rename to archive-doc-gen/docs/images/WebAppURL.png
diff --git a/docs/images/architecture.png b/archive-doc-gen/docs/images/architecture.png
similarity index 100%
rename from docs/images/architecture.png
rename to archive-doc-gen/docs/images/architecture.png
diff --git a/docs/images/customerTruth.png b/archive-doc-gen/docs/images/customerTruth.png
similarity index 100%
rename from docs/images/customerTruth.png
rename to archive-doc-gen/docs/images/customerTruth.png
diff --git a/docs/images/deleteservices.png b/archive-doc-gen/docs/images/deleteservices.png
similarity index 100%
rename from docs/images/deleteservices.png
rename to archive-doc-gen/docs/images/deleteservices.png
diff --git a/docs/images/deployment_center.png b/archive-doc-gen/docs/images/deployment_center.png
similarity index 100%
rename from docs/images/deployment_center.png
rename to archive-doc-gen/docs/images/deployment_center.png
diff --git a/docs/images/git_bash.png b/archive-doc-gen/docs/images/git_bash.png
similarity index 100%
rename from docs/images/git_bash.png
rename to archive-doc-gen/docs/images/git_bash.png
diff --git a/docs/images/keyfeatures.png b/archive-doc-gen/docs/images/keyfeatures.png
similarity index 100%
rename from docs/images/keyfeatures.png
rename to archive-doc-gen/docs/images/keyfeatures.png
diff --git a/docs/images/landing_page.png b/archive-doc-gen/docs/images/landing_page.png
similarity index 100%
rename from docs/images/landing_page.png
rename to archive-doc-gen/docs/images/landing_page.png
diff --git a/docs/images/logAnalytics.png b/archive-doc-gen/docs/images/logAnalytics.png
similarity index 100%
rename from docs/images/logAnalytics.png
rename to archive-doc-gen/docs/images/logAnalytics.png
diff --git a/docs/images/logAnalyticsJson.png b/archive-doc-gen/docs/images/logAnalyticsJson.png
similarity index 100%
rename from docs/images/logAnalyticsJson.png
rename to archive-doc-gen/docs/images/logAnalyticsJson.png
diff --git a/docs/images/logAnalyticsList.png b/archive-doc-gen/docs/images/logAnalyticsList.png
similarity index 100%
rename from docs/images/logAnalyticsList.png
rename to archive-doc-gen/docs/images/logAnalyticsList.png
diff --git a/docs/images/oneClickDeploy.png b/archive-doc-gen/docs/images/oneClickDeploy.png
similarity index 100%
rename from docs/images/oneClickDeploy.png
rename to archive-doc-gen/docs/images/oneClickDeploy.png
diff --git a/docs/images/quota-check-output.png b/archive-doc-gen/docs/images/quota-check-output.png
similarity index 100%
rename from docs/images/quota-check-output.png
rename to archive-doc-gen/docs/images/quota-check-output.png
diff --git a/docs/images/re_use_foundry_project/azure_ai_foundry_list.png b/archive-doc-gen/docs/images/re_use_foundry_project/azure_ai_foundry_list.png
similarity index 100%
rename from docs/images/re_use_foundry_project/azure_ai_foundry_list.png
rename to archive-doc-gen/docs/images/re_use_foundry_project/azure_ai_foundry_list.png
diff --git a/docs/images/re_use_foundry_project/navigate_to_projects.png b/archive-doc-gen/docs/images/re_use_foundry_project/navigate_to_projects.png
similarity index 100%
rename from docs/images/re_use_foundry_project/navigate_to_projects.png
rename to archive-doc-gen/docs/images/re_use_foundry_project/navigate_to_projects.png
diff --git a/docs/images/re_use_foundry_project/project_resource_id.png b/archive-doc-gen/docs/images/re_use_foundry_project/project_resource_id.png
similarity index 100%
rename from docs/images/re_use_foundry_project/project_resource_id.png
rename to archive-doc-gen/docs/images/re_use_foundry_project/project_resource_id.png
diff --git a/docs/images/re_use_log/logAnalytics.png b/archive-doc-gen/docs/images/re_use_log/logAnalytics.png
similarity index 100%
rename from docs/images/re_use_log/logAnalytics.png
rename to archive-doc-gen/docs/images/re_use_log/logAnalytics.png
diff --git a/docs/images/re_use_log/logAnalyticsJson.png b/archive-doc-gen/docs/images/re_use_log/logAnalyticsJson.png
similarity index 100%
rename from docs/images/re_use_log/logAnalyticsJson.png
rename to archive-doc-gen/docs/images/re_use_log/logAnalyticsJson.png
diff --git a/docs/images/re_use_log/logAnalyticsList.png b/archive-doc-gen/docs/images/re_use_log/logAnalyticsList.png
similarity index 100%
rename from docs/images/re_use_log/logAnalyticsList.png
rename to archive-doc-gen/docs/images/re_use_log/logAnalyticsList.png
diff --git a/docs/images/readme/business-scenario.png b/archive-doc-gen/docs/images/readme/business-scenario.png
similarity index 100%
rename from docs/images/readme/business-scenario.png
rename to archive-doc-gen/docs/images/readme/business-scenario.png
diff --git a/docs/images/readme/quick-deploy.png b/archive-doc-gen/docs/images/readme/quick-deploy.png
similarity index 100%
rename from docs/images/readme/quick-deploy.png
rename to archive-doc-gen/docs/images/readme/quick-deploy.png
diff --git a/docs/images/readme/solution-overview.png b/archive-doc-gen/docs/images/readme/solution-overview.png
similarity index 100%
rename from docs/images/readme/solution-overview.png
rename to archive-doc-gen/docs/images/readme/solution-overview.png
diff --git a/docs/images/readme/supporting-documentation.png b/archive-doc-gen/docs/images/readme/supporting-documentation.png
similarity index 100%
rename from docs/images/readme/supporting-documentation.png
rename to archive-doc-gen/docs/images/readme/supporting-documentation.png
diff --git a/docs/images/resource-groups.png b/archive-doc-gen/docs/images/resource-groups.png
similarity index 100%
rename from docs/images/resource-groups.png
rename to archive-doc-gen/docs/images/resource-groups.png
diff --git a/docs/images/resource_menu.png b/archive-doc-gen/docs/images/resource_menu.png
similarity index 100%
rename from docs/images/resource_menu.png
rename to archive-doc-gen/docs/images/resource_menu.png
diff --git a/docs/images/resourcegroup.png b/archive-doc-gen/docs/images/resourcegroup.png
similarity index 100%
rename from docs/images/resourcegroup.png
rename to archive-doc-gen/docs/images/resourcegroup.png
diff --git a/docs/images/resourcegroup1.png b/archive-doc-gen/docs/images/resourcegroup1.png
similarity index 100%
rename from docs/images/resourcegroup1.png
rename to archive-doc-gen/docs/images/resourcegroup1.png
diff --git a/docs/images/supportingDocuments.png b/archive-doc-gen/docs/images/supportingDocuments.png
similarity index 100%
rename from docs/images/supportingDocuments.png
rename to archive-doc-gen/docs/images/supportingDocuments.png
diff --git a/docs/images/userStory.png b/archive-doc-gen/docs/images/userStory.png
similarity index 100%
rename from docs/images/userStory.png
rename to archive-doc-gen/docs/images/userStory.png
diff --git a/docs/re-use-foundry-project.md b/archive-doc-gen/docs/re-use-foundry-project.md
similarity index 100%
rename from docs/re-use-foundry-project.md
rename to archive-doc-gen/docs/re-use-foundry-project.md
diff --git a/docs/re-use-log-analytics.md b/archive-doc-gen/docs/re-use-log-analytics.md
similarity index 100%
rename from docs/re-use-log-analytics.md
rename to archive-doc-gen/docs/re-use-log-analytics.md
diff --git a/infra/data/pdfdata.zip b/archive-doc-gen/infra/data/pdfdata.zip
similarity index 100%
rename from infra/data/pdfdata.zip
rename to archive-doc-gen/infra/data/pdfdata.zip
diff --git a/infra/main.bicep b/archive-doc-gen/infra/main.bicep
similarity index 100%
rename from infra/main.bicep
rename to archive-doc-gen/infra/main.bicep
diff --git a/infra/main.json b/archive-doc-gen/infra/main.json
similarity index 100%
rename from infra/main.json
rename to archive-doc-gen/infra/main.json
diff --git a/infra/main.parameters.json b/archive-doc-gen/infra/main.parameters.json
similarity index 100%
rename from infra/main.parameters.json
rename to archive-doc-gen/infra/main.parameters.json
diff --git a/infra/main.waf.parameters.json b/archive-doc-gen/infra/main.waf.parameters.json
similarity index 100%
rename from infra/main.waf.parameters.json
rename to archive-doc-gen/infra/main.waf.parameters.json
diff --git a/infra/main_custom.bicep b/archive-doc-gen/infra/main_custom.bicep
similarity index 100%
rename from infra/main_custom.bicep
rename to archive-doc-gen/infra/main_custom.bicep
diff --git a/infra/modules/ai-project.bicep b/archive-doc-gen/infra/modules/ai-project.bicep
similarity index 100%
rename from infra/modules/ai-project.bicep
rename to archive-doc-gen/infra/modules/ai-project.bicep
diff --git a/infra/modules/ai-services-deployments.bicep b/archive-doc-gen/infra/modules/ai-services-deployments.bicep
similarity index 100%
rename from infra/modules/ai-services-deployments.bicep
rename to archive-doc-gen/infra/modules/ai-services-deployments.bicep
diff --git a/infra/modules/deploy_aifp_aisearch_connection.bicep b/archive-doc-gen/infra/modules/deploy_aifp_aisearch_connection.bicep
similarity index 100%
rename from infra/modules/deploy_aifp_aisearch_connection.bicep
rename to archive-doc-gen/infra/modules/deploy_aifp_aisearch_connection.bicep
diff --git a/infra/modules/role-assignment.bicep b/archive-doc-gen/infra/modules/role-assignment.bicep
similarity index 100%
rename from infra/modules/role-assignment.bicep
rename to archive-doc-gen/infra/modules/role-assignment.bicep
diff --git a/infra/modules/virtualNetwork.bicep b/archive-doc-gen/infra/modules/virtualNetwork.bicep
similarity index 100%
rename from infra/modules/virtualNetwork.bicep
rename to archive-doc-gen/infra/modules/virtualNetwork.bicep
diff --git a/infra/modules/web-sites.bicep b/archive-doc-gen/infra/modules/web-sites.bicep
similarity index 100%
rename from infra/modules/web-sites.bicep
rename to archive-doc-gen/infra/modules/web-sites.bicep
diff --git a/infra/modules/web-sites.config.bicep b/archive-doc-gen/infra/modules/web-sites.config.bicep
similarity index 100%
rename from infra/modules/web-sites.config.bicep
rename to archive-doc-gen/infra/modules/web-sites.config.bicep
diff --git a/infra/scripts/add_cosmosdb_access.sh b/archive-doc-gen/infra/scripts/add_cosmosdb_access.sh
similarity index 100%
rename from infra/scripts/add_cosmosdb_access.sh
rename to archive-doc-gen/infra/scripts/add_cosmosdb_access.sh
diff --git a/infra/scripts/copy_kb_files.sh b/archive-doc-gen/infra/scripts/copy_kb_files.sh
similarity index 100%
rename from infra/scripts/copy_kb_files.sh
rename to archive-doc-gen/infra/scripts/copy_kb_files.sh
diff --git a/infra/scripts/index_scripts/01_create_search_index.py b/archive-doc-gen/infra/scripts/index_scripts/01_create_search_index.py
similarity index 100%
rename from infra/scripts/index_scripts/01_create_search_index.py
rename to archive-doc-gen/infra/scripts/index_scripts/01_create_search_index.py
diff --git a/infra/scripts/index_scripts/02_process_data.py b/archive-doc-gen/infra/scripts/index_scripts/02_process_data.py
similarity index 100%
rename from infra/scripts/index_scripts/02_process_data.py
rename to archive-doc-gen/infra/scripts/index_scripts/02_process_data.py
diff --git a/infra/scripts/index_scripts/requirements.txt b/archive-doc-gen/infra/scripts/index_scripts/requirements.txt
similarity index 100%
rename from infra/scripts/index_scripts/requirements.txt
rename to archive-doc-gen/infra/scripts/index_scripts/requirements.txt
diff --git a/infra/scripts/package_webapp.ps1 b/archive-doc-gen/infra/scripts/package_webapp.ps1
similarity index 100%
rename from infra/scripts/package_webapp.ps1
rename to archive-doc-gen/infra/scripts/package_webapp.ps1
diff --git a/infra/scripts/package_webapp.sh b/archive-doc-gen/infra/scripts/package_webapp.sh
similarity index 100%
rename from infra/scripts/package_webapp.sh
rename to archive-doc-gen/infra/scripts/package_webapp.sh
diff --git a/infra/scripts/process_sample_data.sh b/archive-doc-gen/infra/scripts/process_sample_data.sh
similarity index 100%
rename from infra/scripts/process_sample_data.sh
rename to archive-doc-gen/infra/scripts/process_sample_data.sh
diff --git a/infra/scripts/run_create_index_scripts.sh b/archive-doc-gen/infra/scripts/run_create_index_scripts.sh
similarity index 100%
rename from infra/scripts/run_create_index_scripts.sh
rename to archive-doc-gen/infra/scripts/run_create_index_scripts.sh
diff --git a/infra/vscode_web/.gitignore b/archive-doc-gen/infra/vscode_web/.gitignore
similarity index 100%
rename from infra/vscode_web/.gitignore
rename to archive-doc-gen/infra/vscode_web/.gitignore
diff --git a/infra/vscode_web/LICENSE b/archive-doc-gen/infra/vscode_web/LICENSE
similarity index 100%
rename from infra/vscode_web/LICENSE
rename to archive-doc-gen/infra/vscode_web/LICENSE
diff --git a/infra/vscode_web/README-noazd.md b/archive-doc-gen/infra/vscode_web/README-noazd.md
similarity index 100%
rename from infra/vscode_web/README-noazd.md
rename to archive-doc-gen/infra/vscode_web/README-noazd.md
diff --git a/infra/vscode_web/README.md b/archive-doc-gen/infra/vscode_web/README.md
similarity index 100%
rename from infra/vscode_web/README.md
rename to archive-doc-gen/infra/vscode_web/README.md
diff --git a/infra/vscode_web/codeSample.py b/archive-doc-gen/infra/vscode_web/codeSample.py
similarity index 100%
rename from infra/vscode_web/codeSample.py
rename to archive-doc-gen/infra/vscode_web/codeSample.py
diff --git a/infra/vscode_web/endpoint-requirements.txt b/archive-doc-gen/infra/vscode_web/endpoint-requirements.txt
similarity index 100%
rename from infra/vscode_web/endpoint-requirements.txt
rename to archive-doc-gen/infra/vscode_web/endpoint-requirements.txt
diff --git a/infra/vscode_web/endpointCodeSample.py b/archive-doc-gen/infra/vscode_web/endpointCodeSample.py
similarity index 100%
rename from infra/vscode_web/endpointCodeSample.py
rename to archive-doc-gen/infra/vscode_web/endpointCodeSample.py
diff --git a/infra/vscode_web/index.json b/archive-doc-gen/infra/vscode_web/index.json
similarity index 100%
rename from infra/vscode_web/index.json
rename to archive-doc-gen/infra/vscode_web/index.json
diff --git a/infra/vscode_web/install.sh b/archive-doc-gen/infra/vscode_web/install.sh
similarity index 100%
rename from infra/vscode_web/install.sh
rename to archive-doc-gen/infra/vscode_web/install.sh
diff --git a/infra/vscode_web/requirements.txt b/archive-doc-gen/infra/vscode_web/requirements.txt
similarity index 100%
rename from infra/vscode_web/requirements.txt
rename to archive-doc-gen/infra/vscode_web/requirements.txt
diff --git a/package-lock.json b/archive-doc-gen/package-lock.json
similarity index 100%
rename from package-lock.json
rename to archive-doc-gen/package-lock.json
diff --git a/scripts/SAMPLE_DATA.md b/archive-doc-gen/scripts/SAMPLE_DATA.md
similarity index 100%
rename from scripts/SAMPLE_DATA.md
rename to archive-doc-gen/scripts/SAMPLE_DATA.md
diff --git a/scripts/auth_init.ps1 b/archive-doc-gen/scripts/auth_init.ps1
similarity index 100%
rename from scripts/auth_init.ps1
rename to archive-doc-gen/scripts/auth_init.ps1
diff --git a/scripts/auth_init.py b/archive-doc-gen/scripts/auth_init.py
similarity index 100%
rename from scripts/auth_init.py
rename to archive-doc-gen/scripts/auth_init.py
diff --git a/scripts/auth_init.sh b/archive-doc-gen/scripts/auth_init.sh
similarity index 100%
rename from scripts/auth_init.sh
rename to archive-doc-gen/scripts/auth_init.sh
diff --git a/scripts/auth_update.ps1 b/archive-doc-gen/scripts/auth_update.ps1
similarity index 100%
rename from scripts/auth_update.ps1
rename to archive-doc-gen/scripts/auth_update.ps1
diff --git a/scripts/auth_update.py b/archive-doc-gen/scripts/auth_update.py
similarity index 100%
rename from scripts/auth_update.py
rename to archive-doc-gen/scripts/auth_update.py
diff --git a/scripts/auth_update.sh b/archive-doc-gen/scripts/auth_update.sh
similarity index 100%
rename from scripts/auth_update.sh
rename to archive-doc-gen/scripts/auth_update.sh
diff --git a/scripts/checkquota.sh b/archive-doc-gen/scripts/checkquota.sh
similarity index 100%
rename from scripts/checkquota.sh
rename to archive-doc-gen/scripts/checkquota.sh
diff --git a/scripts/chunk_documents.py b/archive-doc-gen/scripts/chunk_documents.py
similarity index 100%
rename from scripts/chunk_documents.py
rename to archive-doc-gen/scripts/chunk_documents.py
diff --git a/scripts/config.json b/archive-doc-gen/scripts/config.json
similarity index 100%
rename from scripts/config.json
rename to archive-doc-gen/scripts/config.json
diff --git a/scripts/data_preparation.py b/archive-doc-gen/scripts/data_preparation.py
similarity index 100%
rename from scripts/data_preparation.py
rename to archive-doc-gen/scripts/data_preparation.py
diff --git a/scripts/data_utils.py b/archive-doc-gen/scripts/data_utils.py
similarity index 100%
rename from scripts/data_utils.py
rename to archive-doc-gen/scripts/data_utils.py
diff --git a/scripts/embed_documents.py b/archive-doc-gen/scripts/embed_documents.py
similarity index 100%
rename from scripts/embed_documents.py
rename to archive-doc-gen/scripts/embed_documents.py
diff --git a/scripts/loadenv.ps1 b/archive-doc-gen/scripts/loadenv.ps1
similarity index 100%
rename from scripts/loadenv.ps1
rename to archive-doc-gen/scripts/loadenv.ps1
diff --git a/scripts/loadenv.sh b/archive-doc-gen/scripts/loadenv.sh
similarity index 100%
rename from scripts/loadenv.sh
rename to archive-doc-gen/scripts/loadenv.sh
diff --git a/scripts/prepdocs.ps1 b/archive-doc-gen/scripts/prepdocs.ps1
similarity index 100%
rename from scripts/prepdocs.ps1
rename to archive-doc-gen/scripts/prepdocs.ps1
diff --git a/scripts/prepdocs.py b/archive-doc-gen/scripts/prepdocs.py
similarity index 100%
rename from scripts/prepdocs.py
rename to archive-doc-gen/scripts/prepdocs.py
diff --git a/scripts/prepdocs.sh b/archive-doc-gen/scripts/prepdocs.sh
similarity index 100%
rename from scripts/prepdocs.sh
rename to archive-doc-gen/scripts/prepdocs.sh
diff --git a/scripts/quota_check_params.sh b/archive-doc-gen/scripts/quota_check_params.sh
similarity index 100%
rename from scripts/quota_check_params.sh
rename to archive-doc-gen/scripts/quota_check_params.sh
diff --git a/scripts/readme.md b/archive-doc-gen/scripts/readme.md
similarity index 100%
rename from scripts/readme.md
rename to archive-doc-gen/scripts/readme.md
diff --git a/scripts/role_assignment.sh b/archive-doc-gen/scripts/role_assignment.sh
similarity index 100%
rename from scripts/role_assignment.sh
rename to archive-doc-gen/scripts/role_assignment.sh
diff --git a/src/.dockerignore b/archive-doc-gen/src/.dockerignore
similarity index 100%
rename from src/.dockerignore
rename to archive-doc-gen/src/.dockerignore
diff --git a/src/.env.sample b/archive-doc-gen/src/.env.sample
similarity index 100%
rename from src/.env.sample
rename to archive-doc-gen/src/.env.sample
diff --git a/src/.gitignore b/archive-doc-gen/src/.gitignore
similarity index 100%
rename from src/.gitignore
rename to archive-doc-gen/src/.gitignore
diff --git a/src/SUPPORT.md b/archive-doc-gen/src/SUPPORT.md
similarity index 100%
rename from src/SUPPORT.md
rename to archive-doc-gen/src/SUPPORT.md
diff --git a/src/TEST_CASE_FLOWS.md b/archive-doc-gen/src/TEST_CASE_FLOWS.md
similarity index 100%
rename from src/TEST_CASE_FLOWS.md
rename to archive-doc-gen/src/TEST_CASE_FLOWS.md
diff --git a/src/WebApp.Dockerfile b/archive-doc-gen/src/WebApp.Dockerfile
similarity index 100%
rename from src/WebApp.Dockerfile
rename to archive-doc-gen/src/WebApp.Dockerfile
diff --git a/src/app.py b/archive-doc-gen/src/app.py
similarity index 100%
rename from src/app.py
rename to archive-doc-gen/src/app.py
diff --git a/src/backend/__init__.py b/archive-doc-gen/src/backend/__init__.py
similarity index 100%
rename from src/backend/__init__.py
rename to archive-doc-gen/src/backend/__init__.py
diff --git a/src/backend/api/agent/agent_factory_base.py b/archive-doc-gen/src/backend/api/agent/agent_factory_base.py
similarity index 100%
rename from src/backend/api/agent/agent_factory_base.py
rename to archive-doc-gen/src/backend/api/agent/agent_factory_base.py
diff --git a/src/backend/api/agent/browse_agent_factory.py b/archive-doc-gen/src/backend/api/agent/browse_agent_factory.py
similarity index 100%
rename from src/backend/api/agent/browse_agent_factory.py
rename to archive-doc-gen/src/backend/api/agent/browse_agent_factory.py
diff --git a/src/backend/api/agent/section_agent_factory.py b/archive-doc-gen/src/backend/api/agent/section_agent_factory.py
similarity index 100%
rename from src/backend/api/agent/section_agent_factory.py
rename to archive-doc-gen/src/backend/api/agent/section_agent_factory.py
diff --git a/src/backend/api/agent/template_agent_factory.py b/archive-doc-gen/src/backend/api/agent/template_agent_factory.py
similarity index 100%
rename from src/backend/api/agent/template_agent_factory.py
rename to archive-doc-gen/src/backend/api/agent/template_agent_factory.py
diff --git a/src/backend/auth/__init__.py b/archive-doc-gen/src/backend/auth/__init__.py
similarity index 100%
rename from src/backend/auth/__init__.py
rename to archive-doc-gen/src/backend/auth/__init__.py
diff --git a/src/backend/auth/auth_utils.py b/archive-doc-gen/src/backend/auth/auth_utils.py
similarity index 100%
rename from src/backend/auth/auth_utils.py
rename to archive-doc-gen/src/backend/auth/auth_utils.py
diff --git a/src/backend/auth/sample_user.py b/archive-doc-gen/src/backend/auth/sample_user.py
similarity index 100%
rename from src/backend/auth/sample_user.py
rename to archive-doc-gen/src/backend/auth/sample_user.py
diff --git a/src/backend/helpers/azure_credential_utils.py b/archive-doc-gen/src/backend/helpers/azure_credential_utils.py
similarity index 100%
rename from src/backend/helpers/azure_credential_utils.py
rename to archive-doc-gen/src/backend/helpers/azure_credential_utils.py
diff --git a/src/backend/history/cosmosdbservice.py b/archive-doc-gen/src/backend/history/cosmosdbservice.py
similarity index 100%
rename from src/backend/history/cosmosdbservice.py
rename to archive-doc-gen/src/backend/history/cosmosdbservice.py
diff --git a/src/backend/security/__init__.py b/archive-doc-gen/src/backend/security/__init__.py
similarity index 100%
rename from src/backend/security/__init__.py
rename to archive-doc-gen/src/backend/security/__init__.py
diff --git a/src/backend/security/ms_defender_utils.py b/archive-doc-gen/src/backend/security/ms_defender_utils.py
similarity index 100%
rename from src/backend/security/ms_defender_utils.py
rename to archive-doc-gen/src/backend/security/ms_defender_utils.py
diff --git a/src/backend/settings.py b/archive-doc-gen/src/backend/settings.py
similarity index 100%
rename from src/backend/settings.py
rename to archive-doc-gen/src/backend/settings.py
diff --git a/src/backend/utils.py b/archive-doc-gen/src/backend/utils.py
similarity index 100%
rename from src/backend/utils.py
rename to archive-doc-gen/src/backend/utils.py
diff --git a/src/event_utils.py b/archive-doc-gen/src/event_utils.py
similarity index 100%
rename from src/event_utils.py
rename to archive-doc-gen/src/event_utils.py
diff --git a/src/frontend/.eslintignore b/archive-doc-gen/src/frontend/.eslintignore
similarity index 100%
rename from src/frontend/.eslintignore
rename to archive-doc-gen/src/frontend/.eslintignore
diff --git a/src/frontend/.eslintrc.json b/archive-doc-gen/src/frontend/.eslintrc.json
similarity index 100%
rename from src/frontend/.eslintrc.json
rename to archive-doc-gen/src/frontend/.eslintrc.json
diff --git a/src/frontend/.prettierignore b/archive-doc-gen/src/frontend/.prettierignore
similarity index 100%
rename from src/frontend/.prettierignore
rename to archive-doc-gen/src/frontend/.prettierignore
diff --git a/src/frontend/.prettierrc.json b/archive-doc-gen/src/frontend/.prettierrc.json
similarity index 100%
rename from src/frontend/.prettierrc.json
rename to archive-doc-gen/src/frontend/.prettierrc.json
diff --git a/src/frontend/__mocks__/dompurify.ts b/archive-doc-gen/src/frontend/__mocks__/dompurify.ts
similarity index 100%
rename from src/frontend/__mocks__/dompurify.ts
rename to archive-doc-gen/src/frontend/__mocks__/dompurify.ts
diff --git a/src/frontend/__mocks__/fileMock.ts b/archive-doc-gen/src/frontend/__mocks__/fileMock.ts
similarity index 100%
rename from src/frontend/__mocks__/fileMock.ts
rename to archive-doc-gen/src/frontend/__mocks__/fileMock.ts
diff --git a/src/frontend/__mocks__/mockAPIData.ts b/archive-doc-gen/src/frontend/__mocks__/mockAPIData.ts
similarity index 100%
rename from src/frontend/__mocks__/mockAPIData.ts
rename to archive-doc-gen/src/frontend/__mocks__/mockAPIData.ts
diff --git a/src/frontend/__mocks__/react-markdown.tsx b/archive-doc-gen/src/frontend/__mocks__/react-markdown.tsx
similarity index 100%
rename from src/frontend/__mocks__/react-markdown.tsx
rename to archive-doc-gen/src/frontend/__mocks__/react-markdown.tsx
diff --git a/src/frontend/eslint.config.ts b/archive-doc-gen/src/frontend/eslint.config.ts
similarity index 100%
rename from src/frontend/eslint.config.ts
rename to archive-doc-gen/src/frontend/eslint.config.ts
diff --git a/src/frontend/index.html b/archive-doc-gen/src/frontend/index.html
similarity index 100%
rename from src/frontend/index.html
rename to archive-doc-gen/src/frontend/index.html
diff --git a/src/frontend/jest.config.ts b/archive-doc-gen/src/frontend/jest.config.ts
similarity index 100%
rename from src/frontend/jest.config.ts
rename to archive-doc-gen/src/frontend/jest.config.ts
diff --git a/src/frontend/jest.polyfills.js b/archive-doc-gen/src/frontend/jest.polyfills.js
similarity index 100%
rename from src/frontend/jest.polyfills.js
rename to archive-doc-gen/src/frontend/jest.polyfills.js
diff --git a/src/frontend/package-lock.json b/archive-doc-gen/src/frontend/package-lock.json
similarity index 100%
rename from src/frontend/package-lock.json
rename to archive-doc-gen/src/frontend/package-lock.json
diff --git a/src/frontend/package.json b/archive-doc-gen/src/frontend/package.json
similarity index 100%
rename from src/frontend/package.json
rename to archive-doc-gen/src/frontend/package.json
diff --git a/src/frontend/polyfills.js b/archive-doc-gen/src/frontend/polyfills.js
similarity index 100%
rename from src/frontend/polyfills.js
rename to archive-doc-gen/src/frontend/polyfills.js
diff --git a/src/frontend/public/favicon.ico b/archive-doc-gen/src/frontend/public/favicon.ico
similarity index 100%
rename from src/frontend/public/favicon.ico
rename to archive-doc-gen/src/frontend/public/favicon.ico
diff --git a/src/frontend/src/api/api.ts b/archive-doc-gen/src/frontend/src/api/api.ts
similarity index 100%
rename from src/frontend/src/api/api.ts
rename to archive-doc-gen/src/frontend/src/api/api.ts
diff --git a/src/frontend/src/api/index.ts b/archive-doc-gen/src/frontend/src/api/index.ts
similarity index 100%
rename from src/frontend/src/api/index.ts
rename to archive-doc-gen/src/frontend/src/api/index.ts
diff --git a/src/frontend/src/api/models.ts b/archive-doc-gen/src/frontend/src/api/models.ts
similarity index 100%
rename from src/frontend/src/api/models.ts
rename to archive-doc-gen/src/frontend/src/api/models.ts
diff --git a/src/frontend/src/assets/Azure.svg b/archive-doc-gen/src/frontend/src/assets/Azure.svg
similarity index 100%
rename from src/frontend/src/assets/Azure.svg
rename to archive-doc-gen/src/frontend/src/assets/Azure.svg
diff --git a/src/frontend/src/assets/ClearChat.svg b/archive-doc-gen/src/frontend/src/assets/ClearChat.svg
similarity index 100%
rename from src/frontend/src/assets/ClearChat.svg
rename to archive-doc-gen/src/frontend/src/assets/ClearChat.svg
diff --git a/src/frontend/src/assets/Contoso.svg b/archive-doc-gen/src/frontend/src/assets/Contoso.svg
similarity index 100%
rename from src/frontend/src/assets/Contoso.svg
rename to archive-doc-gen/src/frontend/src/assets/Contoso.svg
diff --git a/src/frontend/src/assets/Generate.svg b/archive-doc-gen/src/frontend/src/assets/Generate.svg
similarity index 100%
rename from src/frontend/src/assets/Generate.svg
rename to archive-doc-gen/src/frontend/src/assets/Generate.svg
diff --git a/src/frontend/src/assets/Send.svg b/archive-doc-gen/src/frontend/src/assets/Send.svg
similarity index 100%
rename from src/frontend/src/assets/Send.svg
rename to archive-doc-gen/src/frontend/src/assets/Send.svg
diff --git a/src/frontend/src/components/Answer/Answer.module.css b/archive-doc-gen/src/frontend/src/components/Answer/Answer.module.css
similarity index 100%
rename from src/frontend/src/components/Answer/Answer.module.css
rename to archive-doc-gen/src/frontend/src/components/Answer/Answer.module.css
diff --git a/src/frontend/src/components/Answer/Answer.test.tsx b/archive-doc-gen/src/frontend/src/components/Answer/Answer.test.tsx
similarity index 100%
rename from src/frontend/src/components/Answer/Answer.test.tsx
rename to archive-doc-gen/src/frontend/src/components/Answer/Answer.test.tsx
diff --git a/src/frontend/src/components/Answer/Answer.tsx b/archive-doc-gen/src/frontend/src/components/Answer/Answer.tsx
similarity index 100%
rename from src/frontend/src/components/Answer/Answer.tsx
rename to archive-doc-gen/src/frontend/src/components/Answer/Answer.tsx
diff --git a/src/frontend/src/components/Answer/AnswerParser.test.ts b/archive-doc-gen/src/frontend/src/components/Answer/AnswerParser.test.ts
similarity index 100%
rename from src/frontend/src/components/Answer/AnswerParser.test.ts
rename to archive-doc-gen/src/frontend/src/components/Answer/AnswerParser.test.ts
diff --git a/src/frontend/src/components/Answer/AnswerParser.tsx b/archive-doc-gen/src/frontend/src/components/Answer/AnswerParser.tsx
similarity index 100%
rename from src/frontend/src/components/Answer/AnswerParser.tsx
rename to archive-doc-gen/src/frontend/src/components/Answer/AnswerParser.tsx
diff --git a/src/frontend/src/components/Answer/index.ts b/archive-doc-gen/src/frontend/src/components/Answer/index.ts
similarity index 100%
rename from src/frontend/src/components/Answer/index.ts
rename to archive-doc-gen/src/frontend/src/components/Answer/index.ts
diff --git a/src/frontend/src/components/ChatHistory/ChatHistoryList.test.tsx b/archive-doc-gen/src/frontend/src/components/ChatHistory/ChatHistoryList.test.tsx
similarity index 100%
rename from src/frontend/src/components/ChatHistory/ChatHistoryList.test.tsx
rename to archive-doc-gen/src/frontend/src/components/ChatHistory/ChatHistoryList.test.tsx
diff --git a/src/frontend/src/components/ChatHistory/ChatHistoryList.tsx b/archive-doc-gen/src/frontend/src/components/ChatHistory/ChatHistoryList.tsx
similarity index 100%
rename from src/frontend/src/components/ChatHistory/ChatHistoryList.tsx
rename to archive-doc-gen/src/frontend/src/components/ChatHistory/ChatHistoryList.tsx
diff --git a/src/frontend/src/components/ChatHistory/ChatHistoryListItem.tsx b/archive-doc-gen/src/frontend/src/components/ChatHistory/ChatHistoryListItem.tsx
similarity index 100%
rename from src/frontend/src/components/ChatHistory/ChatHistoryListItem.tsx
rename to archive-doc-gen/src/frontend/src/components/ChatHistory/ChatHistoryListItem.tsx
diff --git a/src/frontend/src/components/ChatHistory/ChatHistoryPanel.module.css b/archive-doc-gen/src/frontend/src/components/ChatHistory/ChatHistoryPanel.module.css
similarity index 100%
rename from src/frontend/src/components/ChatHistory/ChatHistoryPanel.module.css
rename to archive-doc-gen/src/frontend/src/components/ChatHistory/ChatHistoryPanel.module.css
diff --git a/src/frontend/src/components/ChatHistory/ChatHistoryPanel.test.tsx b/archive-doc-gen/src/frontend/src/components/ChatHistory/ChatHistoryPanel.test.tsx
similarity index 100%
rename from src/frontend/src/components/ChatHistory/ChatHistoryPanel.test.tsx
rename to archive-doc-gen/src/frontend/src/components/ChatHistory/ChatHistoryPanel.test.tsx
diff --git a/src/frontend/src/components/ChatHistory/ChatHistoryPanel.tsx b/archive-doc-gen/src/frontend/src/components/ChatHistory/ChatHistoryPanel.tsx
similarity index 100%
rename from src/frontend/src/components/ChatHistory/ChatHistoryPanel.tsx
rename to archive-doc-gen/src/frontend/src/components/ChatHistory/ChatHistoryPanel.tsx
diff --git a/src/frontend/src/components/ChatHistory/chatHistoryListItem.test.tsx b/archive-doc-gen/src/frontend/src/components/ChatHistory/chatHistoryListItem.test.tsx
similarity index 100%
rename from src/frontend/src/components/ChatHistory/chatHistoryListItem.test.tsx
rename to archive-doc-gen/src/frontend/src/components/ChatHistory/chatHistoryListItem.test.tsx
diff --git a/src/frontend/src/components/DraftCards/SectionCard.test.tsx b/archive-doc-gen/src/frontend/src/components/DraftCards/SectionCard.test.tsx
similarity index 100%
rename from src/frontend/src/components/DraftCards/SectionCard.test.tsx
rename to archive-doc-gen/src/frontend/src/components/DraftCards/SectionCard.test.tsx
diff --git a/src/frontend/src/components/DraftCards/SectionCard.tsx b/archive-doc-gen/src/frontend/src/components/DraftCards/SectionCard.tsx
similarity index 100%
rename from src/frontend/src/components/DraftCards/SectionCard.tsx
rename to archive-doc-gen/src/frontend/src/components/DraftCards/SectionCard.tsx
diff --git a/src/frontend/src/components/DraftCards/TitleCard.test.tsx b/archive-doc-gen/src/frontend/src/components/DraftCards/TitleCard.test.tsx
similarity index 100%
rename from src/frontend/src/components/DraftCards/TitleCard.test.tsx
rename to archive-doc-gen/src/frontend/src/components/DraftCards/TitleCard.test.tsx
diff --git a/src/frontend/src/components/DraftCards/TitleCard.tsx b/archive-doc-gen/src/frontend/src/components/DraftCards/TitleCard.tsx
similarity index 100%
rename from src/frontend/src/components/DraftCards/TitleCard.tsx
rename to archive-doc-gen/src/frontend/src/components/DraftCards/TitleCard.tsx
diff --git a/src/frontend/src/components/FeatureCard/FeatureCard.test.tsx b/archive-doc-gen/src/frontend/src/components/FeatureCard/FeatureCard.test.tsx
similarity index 100%
rename from src/frontend/src/components/FeatureCard/FeatureCard.test.tsx
rename to archive-doc-gen/src/frontend/src/components/FeatureCard/FeatureCard.test.tsx
diff --git a/src/frontend/src/components/FeatureCard/FeatureCard.tsx b/archive-doc-gen/src/frontend/src/components/FeatureCard/FeatureCard.tsx
similarity index 100%
rename from src/frontend/src/components/FeatureCard/FeatureCard.tsx
rename to archive-doc-gen/src/frontend/src/components/FeatureCard/FeatureCard.tsx
diff --git a/src/frontend/src/components/QuestionInput/QuestionInput.module.css b/archive-doc-gen/src/frontend/src/components/QuestionInput/QuestionInput.module.css
similarity index 100%
rename from src/frontend/src/components/QuestionInput/QuestionInput.module.css
rename to archive-doc-gen/src/frontend/src/components/QuestionInput/QuestionInput.module.css
diff --git a/src/frontend/src/components/QuestionInput/QuestionInput.test.tsx b/archive-doc-gen/src/frontend/src/components/QuestionInput/QuestionInput.test.tsx
similarity index 100%
rename from src/frontend/src/components/QuestionInput/QuestionInput.test.tsx
rename to archive-doc-gen/src/frontend/src/components/QuestionInput/QuestionInput.test.tsx
diff --git a/src/frontend/src/components/QuestionInput/QuestionInput.tsx b/archive-doc-gen/src/frontend/src/components/QuestionInput/QuestionInput.tsx
similarity index 100%
rename from src/frontend/src/components/QuestionInput/QuestionInput.tsx
rename to archive-doc-gen/src/frontend/src/components/QuestionInput/QuestionInput.tsx
diff --git a/src/frontend/src/components/QuestionInput/index.ts b/archive-doc-gen/src/frontend/src/components/QuestionInput/index.ts
similarity index 100%
rename from src/frontend/src/components/QuestionInput/index.ts
rename to archive-doc-gen/src/frontend/src/components/QuestionInput/index.ts
diff --git a/src/frontend/src/components/Sidebar/Sidebar.module.css b/archive-doc-gen/src/frontend/src/components/Sidebar/Sidebar.module.css
similarity index 100%
rename from src/frontend/src/components/Sidebar/Sidebar.module.css
rename to archive-doc-gen/src/frontend/src/components/Sidebar/Sidebar.module.css
diff --git a/src/frontend/src/components/Sidebar/Sidebar.test.tsx b/archive-doc-gen/src/frontend/src/components/Sidebar/Sidebar.test.tsx
similarity index 100%
rename from src/frontend/src/components/Sidebar/Sidebar.test.tsx
rename to archive-doc-gen/src/frontend/src/components/Sidebar/Sidebar.test.tsx
diff --git a/src/frontend/src/components/Sidebar/Sidebar.tsx b/archive-doc-gen/src/frontend/src/components/Sidebar/Sidebar.tsx
similarity index 100%
rename from src/frontend/src/components/Sidebar/Sidebar.tsx
rename to archive-doc-gen/src/frontend/src/components/Sidebar/Sidebar.tsx
diff --git a/src/frontend/src/components/common/Button.module.css b/archive-doc-gen/src/frontend/src/components/common/Button.module.css
similarity index 100%
rename from src/frontend/src/components/common/Button.module.css
rename to archive-doc-gen/src/frontend/src/components/common/Button.module.css
diff --git a/src/frontend/src/components/common/Button.test.tsx b/archive-doc-gen/src/frontend/src/components/common/Button.test.tsx
similarity index 100%
rename from src/frontend/src/components/common/Button.test.tsx
rename to archive-doc-gen/src/frontend/src/components/common/Button.test.tsx
diff --git a/src/frontend/src/components/common/Button.tsx b/archive-doc-gen/src/frontend/src/components/common/Button.tsx
similarity index 100%
rename from src/frontend/src/components/common/Button.tsx
rename to archive-doc-gen/src/frontend/src/components/common/Button.tsx
diff --git a/src/frontend/src/constants/chatHistory.test.tsx b/archive-doc-gen/src/frontend/src/constants/chatHistory.test.tsx
similarity index 100%
rename from src/frontend/src/constants/chatHistory.test.tsx
rename to archive-doc-gen/src/frontend/src/constants/chatHistory.test.tsx
diff --git a/src/frontend/src/constants/chatHistory.tsx b/archive-doc-gen/src/frontend/src/constants/chatHistory.tsx
similarity index 100%
rename from src/frontend/src/constants/chatHistory.tsx
rename to archive-doc-gen/src/frontend/src/constants/chatHistory.tsx
diff --git a/src/frontend/src/constants/xssAllowTags.ts b/archive-doc-gen/src/frontend/src/constants/xssAllowTags.ts
similarity index 100%
rename from src/frontend/src/constants/xssAllowTags.ts
rename to archive-doc-gen/src/frontend/src/constants/xssAllowTags.ts
diff --git a/src/frontend/src/helpers/helpers.ts b/archive-doc-gen/src/frontend/src/helpers/helpers.ts
similarity index 100%
rename from src/frontend/src/helpers/helpers.ts
rename to archive-doc-gen/src/frontend/src/helpers/helpers.ts
diff --git a/src/frontend/src/index.css b/archive-doc-gen/src/frontend/src/index.css
similarity index 100%
rename from src/frontend/src/index.css
rename to archive-doc-gen/src/frontend/src/index.css
diff --git a/src/frontend/src/index.tsx b/archive-doc-gen/src/frontend/src/index.tsx
similarity index 100%
rename from src/frontend/src/index.tsx
rename to archive-doc-gen/src/frontend/src/index.tsx
diff --git a/src/frontend/src/pages/NoPage.tsx b/archive-doc-gen/src/frontend/src/pages/NoPage.tsx
similarity index 100%
rename from src/frontend/src/pages/NoPage.tsx
rename to archive-doc-gen/src/frontend/src/pages/NoPage.tsx
diff --git a/src/frontend/src/pages/chat/Chat.module.css b/archive-doc-gen/src/frontend/src/pages/chat/Chat.module.css
similarity index 100%
rename from src/frontend/src/pages/chat/Chat.module.css
rename to archive-doc-gen/src/frontend/src/pages/chat/Chat.module.css
diff --git a/src/frontend/src/pages/chat/Chat.test.tsx b/archive-doc-gen/src/frontend/src/pages/chat/Chat.test.tsx
similarity index 100%
rename from src/frontend/src/pages/chat/Chat.test.tsx
rename to archive-doc-gen/src/frontend/src/pages/chat/Chat.test.tsx
diff --git a/src/frontend/src/pages/chat/Chat.tsx b/archive-doc-gen/src/frontend/src/pages/chat/Chat.tsx
similarity index 100%
rename from src/frontend/src/pages/chat/Chat.tsx
rename to archive-doc-gen/src/frontend/src/pages/chat/Chat.tsx
diff --git a/src/frontend/src/pages/chat/Components/AuthNotConfigure.test.tsx b/archive-doc-gen/src/frontend/src/pages/chat/Components/AuthNotConfigure.test.tsx
similarity index 100%
rename from src/frontend/src/pages/chat/Components/AuthNotConfigure.test.tsx
rename to archive-doc-gen/src/frontend/src/pages/chat/Components/AuthNotConfigure.test.tsx
diff --git a/src/frontend/src/pages/chat/Components/AuthNotConfigure.tsx b/archive-doc-gen/src/frontend/src/pages/chat/Components/AuthNotConfigure.tsx
similarity index 100%
rename from src/frontend/src/pages/chat/Components/AuthNotConfigure.tsx
rename to archive-doc-gen/src/frontend/src/pages/chat/Components/AuthNotConfigure.tsx
diff --git a/src/frontend/src/pages/chat/Components/ChatMessageContainer.test.tsx b/archive-doc-gen/src/frontend/src/pages/chat/Components/ChatMessageContainer.test.tsx
similarity index 100%
rename from src/frontend/src/pages/chat/Components/ChatMessageContainer.test.tsx
rename to archive-doc-gen/src/frontend/src/pages/chat/Components/ChatMessageContainer.test.tsx
diff --git a/src/frontend/src/pages/chat/Components/ChatMessageContainer.tsx b/archive-doc-gen/src/frontend/src/pages/chat/Components/ChatMessageContainer.tsx
similarity index 100%
rename from src/frontend/src/pages/chat/Components/ChatMessageContainer.tsx
rename to archive-doc-gen/src/frontend/src/pages/chat/Components/ChatMessageContainer.tsx
diff --git a/src/frontend/src/pages/chat/Components/CitationPanel.test.tsx b/archive-doc-gen/src/frontend/src/pages/chat/Components/CitationPanel.test.tsx
similarity index 100%
rename from src/frontend/src/pages/chat/Components/CitationPanel.test.tsx
rename to archive-doc-gen/src/frontend/src/pages/chat/Components/CitationPanel.test.tsx
diff --git a/src/frontend/src/pages/chat/Components/CitationPanel.tsx b/archive-doc-gen/src/frontend/src/pages/chat/Components/CitationPanel.tsx
similarity index 100%
rename from src/frontend/src/pages/chat/Components/CitationPanel.tsx
rename to archive-doc-gen/src/frontend/src/pages/chat/Components/CitationPanel.tsx
diff --git a/src/frontend/src/pages/document/Document.module.css b/archive-doc-gen/src/frontend/src/pages/document/Document.module.css
similarity index 100%
rename from src/frontend/src/pages/document/Document.module.css
rename to archive-doc-gen/src/frontend/src/pages/document/Document.module.css
diff --git a/src/frontend/src/pages/document/Document.test.tsx b/archive-doc-gen/src/frontend/src/pages/document/Document.test.tsx
similarity index 100%
rename from src/frontend/src/pages/document/Document.test.tsx
rename to archive-doc-gen/src/frontend/src/pages/document/Document.test.tsx
diff --git a/src/frontend/src/pages/document/Document.tsx b/archive-doc-gen/src/frontend/src/pages/document/Document.tsx
similarity index 100%
rename from src/frontend/src/pages/document/Document.tsx
rename to archive-doc-gen/src/frontend/src/pages/document/Document.tsx
diff --git a/src/frontend/src/pages/draft/Draft.module.css b/archive-doc-gen/src/frontend/src/pages/draft/Draft.module.css
similarity index 100%
rename from src/frontend/src/pages/draft/Draft.module.css
rename to archive-doc-gen/src/frontend/src/pages/draft/Draft.module.css
diff --git a/src/frontend/src/pages/draft/Draft.test.tsx b/archive-doc-gen/src/frontend/src/pages/draft/Draft.test.tsx
similarity index 100%
rename from src/frontend/src/pages/draft/Draft.test.tsx
rename to archive-doc-gen/src/frontend/src/pages/draft/Draft.test.tsx
diff --git a/src/frontend/src/pages/draft/Draft.tsx b/archive-doc-gen/src/frontend/src/pages/draft/Draft.tsx
similarity index 100%
rename from src/frontend/src/pages/draft/Draft.tsx
rename to archive-doc-gen/src/frontend/src/pages/draft/Draft.tsx
diff --git a/src/frontend/src/pages/landing/Landing.module.css b/archive-doc-gen/src/frontend/src/pages/landing/Landing.module.css
similarity index 100%
rename from src/frontend/src/pages/landing/Landing.module.css
rename to archive-doc-gen/src/frontend/src/pages/landing/Landing.module.css
diff --git a/src/frontend/src/pages/landing/Landing.test.tsx b/archive-doc-gen/src/frontend/src/pages/landing/Landing.test.tsx
similarity index 100%
rename from src/frontend/src/pages/landing/Landing.test.tsx
rename to archive-doc-gen/src/frontend/src/pages/landing/Landing.test.tsx
diff --git a/src/frontend/src/pages/landing/Landing.tsx b/archive-doc-gen/src/frontend/src/pages/landing/Landing.tsx
similarity index 100%
rename from src/frontend/src/pages/landing/Landing.tsx
rename to archive-doc-gen/src/frontend/src/pages/landing/Landing.tsx
diff --git a/src/frontend/src/pages/layout/Layout.module.css b/archive-doc-gen/src/frontend/src/pages/layout/Layout.module.css
similarity index 100%
rename from src/frontend/src/pages/layout/Layout.module.css
rename to archive-doc-gen/src/frontend/src/pages/layout/Layout.module.css
diff --git a/src/frontend/src/pages/layout/Layout.test.tsx b/archive-doc-gen/src/frontend/src/pages/layout/Layout.test.tsx
similarity index 100%
rename from src/frontend/src/pages/layout/Layout.test.tsx
rename to archive-doc-gen/src/frontend/src/pages/layout/Layout.test.tsx
diff --git a/src/frontend/src/pages/layout/Layout.tsx b/archive-doc-gen/src/frontend/src/pages/layout/Layout.tsx
similarity index 100%
rename from src/frontend/src/pages/layout/Layout.tsx
rename to archive-doc-gen/src/frontend/src/pages/layout/Layout.tsx
diff --git a/src/frontend/src/state/AppProvider.tsx b/archive-doc-gen/src/frontend/src/state/AppProvider.tsx
similarity index 100%
rename from src/frontend/src/state/AppProvider.tsx
rename to archive-doc-gen/src/frontend/src/state/AppProvider.tsx
diff --git a/src/frontend/src/state/AppReducer.tsx b/archive-doc-gen/src/frontend/src/state/AppReducer.tsx
similarity index 100%
rename from src/frontend/src/state/AppReducer.tsx
rename to archive-doc-gen/src/frontend/src/state/AppReducer.tsx
diff --git a/src/frontend/src/test/setupTests.ts b/archive-doc-gen/src/frontend/src/test/setupTests.ts
similarity index 100%
rename from src/frontend/src/test/setupTests.ts
rename to archive-doc-gen/src/frontend/src/test/setupTests.ts
diff --git a/src/frontend/src/test/test.utils.tsx b/archive-doc-gen/src/frontend/src/test/test.utils.tsx
similarity index 100%
rename from src/frontend/src/test/test.utils.tsx
rename to archive-doc-gen/src/frontend/src/test/test.utils.tsx
diff --git a/src/frontend/src/vite-env.d.ts b/archive-doc-gen/src/frontend/src/vite-env.d.ts
similarity index 100%
rename from src/frontend/src/vite-env.d.ts
rename to archive-doc-gen/src/frontend/src/vite-env.d.ts
diff --git a/src/frontend/tsconfig.json b/archive-doc-gen/src/frontend/tsconfig.json
similarity index 100%
rename from src/frontend/tsconfig.json
rename to archive-doc-gen/src/frontend/tsconfig.json
diff --git a/src/frontend/tsconfig.node.json b/archive-doc-gen/src/frontend/tsconfig.node.json
similarity index 100%
rename from src/frontend/tsconfig.node.json
rename to archive-doc-gen/src/frontend/tsconfig.node.json
diff --git a/src/frontend/vite.config.ts b/archive-doc-gen/src/frontend/vite.config.ts
similarity index 100%
rename from src/frontend/vite.config.ts
rename to archive-doc-gen/src/frontend/vite.config.ts
diff --git a/src/gunicorn.conf.py b/archive-doc-gen/src/gunicorn.conf.py
similarity index 100%
rename from src/gunicorn.conf.py
rename to archive-doc-gen/src/gunicorn.conf.py
diff --git a/src/requirements-dev.txt b/archive-doc-gen/src/requirements-dev.txt
similarity index 100%
rename from src/requirements-dev.txt
rename to archive-doc-gen/src/requirements-dev.txt
diff --git a/src/requirements.txt b/archive-doc-gen/src/requirements.txt
similarity index 100%
rename from src/requirements.txt
rename to archive-doc-gen/src/requirements.txt
diff --git a/src/start.cmd b/archive-doc-gen/src/start.cmd
similarity index 100%
rename from src/start.cmd
rename to archive-doc-gen/src/start.cmd
diff --git a/src/start.sh b/archive-doc-gen/src/start.sh
similarity index 100%
rename from src/start.sh
rename to archive-doc-gen/src/start.sh
diff --git a/src/test.cmd b/archive-doc-gen/src/test.cmd
similarity index 100%
rename from src/test.cmd
rename to archive-doc-gen/src/test.cmd
diff --git a/src/tests/conftest.py b/archive-doc-gen/src/tests/conftest.py
similarity index 100%
rename from src/tests/conftest.py
rename to archive-doc-gen/src/tests/conftest.py
diff --git a/src/tests/integration_tests/conftest.py b/archive-doc-gen/src/tests/integration_tests/conftest.py
similarity index 100%
rename from src/tests/integration_tests/conftest.py
rename to archive-doc-gen/src/tests/integration_tests/conftest.py
diff --git a/src/tests/integration_tests/dotenv_templates/dotenv.jinja2 b/archive-doc-gen/src/tests/integration_tests/dotenv_templates/dotenv.jinja2
similarity index 100%
rename from src/tests/integration_tests/dotenv_templates/dotenv.jinja2
rename to archive-doc-gen/src/tests/integration_tests/dotenv_templates/dotenv.jinja2
diff --git a/src/tests/integration_tests/test_datasources.py b/archive-doc-gen/src/tests/integration_tests/test_datasources.py
similarity index 100%
rename from src/tests/integration_tests/test_datasources.py
rename to archive-doc-gen/src/tests/integration_tests/test_datasources.py
diff --git a/src/tests/integration_tests/test_startup_scripts.py b/archive-doc-gen/src/tests/integration_tests/test_startup_scripts.py
similarity index 100%
rename from src/tests/integration_tests/test_startup_scripts.py
rename to archive-doc-gen/src/tests/integration_tests/test_startup_scripts.py
diff --git a/src/tests/unit_tests/dotenv_data/dotenv_no_datasource_1 b/archive-doc-gen/src/tests/unit_tests/dotenv_data/dotenv_no_datasource_1
similarity index 100%
rename from src/tests/unit_tests/dotenv_data/dotenv_no_datasource_1
rename to archive-doc-gen/src/tests/unit_tests/dotenv_data/dotenv_no_datasource_1
diff --git a/src/tests/unit_tests/dotenv_data/dotenv_no_datasource_2 b/archive-doc-gen/src/tests/unit_tests/dotenv_data/dotenv_no_datasource_2
similarity index 100%
rename from src/tests/unit_tests/dotenv_data/dotenv_no_datasource_2
rename to archive-doc-gen/src/tests/unit_tests/dotenv_data/dotenv_no_datasource_2
diff --git a/src/tests/unit_tests/dotenv_data/dotenv_with_azure_search_success b/archive-doc-gen/src/tests/unit_tests/dotenv_data/dotenv_with_azure_search_success
similarity index 100%
rename from src/tests/unit_tests/dotenv_data/dotenv_with_azure_search_success
rename to archive-doc-gen/src/tests/unit_tests/dotenv_data/dotenv_with_azure_search_success
diff --git a/src/tests/unit_tests/dotenv_data/dotenv_with_elasticsearch_success b/archive-doc-gen/src/tests/unit_tests/dotenv_data/dotenv_with_elasticsearch_success
similarity index 100%
rename from src/tests/unit_tests/dotenv_data/dotenv_with_elasticsearch_success
rename to archive-doc-gen/src/tests/unit_tests/dotenv_data/dotenv_with_elasticsearch_success
diff --git a/src/tests/unit_tests/helpers/test_azure_credential_utils.py b/archive-doc-gen/src/tests/unit_tests/helpers/test_azure_credential_utils.py
similarity index 100%
rename from src/tests/unit_tests/helpers/test_azure_credential_utils.py
rename to archive-doc-gen/src/tests/unit_tests/helpers/test_azure_credential_utils.py
diff --git a/src/tests/unit_tests/test_settings.py b/archive-doc-gen/src/tests/unit_tests/test_settings.py
similarity index 100%
rename from src/tests/unit_tests/test_settings.py
rename to archive-doc-gen/src/tests/unit_tests/test_settings.py
diff --git a/src/tests/unit_tests/test_utils.py b/archive-doc-gen/src/tests/unit_tests/test_utils.py
similarity index 100%
rename from src/tests/unit_tests/test_utils.py
rename to archive-doc-gen/src/tests/unit_tests/test_utils.py
diff --git a/tests/e2e-test/.gitignore b/archive-doc-gen/tests/e2e-test/.gitignore
similarity index 100%
rename from tests/e2e-test/.gitignore
rename to archive-doc-gen/tests/e2e-test/.gitignore
diff --git a/tests/e2e-test/README.md b/archive-doc-gen/tests/e2e-test/README.md
similarity index 100%
rename from tests/e2e-test/README.md
rename to archive-doc-gen/tests/e2e-test/README.md
diff --git a/tests/e2e-test/base/__init__.py b/archive-doc-gen/tests/e2e-test/base/__init__.py
similarity index 100%
rename from tests/e2e-test/base/__init__.py
rename to archive-doc-gen/tests/e2e-test/base/__init__.py
diff --git a/tests/e2e-test/base/base.py b/archive-doc-gen/tests/e2e-test/base/base.py
similarity index 100%
rename from tests/e2e-test/base/base.py
rename to archive-doc-gen/tests/e2e-test/base/base.py
diff --git a/tests/e2e-test/config/constants.py b/archive-doc-gen/tests/e2e-test/config/constants.py
similarity index 100%
rename from tests/e2e-test/config/constants.py
rename to archive-doc-gen/tests/e2e-test/config/constants.py
diff --git a/tests/e2e-test/img.png b/archive-doc-gen/tests/e2e-test/img.png
similarity index 100%
rename from tests/e2e-test/img.png
rename to archive-doc-gen/tests/e2e-test/img.png
diff --git a/tests/e2e-test/img_1.png b/archive-doc-gen/tests/e2e-test/img_1.png
similarity index 100%
rename from tests/e2e-test/img_1.png
rename to archive-doc-gen/tests/e2e-test/img_1.png
diff --git a/tests/e2e-test/pages/__init__.py b/archive-doc-gen/tests/e2e-test/pages/__init__.py
similarity index 100%
rename from tests/e2e-test/pages/__init__.py
rename to archive-doc-gen/tests/e2e-test/pages/__init__.py
diff --git a/tests/e2e-test/pages/browsePage.py b/archive-doc-gen/tests/e2e-test/pages/browsePage.py
similarity index 100%
rename from tests/e2e-test/pages/browsePage.py
rename to archive-doc-gen/tests/e2e-test/pages/browsePage.py
diff --git a/tests/e2e-test/pages/draftPage.py b/archive-doc-gen/tests/e2e-test/pages/draftPage.py
similarity index 100%
rename from tests/e2e-test/pages/draftPage.py
rename to archive-doc-gen/tests/e2e-test/pages/draftPage.py
diff --git a/tests/e2e-test/pages/generatePage.py b/archive-doc-gen/tests/e2e-test/pages/generatePage.py
similarity index 100%
rename from tests/e2e-test/pages/generatePage.py
rename to archive-doc-gen/tests/e2e-test/pages/generatePage.py
diff --git a/tests/e2e-test/pages/homePage.py b/archive-doc-gen/tests/e2e-test/pages/homePage.py
similarity index 100%
rename from tests/e2e-test/pages/homePage.py
rename to archive-doc-gen/tests/e2e-test/pages/homePage.py
diff --git a/tests/e2e-test/pytest.ini b/archive-doc-gen/tests/e2e-test/pytest.ini
similarity index 100%
rename from tests/e2e-test/pytest.ini
rename to archive-doc-gen/tests/e2e-test/pytest.ini
diff --git a/tests/e2e-test/requirements.txt b/archive-doc-gen/tests/e2e-test/requirements.txt
similarity index 100%
rename from tests/e2e-test/requirements.txt
rename to archive-doc-gen/tests/e2e-test/requirements.txt
diff --git a/tests/e2e-test/sample_dotenv_file.txt b/archive-doc-gen/tests/e2e-test/sample_dotenv_file.txt
similarity index 100%
rename from tests/e2e-test/sample_dotenv_file.txt
rename to archive-doc-gen/tests/e2e-test/sample_dotenv_file.txt
diff --git a/tests/e2e-test/tests/__init__.py b/archive-doc-gen/tests/e2e-test/tests/__init__.py
similarity index 100%
rename from tests/e2e-test/tests/__init__.py
rename to archive-doc-gen/tests/e2e-test/tests/__init__.py
diff --git a/tests/e2e-test/tests/conftest.py b/archive-doc-gen/tests/e2e-test/tests/conftest.py
similarity index 100%
rename from tests/e2e-test/tests/conftest.py
rename to archive-doc-gen/tests/e2e-test/tests/conftest.py
diff --git a/tests/e2e-test/tests/test_st_docgen_tc.py b/archive-doc-gen/tests/e2e-test/tests/test_st_docgen_tc.py
similarity index 100%
rename from tests/e2e-test/tests/test_st_docgen_tc.py
rename to archive-doc-gen/tests/e2e-test/tests/test_st_docgen_tc.py
diff --git a/content-gen/.env.sample b/content-gen/.env.sample
new file mode 100644
index 000000000..fddde5c59
--- /dev/null
+++ b/content-gen/.env.sample
@@ -0,0 +1,116 @@
+# Content Generation Solution Accelerator - Development Environment
+# Copy this file to .env and fill in your values
+
+# =============================================================================
+# Azure Authentication
+# =============================================================================
+AZURE_CLIENT_ID=
+
+# =============================================================================
+# Azure AI Foundry Configuration (Optional - for agent-based workflows)
+# =============================================================================
+# Set USE_FOUNDRY=true to use Azure AI Foundry instead of direct Azure OpenAI
+USE_FOUNDRY=false
+
+# Azure AI Foundry project endpoint (required if USE_FOUNDRY=true)
+# Format: https://.services.ai.azure.com
+AZURE_AI_PROJECT_ENDPOINT=
+
+# Image model deployment name in Foundry (e.g., gpt-image-1)
+AZURE_AI_IMAGE_DEPLOYMENT=gpt-image-1
+
+# =============================================================================
+# Azure OpenAI Configuration
+# =============================================================================
+AI_FOUNDRY_RESOURCE_ID=/subscriptions/your-subscription-id/resourceGroups/your-resource-group/providers/Microsoft.CognitiveServices/accounts/your-aif-account
+AZURE_EXISTING_AI_PROJECT_RESOURCE_ID=/subscriptions/your-subscription-id/resourceGroups/your-resource-group/providers/Microsoft.CognitiveServices/accounts/your-aif-account/projects/your-project-name
+# Your Azure OpenAI endpoint (e.g., https://your-resource.openai.azure.com/)
+AZURE_OPENAI_ENDPOINT=https://your-openai.openai.azure.com/
+
+# Model deployments
+AZURE_OPENAI_GPT_MODEL=gpt-5.1
+
+# Image Generation Model Configuration
+# Supported models: dall-e-3 or gpt-image-1
+AZURE_OPENAI_IMAGE_MODEL=gpt-image-1
+
+# For gpt-image-1 (if using a different endpoint than DALL-E)
+AZURE_OPENAI_GPT_IMAGE_ENDPOINT=https://your-openai.openai.azure.com
+
+# Image generation settings
+# For dall-e-3: sizes are 1024x1024, 1024x1792, 1792x1024; quality is standard or hd
+# For gpt-image-1: sizes are 1024x1024, 1536x1024, 1024x1536, auto; quality is low, medium, high, auto
+AZURE_OPENAI_IMAGE_SIZE=1024x1024
+AZURE_OPENAI_IMAGE_QUALITY=medium
+
+# API versions
+AZURE_OPENAI_API_VERSION=2024-06-01
+AZURE_OPENAI_PREVIEW_API_VERSION=2024-02-01
+
+# Generation parameters
+AZURE_OPENAI_TEMPERATURE=0.7
+AZURE_OPENAI_MAX_TOKENS=2000
+
+# =============================================================================
+# Azure Cosmos DB
+# =============================================================================
+AZURE_COSMOS_ENDPOINT=https://your-cosmos.documents.azure.com:443/
+AZURE_COSMOS_DATABASE_NAME=content-generation
+AZURE_COSMOS_PRODUCTS_CONTAINER=products
+AZURE_COSMOS_CONVERSATIONS_CONTAINER=conversations
+
+# =============================================================================
+# Azure Blob Storage
+# =============================================================================
+AZURE_BLOB_ACCOUNT_NAME=yourstorageaccount
+AZURE_BLOB_PRODUCT_IMAGES_CONTAINER=product-images
+AZURE_BLOB_GENERATED_IMAGES_CONTAINER=generated-images
+
+# =============================================================================
+# Azure AI Search
+# =============================================================================
+AZURE_AI_SEARCH_ENDPOINT=https://your-search.search.windows.net
+AZURE_AI_SEARCH_PRODUCTS_INDEX=products
+AZURE_AI_SEARCH_IMAGE_INDEX=product-images
+
+# =============================================================================
+# UI Configuration
+# =============================================================================
+UI_APP_NAME=Content Generation Accelerator
+UI_TITLE=Content Generation
+UI_CHAT_TITLE=Marketing Content Generator
+UI_CHAT_DESCRIPTION=AI-powered multimodal content generation for marketing campaigns.
+
+# =============================================================================
+# Brand Guidelines
+# =============================================================================
+# Voice and Tone
+BRAND_TONE=Professional yet approachable
+BRAND_VOICE=Innovative, trustworthy, customer-focused
+
+# Visual Guidelines
+BRAND_PRIMARY_COLOR=#0078D4
+BRAND_SECONDARY_COLOR=#107C10
+BRAND_IMAGE_STYLE=Modern, clean, minimalist with bright lighting
+
+# Content Rules
+BRAND_MAX_HEADLINE_LENGTH=60
+BRAND_MAX_BODY_LENGTH=500
+BRAND_REQUIRE_CTA=true
+
+# Comma-separated list of prohibited words
+BRAND_PROHIBITED_WORDS=cheapest,guaranteed,best in class,#1,market leader
+
+# Comma-separated list of required disclosures (leave empty if none)
+BRAND_REQUIRED_DISCLOSURES=
+
+# =============================================================================
+# Application Settings
+# =============================================================================
+# Server configuration
+PORT=5000
+WORKERS=4
+
+# Feature flags
+AUTH_ENABLED=false
+SANITIZE_ANSWER=false
diff --git a/content-gen/README.md b/content-gen/README.md
new file mode 100644
index 000000000..df7179765
--- /dev/null
+++ b/content-gen/README.md
@@ -0,0 +1,190 @@
+# Intelligent Content Generation Accelerator
+
+A multimodal content generation solution for retail marketing campaigns using Microsoft Agent Framework with HandoffBuilder orchestration. The system interprets creative briefs and generates compliant marketing content (text and images) grounded in enterprise product data, brand guidelines, and product images.
+
+## Overview
+
+This accelerator provides an internal chatbot that can:
+
+- **Interpret Creative Briefs**: Parse free-text creative briefs into structured fields (overview, objectives, target audience, key message, tone/style, deliverable, timelines, visual guidelines, CTA)
+- **Generate Multimodal Content**: Create marketing copy and images using GPT models and DALL-E 3
+- **Ensure Brand Compliance**: Validate all content against brand guidelines with severity-categorized warnings
+- **Ground in Enterprise Data**: Leverage product information, product images, and brand guidelines stored in Azure services
+
+## Architecture
+
+### Backend
+- **Runtime**: Python 3.11 + Quart + Hypercorn
+- **Deployment**: Azure Container Instance (ACI) in private VNet
+- **Authentication**: System-assigned Managed Identity
+
+### Frontend
+- **Framework**: React + Vite + TypeScript + Fluent UI
+- **Deployment**: Azure App Service with Node.js proxy
+- **Features**: Server-Sent Events (SSE) for streaming responses
+
+### Specialized Agents (Microsoft Agent Framework)
+
+The solution uses **HandoffBuilder** orchestration with 6 specialized agents:
+
+| Agent | Role |
+|-------|------|
+| **TriageAgent** | Coordinator that routes user requests to appropriate specialists |
+| **PlanningAgent** | Parses creative briefs, develops content strategy, returns for user confirmation |
+| **ResearchAgent** | Retrieves products from CosmosDB, fetches brand guidelines, assembles grounding data |
+| **TextContentAgent** | Generates marketing copy (headlines, body, CTAs) using GPT |
+| **ImageContentAgent** | Creates marketing images via DALL-E 3 with product context |
+| **ComplianceAgent** | Validates content against brand guidelines, categorizes violations |
+
+### Compliance Severity Levels
+
+| Level | Description | Action |
+|-------|-------------|--------|
+| **Error** | Legal/regulatory violations | Blocks acceptance until modified |
+| **Warning** | Brand guideline deviations | Review recommended |
+| **Info** | Style suggestions | Optional improvements |
+
+### Azure Services
+
+| Service | Purpose |
+|---------|---------|
+| Azure OpenAI (GPT) | Text generation and content creation |
+| Azure OpenAI (DALL-E 3) | Image generation (can be separate resource) |
+| Azure Cosmos DB | Products catalog, chat conversations |
+| Azure Blob Storage | Product images, generated images |
+| Azure Container Instance | Backend API hosting |
+| Azure App Service | Frontend hosting |
+| Azure Container Registry | Container image storage |
+
+## Creative Brief Fields
+
+The system extracts the following fields from free-text creative briefs:
+
+1. **Overview** - Campaign summary
+2. **Objectives** - Goals and KPIs
+3. **Target Audience** - Demographics and psychographics
+4. **Key Message** - Core messaging
+5. **Tone and Style** - Voice and manner
+6. **Deliverable** - Expected outputs
+7. **Timelines** - Due dates and milestones
+8. **Visual Guidelines** - Image requirements
+9. **CTA** - Call to action
+
+## Product Schema
+
+```json
+{
+ "product_name": "string",
+ "category": "string",
+ "sub_category": "string",
+ "marketing_description": "string",
+ "detailed_spec_description": "string",
+ "sku": "string",
+ "model": "string",
+ "image_description": "string (auto-generated via GPT-5 vision)",
+ "image_url": "string"
+}
+```
+
+## Getting Started
+
+### Prerequisites
+
+- Azure subscription with access to:
+ - Azure OpenAI (GPT model - GPT-4 or higher recommended)
+ - Azure OpenAI (DALL-E 3 - can be same or different resource)
+ - Azure Cosmos DB
+ - Azure Blob Storage
+ - Azure Container Instance
+ - Azure App Service
+ - Azure Container Registry
+- Azure CLI >= 2.50.0
+- Docker (optional - ACR can build containers)
+- Python 3.11+
+- Node.js 18+
+
+### Quick Deployment (Recommended)
+
+Using Azure Developer CLI (`azd`):
+
+```bash
+# Clone the repository
+git clone
+cd content-gen
+
+# Deploy everything with one command
+azd up
+```
+
+See [docs/AZD_DEPLOYMENT.md](docs/AZD_DEPLOYMENT.md) for detailed `azd` deployment instructions.
+
+### Manual Deployment
+
+For more control over individual resources:
+
+```bash
+# Clone the repository
+git clone
+cd content-gen
+
+# Run deployment script
+./scripts/deploy.sh
+```
+
+See [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) for detailed manual deployment instructions.
+
+### Local Development
+
+```bash
+# Backend
+cd src
+pip install -r requirements.txt
+python app.py
+
+# Frontend
+cd src/app/frontend
+npm install
+npm run dev
+```
+
+## Configuration
+
+### Environment Variables
+
+See `src/backend/settings.py` for all configuration options. Key settings:
+
+| Variable | Description |
+|----------|-------------|
+| `AZURE_OPENAI_ENDPOINT` | Azure OpenAI endpoint for GPT model |
+| `AZURE_OPENAI_DEPLOYMENT_NAME` | GPT model deployment name |
+| `AZURE_OPENAI_DALLE_ENDPOINT` | Azure OpenAI endpoint for DALL-E (if separate) |
+| `AZURE_OPENAI_DALLE_DEPLOYMENT` | DALL-E deployment name (dall-e-3) |
+| `COSMOS_ENDPOINT` | Azure Cosmos DB endpoint |
+| `COSMOS_DATABASE` | Cosmos DB database name |
+| `AZURE_STORAGE_ACCOUNT_NAME` | Storage account name |
+| `BRAND_*` | Brand guideline parameters |
+
+### Brand Guidelines
+
+Brand guidelines are configured via environment variables with the `BRAND_` prefix:
+
+```env
+BRAND_TONE=Professional yet approachable
+BRAND_VOICE=Innovative, trustworthy, customer-focused
+BRAND_PROHIBITED_WORDS=guarantee,best,only,exclusive
+BRAND_REQUIRED_DISCLOSURES=Terms apply,See details
+BRAND_PRIMARY_COLOR=#0078D4
+BRAND_SECONDARY_COLOR=#107C10
+```
+
+## Documentation
+
+- [Local Development Guide](docs/LOCAL_DEPLOYMENT.md) - Run locally for development
+- [AZD Deployment Guide](docs/AZD_DEPLOYMENT.md) - Deploy with Azure Developer CLI
+- [Manual Deployment Guide](docs/DEPLOYMENT.md) - Step-by-step manual deployment
+- [Image Generation Configuration](docs/IMAGE_GENERATION.md) - DALL-E 3 and GPT-Image-1 setup
+- [API Reference](docs/API.md)
+
+## License
+
+MIT License - See [LICENSE](LICENSE) for details.
diff --git a/content-gen/azure.yaml b/content-gen/azure.yaml
new file mode 100644
index 000000000..424fb2923
--- /dev/null
+++ b/content-gen/azure.yaml
@@ -0,0 +1,152 @@
+environment:
+ name: content-generation
+ location: eastus
+
+name: content-generation
+metadata:
+ template: content-generation@1.22
+
+requiredVersions:
+ azd: '>= 1.18.0'
+
+parameters:
+ solutionPrefix:
+ type: string
+ default: contentgen
+ displayName: Solution Prefix
+ description: A unique prefix for all resources (3-15 chars)
+ azureAiServiceLocation:
+ type: string
+ default: eastus
+ displayName: AI Services Location
+ description: Location for Azure AI Services deployments
+ enableMonitoring:
+ type: boolean
+ default: false
+ displayName: Enable Monitoring (WAF)
+ description: Enable Log Analytics and Application Insights
+ enableScalability:
+ type: boolean
+ default: false
+ displayName: Enable Scalability (WAF)
+ description: Enable auto-scaling and higher SKUs
+ enableRedundancy:
+ type: boolean
+ default: false
+ displayName: Enable Redundancy (WAF)
+ description: Enable zone redundancy and geo-replication
+ enablePrivateNetworking:
+ type: boolean
+ default: false
+ displayName: Enable Private Networking (WAF)
+ description: Enable VNet integration and private endpoints
+
+infra:
+ provider: bicep
+ path: ./infra
+ module: main
+
+workflows:
+ up:
+ steps:
+ - azd: provision
+
+hooks:
+ postprovision:
+ windows:
+ run: |
+ Write-Host "===== Provision Complete =====" -ForegroundColor Green
+ Write-Host ""
+ Write-Host "Web App URL: " -NoNewline
+ Write-Host "$env:WEB_APP_URL" -ForegroundColor Cyan
+ Write-Host "Storage Account: " -NoNewline
+ Write-Host "$env:AZURE_BLOB_ACCOUNT_NAME" -ForegroundColor Cyan
+ Write-Host "AI Search Service: " -NoNewline
+ Write-Host "$env:AI_SEARCH_SERVICE_NAME" -ForegroundColor Cyan
+ Write-Host "AI Search Index: " -NoNewline
+ Write-Host "$env:AZURE_AI_SEARCH_PRODUCTS_INDEX" -ForegroundColor Cyan
+ Write-Host "AI Service Location: " -NoNewline
+ Write-Host "$env:AI_SERVICE_LOCATION" -ForegroundColor Cyan
+ Write-Host "Container Instance: " -NoNewline
+ Write-Host "$env:CONTAINER_INSTANCE_NAME" -ForegroundColor Cyan
+
+ # Run post-deploy script to upload sample data and create search index
+ Write-Host ""
+ # Note: Cosmos DB role is assigned to deployer via Bicep.
+ Write-Host "===== Running Post-Deploy Script =====" -ForegroundColor Yellow
+ Write-Host "This will upload sample data and create the search index..."
+
+ # Ensure post-deploy Python dependencies are installed
+ $python = "python"
+ if (Test-Path "./.venv/Scripts/python.exe") { $python = "./.venv/Scripts/python.exe" }
+ elseif (Test-Path "../.venv/Scripts/python.exe") { $python = "../.venv/Scripts/python.exe" }
+ elseif (Test-Path "./.venv/bin/python") { $python = "./.venv/bin/python" }
+ elseif (Test-Path "../.venv/bin/python") { $python = "../.venv/bin/python" }
+ & $python -m pip install -r ./scripts/requirements-post-deploy.txt --quiet | Out-Null
+
+ if (Test-Path "./scripts/post_deploy.py") {
+ & $python ./scripts/post_deploy.py --skip-tests
+
+ if ($LASTEXITCODE -eq 0) {
+ Write-Host "Post-deploy script completed successfully!" -ForegroundColor Green
+ } else {
+ Write-Host "Post-deploy script completed with warnings (some steps may have failed)" -ForegroundColor Yellow
+ }
+ } else {
+ Write-Host "Warning: post_deploy.py not found, skipping sample data upload" -ForegroundColor Yellow
+ }
+
+ Write-Host ""
+ Write-Host "===== Deployment Complete =====" -ForegroundColor Green
+ Write-Host ""
+ Write-Host "Access the web application:"
+ Write-Host " $env:WEB_APP_URL" -ForegroundColor Cyan
+ shell: pwsh
+ continueOnError: false
+ interactive: true
+ posix:
+ run: |
+ echo "===== Provision Complete ====="
+ echo ""
+ echo "Web App URL: $WEB_APP_URL"
+ echo "Storage Account: $AZURE_BLOB_ACCOUNT_NAME"
+ echo "AI Search Service: $AI_SEARCH_SERVICE_NAME"
+ echo "AI Search Index: $AZURE_AI_SEARCH_PRODUCTS_INDEX"
+ echo "AI Service Location: $AI_SERVICE_LOCATION"
+ echo "Container Instance: $CONTAINER_INSTANCE_NAME"
+
+ echo ""
+ echo "Container Registry: $ACR_NAME"
+
+ # Run post-deploy script to upload sample data and create search index
+ echo ""
+ # Note: Cosmos DB role is assigned to deployer via Bicep.
+ echo "===== Running Post-Deploy Script ====="
+ echo "This will upload sample data and create the search index..."
+
+ if [ -f "./scripts/post_deploy.py" ]; then
+ # Prefer local venv if present (repo root or content-gen)
+ if [ -x "./.venv/bin/python" ]; then
+ PYTHON_BIN="./.venv/bin/python"
+ elif [ -x "../.venv/bin/python" ]; then
+ PYTHON_BIN="../.venv/bin/python"
+ else
+ PYTHON_BIN="python3"
+ fi
+
+ "$PYTHON_BIN" -m pip install -r ./scripts/requirements-post-deploy.txt --quiet > /dev/null \
+ && "$PYTHON_BIN" ./scripts/post_deploy.py --skip-tests \
+ && echo "Post-deploy script completed successfully!" \
+ || echo "Post-deploy script completed with warnings (some steps may have failed)"
+ else
+ echo "Warning: post_deploy.py not found, skipping sample data upload"
+ fi
+
+ echo ""
+ echo "===== Deployment Complete ====="
+ echo ""
+ echo "Access the web application:"
+ echo " $WEB_APP_URL"
+ shell: sh
+ continueOnError: false
+ interactive: true
diff --git a/content-gen/docs/AZD_DEPLOYMENT.md b/content-gen/docs/AZD_DEPLOYMENT.md
new file mode 100644
index 000000000..0348c9de6
--- /dev/null
+++ b/content-gen/docs/AZD_DEPLOYMENT.md
@@ -0,0 +1,401 @@
+# Azure Developer CLI (azd) Deployment Guide
+
+This guide covers deploying the Content Generation Solution Accelerator using Azure Developer CLI (`azd`).
+
+## Prerequisites
+
+### Required Tools
+
+1. **Azure Developer CLI (azd)** v1.18.0 or higher
+ ```bash
+ # Install on Linux/macOS
+ curl -fsSL https://aka.ms/install-azd.sh | bash
+
+ # Install on Windows (PowerShell)
+ powershell -ex AllSigned -c "Invoke-RestMethod 'https://aka.ms/install-azd.ps1' | Invoke-Expression"
+
+ # Verify installation
+ azd version
+ ```
+
+2. **Azure CLI**
+ ```bash
+ # Install: https://docs.microsoft.com/cli/azure/install-azure-cli
+ az version
+ ```
+
+3. **Node.js** v18 or higher (for frontend build)
+ ```bash
+ node --version
+ ```
+
+4. **Python** 3.11+ (for post-deployment scripts)
+ ```bash
+ python3 --version
+ ```
+
+### Azure Requirements
+
+- An Azure subscription with the following permissions:
+ - Create Resource Groups
+ - Deploy Azure AI Services (GPT-4o, DALL-E 3 or GPT-Image-1, Text Embeddings)
+ - Create Container Registry, Container Instances, App Service
+ - Create Cosmos DB, Storage Account, AI Search
+ - Assign RBAC roles
+
+- **Quota**: Ensure you have sufficient quota for:
+ - GPT-4o (or your chosen model)
+ - DALL-E 3 or GPT-Image-1 (for image generation)
+ - Text-embedding-3-large
+
+## Quick Start
+
+### 1. Authenticate
+
+```bash
+# Login to Azure
+azd auth login
+
+# Login to Azure CLI (required for some post-deployment scripts)
+az login
+```
+
+### 2. Initialize Environment
+
+```bash
+cd content-gen
+
+# Create a new environment
+azd env new
+
+# Example:
+azd env new content-gen-dev
+```
+
+### 3. Configure Parameters (Optional)
+
+The deployment has sensible defaults, but you can customize:
+
+```bash
+# Set the Azure region (default: eastus)
+azd env set AZURE_LOCATION swedencentral
+
+# Set AI Services region (must support your models)
+azd env set azureAiServiceLocation swedencentral
+
+# GPT Model configuration
+azd env set gptModelName gpt-4o
+azd env set gptModelVersion 2024-11-20
+azd env set gptModelDeploymentType GlobalStandard
+azd env set gptModelCapacity 50
+
+# Image generation model (dalle-3 or gpt-image-1)
+azd env set imageModelChoice gpt-image-1
+azd env set dalleModelCapacity 1
+
+# Embedding model
+azd env set embeddingModel text-embedding-3-large
+azd env set embeddingDeploymentCapacity 50
+
+# Azure OpenAI API version
+azd env set azureOpenaiAPIVersion 2024-12-01-preview
+```
+
+### 4. Enable Optional Features (WAF Pillars)
+
+```bash
+# Enable private networking (VNet integration)
+azd env set enablePrivateNetworking true
+
+# Enable monitoring (Log Analytics + App Insights)
+azd env set enableMonitoring true
+
+# Enable scalability (auto-scaling, higher SKUs)
+azd env set enableScalability true
+
+# Enable redundancy (zone redundancy, geo-replication)
+azd env set enableRedundancy true
+```
+
+### 5. Deploy
+
+```bash
+azd up
+```
+
+This single command will:
+1. **Provision** all Azure resources (AI Services, Cosmos DB, Storage, AI Search, App Service, Container Registry)
+2. **Build** the Docker container image and push to ACR
+3. **Deploy** the container to Azure Container Instances
+4. **Build** the frontend (React/TypeScript)
+5. **Deploy** the frontend to App Service
+6. **Configure** RBAC and Cosmos DB roles
+7. **Upload** sample data and create the search index
+
+## Deployment Parameters Reference
+
+| Parameter | Default | Description |
+|-----------|---------|-------------|
+| `AZURE_LOCATION` | eastus | Primary Azure region |
+| `azureAiServiceLocation` | eastus | Region for AI Services (must support chosen models) |
+| `gptModelName` | gpt-4o | GPT model for content generation |
+| `gptModelVersion` | 2024-11-20 | Model version |
+| `gptModelDeploymentType` | GlobalStandard | Deployment type |
+| `gptModelCapacity` | 50 | TPM capacity (in thousands) |
+| `imageModelChoice` | dalle-3 | Image model: `dalle-3` or `gpt-image-1` |
+| `dalleModelCapacity` | 1 | Image model capacity |
+| `embeddingModel` | text-embedding-3-large | Embedding model |
+| `embeddingDeploymentCapacity` | 50 | Embedding TPM capacity |
+| `enablePrivateNetworking` | false | Enable VNet and private endpoints |
+| `enableMonitoring` | false | Enable Log Analytics + App Insights |
+| `enableScalability` | false | Enable auto-scaling |
+| `enableRedundancy` | false | Enable zone/geo redundancy |
+
+## Using Existing Resources
+
+### Reuse Existing AI Foundry Project
+
+```bash
+# Set the resource ID of your existing AI Project
+azd env set azureExistingAIProjectResourceId "/subscriptions//resourceGroups//providers/Microsoft.MachineLearningServices/workspaces/"
+```
+
+### Reuse Existing Log Analytics Workspace
+
+```bash
+# Set the resource ID of your existing Log Analytics workspace
+azd env set existingLogAnalyticsWorkspaceId "/subscriptions//resourceGroups//providers/Microsoft.OperationalInsights/workspaces/"
+```
+
+### Use Existing Container Registry
+
+```bash
+# Set the name of your existing ACR
+azd env set acrName myexistingacr
+```
+
+## Post-Deployment
+
+After `azd up` completes, you'll see output like:
+
+```
+===== Deployment Complete =====
+
+Access the web application:
+ https://app-.azurewebsites.net
+```
+
+### Verify Deployment
+
+1. Open the Web App URL in your browser
+2. Sign in with your Azure AD account
+3. Navigate to the Products page to verify sample data was loaded
+4. Create a test marketing content document
+
+### Access Resources
+
+```bash
+# View all environment values
+azd env get-values
+
+# Get the web app URL
+azd env get-value WEB_APP_URL
+
+# Get resource group name
+azd env get-value RESOURCE_GROUP_NAME
+```
+
+## Day-2 Operations
+
+### Update the Application
+
+After making code changes:
+
+```bash
+# Rebuild and redeploy everything
+azd up
+
+# Or just redeploy (no infra changes)
+azd deploy
+```
+
+### Update Only the Backend (Container)
+
+```bash
+# Get ACR and ACI names
+ACR_NAME=$(azd env get-value ACR_NAME)
+ACI_NAME=$(azd env get-value CONTAINER_INSTANCE_NAME)
+RG_NAME=$(azd env get-value RESOURCE_GROUP_NAME)
+
+# Build and push new image
+az acr build --registry $ACR_NAME --image content-gen-app:latest --file ./src/WebApp.Dockerfile ./src
+
+# Restart ACI to pull new image
+az container restart --name $ACI_NAME --resource-group $RG_NAME
+```
+
+### Update Only the Frontend
+
+```bash
+cd src/app/frontend
+npm install && npm run build
+
+cd ../frontend-server
+zip -r frontend-deploy.zip static/ server.js package.json package-lock.json
+
+az webapp deploy \
+ --resource-group $(azd env get-value RESOURCE_GROUP_NAME) \
+ --name $(azd env get-value APP_SERVICE_NAME) \
+ --src-path frontend-deploy.zip \
+ --type zip
+```
+
+### View Logs
+
+```bash
+# Backend container logs
+az container logs \
+ --name $(azd env get-value CONTAINER_INSTANCE_NAME) \
+ --resource-group $(azd env get-value RESOURCE_GROUP_NAME) \
+ --follow
+
+# App Service logs
+az webapp log tail \
+ --name $(azd env get-value APP_SERVICE_NAME) \
+ --resource-group $(azd env get-value RESOURCE_GROUP_NAME)
+```
+
+## Clean Up
+
+### Delete All Resources
+
+```bash
+# Delete all Azure resources and the environment
+azd down --purge
+
+# Or just delete resources (keep environment config)
+azd down
+```
+
+### Delete Specific Environment
+
+```bash
+# List environments
+azd env list
+
+# Delete an environment
+azd env delete
+```
+
+## Troubleshooting
+
+### Common Issues
+
+#### 1. Quota Exceeded
+
+```
+Error: InsufficientQuota
+```
+
+**Solution**: Check your quota in the Azure portal or run:
+```bash
+az cognitiveservices usage list --location
+```
+
+Request a quota increase or choose a different region.
+
+#### 2. Model Not Available in Region
+
+```
+Error: The model 'gpt-4o' is not available in region 'westeurope'
+```
+
+**Solution**: Set a different region for AI Services:
+```bash
+azd env set azureAiServiceLocation eastus
+```
+
+#### 3. Container Build Fails
+
+```
+Error: az acr build failed
+```
+
+**Solution**: Check the Dockerfile and ensure all required files are present:
+```bash
+# Manual build for debugging
+cd src
+docker build -f WebApp.Dockerfile -t content-gen-app:test .
+```
+
+#### 4. Frontend Deployment Fails
+
+```
+Error: az webapp deploy failed
+```
+
+**Solution**: Ensure the frontend builds successfully:
+```bash
+cd src/app/frontend
+npm install
+npm run build
+```
+
+#### 5. RBAC Assignment Fails
+
+```
+Error: Authorization failed
+```
+
+**Solution**: Ensure you have Owner or User Access Administrator role on the subscription.
+
+### Debug Mode
+
+For more verbose output:
+```bash
+azd up --debug
+```
+
+### Reset Environment
+
+If deployment gets into a bad state:
+```bash
+# Re-run provisioning
+azd provision
+```
+
+## Architecture Deployed
+
+When `enablePrivateNetworking` is enabled:
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ Azure Resource Group │
+│ │
+│ ┌──────────────────┐ ┌───────────────────────────────┐ │
+│ │ App Service │ │ Virtual Network │ │
+│ │ (Node.js Proxy) │──────│ ┌─────────────────────────┐ │ │
+│ │ │ │ │ Container Instance │ │ │
+│ └──────────────────┘ │ │ (Python Backend) │ │ │
+│ │ │ └─────────────────────────┘ │ │
+│ │ │ │ │
+│ ┌───────▼──────────┐ │ ┌─────────────────────────┐ │ │
+│ │ Azure AI Search │◄─────│──│ Private Endpoints │ │ │
+│ └──────────────────┘ │ └─────────────────────────┘ │ │
+│ │ └───────────────────────────────┘ │
+│ ┌───────▼──────────┐ │
+│ │ Cosmos DB │ │
+│ └──────────────────┘ │
+│ │ │
+│ ┌───────▼──────────┐ ┌───────────────────────────────┐ │
+│ │ Storage Account │ │ Azure AI Services │ │
+│ └──────────────────┘ │ (GPT-4o, DALL-E, Embeddings) │ │
+│ └───────────────────────────────┘ │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+## Related Documentation
+
+- [Manual Deployment Guide](DEPLOYMENT.md)
+- [Image Generation Configuration](IMAGE_GENERATION.md)
+- [Azure Developer CLI Documentation](https://learn.microsoft.com/azure/developer/azure-developer-cli/)
diff --git a/content-gen/docs/DEPLOYMENT.md b/content-gen/docs/DEPLOYMENT.md
new file mode 100644
index 000000000..902892e1c
--- /dev/null
+++ b/content-gen/docs/DEPLOYMENT.md
@@ -0,0 +1,279 @@
+# Deployment Guide
+
+## **Pre-requisites**
+
+To deploy this solution, ensure you have access to an [Azure subscription](https://azure.microsoft.com/free/) with the necessary permissions to create **resource groups, resources, app registrations, and assign roles at the resource group level**. This should include Contributor role at the subscription level and Role Based Access Control (RBAC) permissions at the subscription and/or resource group level.
+
+Check the [Azure Products by Region](https://azure.microsoft.com/en-us/explore/global-infrastructure/products-by-region/?products=all®ions=all) page and select a **region** where the following services are available:
+
+- [Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-foundry)
+- [GPT Model Capacity](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models)
+- [DALL-E 3 Model Capacity](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models#dall-e-models)
+- [Azure App Service](https://learn.microsoft.com/en-us/azure/app-service/)
+- [Azure Container Registry](https://learn.microsoft.com/en-us/azure/container-registry/)
+- [Azure Container Instance](https://learn.microsoft.com/en-us/azure/container-instances/)
+- [Azure Cosmos DB](https://learn.microsoft.com/en-us/azure/cosmos-db/)
+- [Azure Blob Storage](https://learn.microsoft.com/en-us/azure/storage/blobs/)
+
+Here are some example regions where the services are available: East US, East US2, Australia East, UK South, France Central.
+
+### **Important Note for PowerShell Users**
+
+If you encounter issues running PowerShell scripts due to the policy of not being digitally signed, you can temporarily adjust the `ExecutionPolicy` by running the following command in an elevated PowerShell session:
+
+```powershell
+Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
+```
+
+This will allow the scripts to run for the current session without permanently changing your system's policy.
+
+## Deployment Options & Steps
+
+Pick from the options below to see step-by-step instructions for GitHub Codespaces, VS Code Dev Containers, and Local Environments.
+
+| [](https://codespaces.new/microsoft/content-generation-solution-accelerator) | [](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/content-generation-solution-accelerator) |
+|---|---|
+
+
+ Deploy in GitHub Codespaces
+
+### GitHub Codespaces
+
+You can run this solution using GitHub Codespaces. The button will open a web-based VS Code instance in your browser:
+
+1. Open the solution accelerator (this may take several minutes):
+
+ [](https://codespaces.new/microsoft/content-generation-solution-accelerator)
+
+2. Accept the default values on the create Codespaces page.
+3. Open a terminal window if it is not already open.
+4. Continue with the [deploying steps](#deploying-with-azd).
+
+
+
+
+ Deploy in VS Code
+
+### VS Code Dev Containers
+
+You can run this solution in VS Code Dev Containers, which will open the project in your local VS Code using the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers):
+
+1. Start Docker Desktop (install it if not already installed).
+2. Open the project:
+
+ [](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/content-generation-solution-accelerator)
+
+3. In the VS Code window that opens, once the project files show up (this may take several minutes), open a terminal window.
+4. Continue with the [deploying steps](#deploying-with-azd).
+
+
+
+
+ Deploy in your local Environment
+
+### Local Environment
+
+If you're not using one of the above options for opening the project, then you'll need to:
+
+1. Make sure the following tools are installed:
+ - [PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-7.5) (v7.0+) - available for Windows, macOS, and Linux.
+ - [Azure Developer CLI (azd)](https://aka.ms/install-azd) (v1.15.0+)
+ - [Python 3.11+](https://www.python.org/downloads/)
+ - [Node.js 18+](https://nodejs.org/)
+ - [Docker Desktop](https://www.docker.com/products/docker-desktop/)
+ - [Git](https://git-scm.com/downloads)
+
+2. Clone the repository or download the project code via command-line:
+
+ ```shell
+ azd init -t microsoft/content-generation-solution-accelerator/
+ ```
+
+3. Open the project folder in your terminal or editor.
+4. Continue with the [deploying steps](#deploying-with-azd).
+
+
+
+
+
+Consider the following settings during your deployment to modify specific settings:
+
+
+ Configurable Deployment Settings
+
+When you start the deployment, most parameters will have **default values**, but you can update the following settings:
+
+| **Setting** | **Description** | **Default value** |
+| ------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ---------------------- |
+| **Azure Region** | The region where resources will be created. | *(empty)* |
+| **Environment Name** | A **3–20 character alphanumeric value** used to generate a unique ID to prefix the resources. | env\_name |
+| **GPT Model** | Choose from **gpt-4, gpt-4o, gpt-4o-mini**. | gpt-4o-mini |
+| **GPT Model Version** | The version of the selected GPT model. | 2024-07-18 |
+| **OpenAI API Version** | The Azure OpenAI API version to use. | 2025-01-01-preview |
+| **GPT Model Deployment Capacity** | Configure capacity for **GPT models** (in thousands). | 30k |
+| **DALL-E Model** | DALL-E model for image generation. | dall-e-3 |
+| **Image Tag** | Docker image tag to deploy. Common values: `latest`, `dev`, `hotfix`. | latest |
+| **Use Local Build** | Boolean flag to determine if local container builds should be used. | false |
+| **Existing Log Analytics Workspace** | To reuse an existing Log Analytics Workspace ID. | *(empty)* |
+| **Existing Azure AI Foundry Project** | To reuse an existing Azure AI Foundry Project ID instead of creating a new one. | *(empty)* |
+
+
+
+
+ [Optional] Quota Recommendations
+
+By default, the **GPT-4o-mini model capacity** in deployment is set to **30k tokens**, so we recommend updating the following:
+
+> **For GPT-4o-mini - increase the capacity to at least 150k tokens post-deployment for optimal performance.**
+
+> **For DALL-E 3 - ensure you have sufficient capacity for image generation requests.**
+
+Depending on your subscription quota and capacity, you can adjust quota settings to better meet your specific needs.
+
+**⚠️ Warning:** Insufficient quota can cause deployment errors. Please ensure you have the recommended capacity or request additional capacity before deploying this solution.
+
+
+
+### Deploying with AZD
+
+Once you've opened the project in [Codespaces](#github-codespaces), [Dev Containers](#vs-code-dev-containers), or [locally](#local-environment), you can deploy it to Azure by following the steps in the [AZD Deployment Guide](AZD_DEPLOYMENT.md)
+
+## Post Deployment Steps
+
+1. **Add App Authentication**
+
+ Follow steps in [App Authentication](./AppAuthentication.md) to configure authentication in app service. Note: Authentication changes can take up to 10 minutes.
+
+2. **Assign RBAC Roles (if needed)**
+
+ If you encounter 401/403 errors, run the RBAC assignment script and wait 5-10 minutes for propagation:
+
+ ```shell
+ bash ./scripts/assign_rbac_roles.sh
+ ```
+
+3. **Deleting Resources After a Failed Deployment**
+ - Follow steps in [Delete Resource Group](./DeleteResourceGroup.md) if your deployment fails and/or you need to clean up the resources.
+
+## Troubleshooting
+
+
+ Common Issues and Solutions
+
+### 401 Unauthorized Errors
+
+**Symptom**: API calls return 401 errors
+
+**Cause**: Missing RBAC role assignments
+
+**Solution**: Run `assign_rbac_roles.sh` and wait 5-10 minutes for propagation
+
+### 403 Forbidden from Cosmos DB
+
+**Symptom**: Cosmos DB operations fail with 403
+
+**Cause**: Missing Cosmos DB data plane role (not ARM role)
+
+**Solution**: Use `az cosmosdb sql role assignment create` (not `az role assignment create`)
+
+### SSE Streaming Not Working
+
+**Symptom**: Long responses timeout, no streaming updates
+
+**Causes**:
+1. HTTP/2 enabled on App Service (breaks SSE)
+2. Proxy timeout too short
+
+**Solution**:
+```bash
+az webapp config set -g $RESOURCE_GROUP -n --http20-enabled false
+```
+
+### Backend Not Accessible
+
+**Symptom**: Frontend cannot reach backend API
+
+**Cause**: VNet/DNS configuration issues
+
+**Solution**:
+1. Verify VNet integration is enabled on App Service
+2. Verify private DNS zone is linked to VNet
+3. Verify A record points to correct ACI IP
+4. Check if ACI IP changed (run `update_backend_dns.sh`)
+
+### Image Generation Not Working
+
+**Symptom**: DALL-E requests fail
+
+**Cause**: Missing DALL-E model deployment or incorrect endpoint
+
+**Solution**:
+1. Verify DALL-E 3 deployment exists in Azure OpenAI resource
+2. Check `AZURE_OPENAI_DALLE_ENDPOINT` and `AZURE_OPENAI_DALLE_DEPLOYMENT` environment variables
+
+
+
+## Environment Variables Reference
+
+
+ Backend Environment Variables (ACI)
+
+| Variable | Description | Example |
+|----------|-------------|---------|
+| AZURE_OPENAI_ENDPOINT | GPT model endpoint | https://ai-account.cognitiveservices.azure.com/ |
+| AZURE_OPENAI_DEPLOYMENT_NAME | GPT deployment name | gpt-4o-mini |
+| AZURE_OPENAI_DALLE_ENDPOINT | DALL-E endpoint | https://dalle-account.cognitiveservices.azure.com/ |
+| AZURE_OPENAI_DALLE_DEPLOYMENT | DALL-E deployment name | dall-e-3 |
+| COSMOS_ENDPOINT | Cosmos DB endpoint | https://cosmos.documents.azure.com:443/ |
+| COSMOS_DATABASE | Database name | content-generation |
+| AZURE_STORAGE_ACCOUNT_NAME | Storage account | storagecontentgen |
+| AZURE_STORAGE_CONTAINER | Product images container | product-images |
+| AZURE_STORAGE_GENERATED_CONTAINER | Generated images container | generated-images |
+
+
+
+
+ Frontend Environment Variables (App Service)
+
+| Variable | Description | Example |
+|----------|-------------|---------|
+| BACKEND_URL | Backend API URL | http://backend.contentgen.internal:8000 |
+| WEBSITES_PORT | App Service port | 3000 |
+
+
+
+## Sample Prompts
+
+To help you get started, here are some **sample prompts** you can use with the Content Generation Solution:
+
+- "Create a product description for a new eco-friendly water bottle"
+- "Generate marketing copy for a summer sale campaign"
+- "Write social media posts promoting our latest product launch"
+- "Create an image for a blog post about sustainable living"
+- "Generate a product image showing a modern office setup"
+
+These prompts serve as a great starting point to explore the solution's capabilities with text generation, image generation, and content management.
+
+## Architecture Overview
+
+The solution consists of:
+
+- **Backend**: Python 3.11 + Quart + Hypercorn running in Azure Container Instance (ACI) with VNet integration
+- **Frontend**: React + Vite + TypeScript + Fluent UI running on Azure App Service with Node.js proxy
+- **AI Services**:
+ - Azure OpenAI (GPT model for text generation)
+ - Azure OpenAI (DALL-E 3 for image generation)
+- **Data Services**:
+ - Azure Cosmos DB (products catalog, conversations)
+ - Azure Blob Storage (product images, generated images)
+- **Networking**:
+ - Private VNet for backend container
+ - App Service with VNet integration for frontend-to-backend communication
+ - Private DNS zone for internal name resolution
+
+## Security Considerations
+
+1. **Managed Identity**: The solution uses system-assigned managed identity instead of connection strings
+2. **Private VNet**: Backend runs in private subnet, not exposed to internet
+3. **RBAC**: Principle of least privilege - only necessary roles are assigned
+4. **No Secrets in Code**: All credentials managed through Azure identity
diff --git a/content-gen/docs/IMAGE_GENERATION.md b/content-gen/docs/IMAGE_GENERATION.md
new file mode 100644
index 000000000..23dfe21f2
--- /dev/null
+++ b/content-gen/docs/IMAGE_GENERATION.md
@@ -0,0 +1,239 @@
+# DALL-E 3 Image Generation: Limitations and Workarounds
+
+## Overview
+
+This document describes the limitations of DALL-E 3 for image generation in the Intelligent Content Generation Accelerator and the workarounds implemented to achieve product-seeded marketing image generation.
+
+## DALL-E 3 Limitations
+
+### Text-Only Input
+
+**DALL-E 3 only accepts text prompts**. Unlike newer models such as GPT-image-1, DALL-E 3 does not support:
+
+- Image-to-image generation
+- Reference/seed images as input
+- Image editing or inpainting with image inputs
+
+This means you cannot directly pass a product image to DALL-E 3 and ask it to create a marketing image featuring that product.
+
+### API Capabilities
+
+| Capability | DALL-E 3 | GPT-image-1 |
+|------------|----------|-------------|
+| Text prompts | ✅ | ✅ |
+| Image input | ❌ | ✅ |
+| Image editing | ❌ | ✅ |
+| Inpainting | ❌ | ✅ |
+| Multiple images per request | 1 only | 1-10 |
+| Output format | URL or base64 | base64 only |
+
+## Implemented Workaround
+
+### GPT-5 Vision for Product Descriptions
+
+To work around DALL-E 3's text-only limitation, we use **GPT-5 Vision** to generate detailed text descriptions of product images during the product ingestion process.
+
+#### Workflow
+
+```
+┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
+│ Product Image │────▶│ GPT-5 Vision │────▶│ Text Description│
+│ (Blob Storage) │ │ (Auto-analyze) │ │ (CosmosDB) │
+└─────────────────┘ └─────────────────┘ └─────────────────┘
+ │
+ ▼
+┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
+│ Marketing Image │◀────│ DALL-E 3 │◀────│ Combined Prompt │
+│ (Output) │ │ (Generate) │ │ (Desc + Brief) │
+└─────────────────┘ └─────────────────┘ └─────────────────┘
+```
+
+#### Step 1: Product Image Ingestion
+
+When a product image is uploaded to Blob Storage, the `ProductIngestionService` automatically:
+
+1. Sends the image to GPT-5 Vision
+2. Generates a detailed text description including:
+ - Product appearance (colors, shapes, materials)
+ - Key visual features
+ - Composition and positioning
+ - Style and aesthetic qualities
+3. Stores the description in CosmosDB alongside product metadata
+
+```python
+async def generate_image_description(image_url: str) -> str:
+ """Generate detailed text description of product image using GPT-5 Vision."""
+ response = await openai_client.chat.completions.create(
+ model="gpt-5",
+ messages=[
+ {
+ "role": "user",
+ "content": [
+ {
+ "type": "text",
+ "text": """Describe this product image in detail for use in marketing image generation.
+ Include: colors, materials, shape, key features, style, and positioning.
+ Be specific enough that an image generator could recreate a similar product."""
+ },
+ {
+ "type": "image_url",
+ "image_url": {"url": image_url}
+ }
+ ]
+ }
+ ],
+ max_tokens=500
+ )
+ return response.choices[0].message.content
+```
+
+#### Step 2: Marketing Image Generation
+
+The `ImageContentAgent` combines the stored product description with:
+
+- Creative brief visual guidelines
+- Brand guidelines (colors, style, composition rules)
+- Scene/context requirements
+
+```python
+async def generate_marketing_image(
+ product: Product,
+ creative_brief: CreativeBrief,
+ brand_guidelines: BrandGuidelines
+) -> bytes:
+ """Generate marketing image using DALL-E 3 with product context."""
+
+ prompt = f"""
+ Create a professional marketing image for a retail campaign.
+
+ PRODUCT (maintain accuracy):
+ {product.image_description}
+
+ SCENE:
+ {creative_brief.visual_guidelines}
+
+ BRAND STYLE:
+ - Primary color: {brand_guidelines.primary_color}
+ - Style: {brand_guidelines.image_style}
+ - Composition: Product centered, 30% negative space
+
+ REQUIREMENTS:
+ - Professional lighting
+ - Clean, modern aesthetic
+ - Suitable for {creative_brief.deliverable}
+ """
+
+ response = await openai_client.images.generate(
+ model="dall-e-3",
+ prompt=prompt,
+ size="1024x1024",
+ quality="hd",
+ n=1
+ )
+
+ return response.data[0].url
+```
+
+## Limitations of the Workaround
+
+### Accuracy Trade-offs
+
+1. **Product Representation**: The generated product in the marketing image may not be an exact match to the original product. DALL-E 3 interprets the text description and creates its own version.
+
+2. **Brand-Specific Details**: Logos, specific patterns, or unique design elements may not be accurately reproduced.
+
+3. **Color Matching**: While we include color descriptions, exact color matching is not guaranteed.
+
+### Recommended Use Cases
+
+| Use Case | Suitability |
+|----------|-------------|
+| Lifestyle/contextual marketing images | ✅ Excellent |
+| Social media campaign visuals | ✅ Excellent |
+| Concept mockups | ✅ Good |
+| Product-in-scene compositions | ✅ Good |
+| Exact product photography replacement | ❌ Not recommended |
+| Catalog/technical images | ❌ Not recommended |
+
+## Future Upgrade Path: GPT-image-1
+
+### When Available
+
+GPT-image-1 (currently in limited access preview) will enable true image-to-image generation:
+
+```python
+# Future implementation with GPT-image-1
+async def generate_marketing_image_with_seed(
+ product_image_path: str,
+ scene_description: str,
+ brand_style: str
+) -> bytes:
+ """Generate marketing image seeded with actual product photo."""
+
+ response = await openai_client.images.edit(
+ model="gpt-image-1",
+ image=open(product_image_path, "rb"), # Actual product image as input
+ prompt=f"""
+ Create a marketing image featuring the product shown.
+ Scene: {scene_description}
+ Brand Style: {brand_style}
+ Maintain product accuracy.
+ """,
+ size="1024x1024",
+ quality="high",
+ input_fidelity="high" # Preserve product details
+ )
+
+ return base64.b64decode(response.data[0].b64_json)
+```
+
+### How to Request Access
+
+1. Visit [GPT-image-1 Access Request](https://aka.ms/oai/gptimage1access)
+2. Complete the application form
+3. Wait for approval (typically 1-2 weeks)
+4. Update the `ImageContentAgent` to use the Image Edit API
+
+### Migration Steps
+
+When GPT-image-1 access is granted:
+
+1. Update `AZURE_DALLE_MODEL` environment variable to `gpt-image-1`
+2. Modify `ImageContentAgent` to use `images.edit()` instead of `images.generate()`
+3. Update Blob Storage retrieval to pass actual image bytes
+4. Test with sample products before production deployment
+
+## Best Practices
+
+### Optimizing Product Descriptions
+
+For best results with the text-based workaround:
+
+1. **Be Specific**: Include exact colors, materials, and dimensions
+2. **Describe Unique Features**: Highlight what makes the product distinctive
+3. **Include Context**: Mention typical use cases or settings
+4. **Avoid Ambiguity**: Use precise terminology
+
+### Example High-Quality Description
+
+```
+A sleek wireless Bluetooth headphone in matte black finish with
+rose gold accents on the ear cup rims and headband adjustment
+sliders. Over-ear cushions in premium memory foam covered with
+soft protein leather. The headband features a padded top section
+with subtle brand embossing. The left ear cup has touch-sensitive
+controls visible as a circular touch pad. Cable port and power
+button are positioned on the bottom edge of the right ear cup.
+Overall aesthetic is premium, modern, and minimalist.
+```
+
+## Compliance Considerations
+
+All generated images are validated by the `ComplianceAgent` for:
+
+- Brand color adherence
+- Prohibited visual elements
+- Appropriate imagery for target audience
+- Required disclaimers (added as text overlay if needed)
+
+Images with compliance violations are flagged with appropriate severity levels before user review.
diff --git a/content-gen/docs/LOCAL_DEPLOYMENT.md b/content-gen/docs/LOCAL_DEPLOYMENT.md
new file mode 100644
index 000000000..6a12e0f39
--- /dev/null
+++ b/content-gen/docs/LOCAL_DEPLOYMENT.md
@@ -0,0 +1,379 @@
+# Local Development Guide
+
+This guide covers running the Content Generation Solution Accelerator locally for development and testing.
+
+## Prerequisites
+
+- **Python 3.11+** - Backend runtime
+- **Node.js 18+** - Frontend build tools
+- **Azure CLI** - For authentication and environment setup
+- **Azure Developer CLI (azd)** - Optional, for automatic environment configuration
+
+### Azure Resources
+
+You need access to the following Azure resources (can use an existing deployment):
+
+- Azure OpenAI with GPT and image generation models deployed
+- Azure Cosmos DB account with database and containers
+- Azure Blob Storage account
+- Azure AI Search service (optional, for product search)
+
+## Quick Start
+
+### Linux/Mac
+
+```bash
+# First time setup
+./scripts/local_dev.sh setup
+
+# Start development servers
+./scripts/local_dev.sh
+```
+
+### Windows PowerShell
+
+```powershell
+# First time setup
+.\scripts\local_dev.ps1 -Command setup
+
+# Start development servers
+.\scripts\local_dev.ps1
+```
+
+## Environment Configuration
+
+### Option 1: Generate from Azure Deployment
+
+If you have an existing Azure deployment with `azd`:
+
+```bash
+./scripts/local_dev.sh env
+```
+
+### Option 2: Manual Configuration
+
+1. Copy the environment template:
+ ```bash
+ cp .env.sample .env
+ ```
+
+2. Edit `.env` with your Azure resource values (see [Environment Variables Reference](#environment-variables-reference) below)
+
+## Development Commands
+
+| Command | Description |
+|---------|-------------|
+| `setup` | Create virtual environment, install Python and Node.js dependencies |
+| `env` | Generate `.env` file from Azure resources (uses azd if available) |
+| `backend` | Start only the Python/Quart backend server (port 5000) |
+| `frontend` | Start only the Vite frontend dev server (port 3000) |
+| `all` | Start both backend and frontend in parallel (default) |
+| `build` | Build frontend for production |
+| `clean` | Remove cache files, node_modules, and build artifacts |
+
+### Usage Examples
+
+**Linux/Mac:**
+```bash
+# Full setup and start
+./scripts/local_dev.sh setup
+./scripts/local_dev.sh
+
+# Start only backend (for API development)
+./scripts/local_dev.sh backend
+
+# Start only frontend (if backend is running elsewhere)
+./scripts/local_dev.sh frontend
+
+# Build for production
+./scripts/local_dev.sh build
+
+# Clean up
+./scripts/local_dev.sh clean
+```
+
+**Windows PowerShell:**
+```powershell
+# Full setup and start
+.\scripts\local_dev.ps1 -Command setup
+.\scripts\local_dev.ps1
+
+# Start only backend
+.\scripts\local_dev.ps1 -Command backend
+
+# Start only frontend
+.\scripts\local_dev.ps1 -Command frontend
+```
+
+## Development URLs
+
+| Service | URL |
+|---------|-----|
+| Frontend | http://localhost:3000 |
+| Backend API | http://localhost:5000 |
+| Health Check | http://localhost:5000/api/health |
+
+The frontend Vite dev server automatically proxies `/api/*` requests to the backend.
+
+## Hot Reload
+
+Both servers support hot reload:
+- **Backend**: Uses Hypercorn with `--reload` flag
+- **Frontend**: Uses Vite's built-in HMR (Hot Module Replacement)
+
+Changes to source files will automatically trigger a reload.
+
+---
+
+## Environment Variables Reference
+
+### Server Configuration
+
+| Variable | Default | Description |
+|----------|---------|-------------|
+| `PORT` | `5000` | Port for the Python backend API |
+| `WORKERS` | `4` | Number of Hypercorn worker processes |
+| `BACKEND_PORT` | `5000` | Alternative port variable for local dev scripts |
+| `FRONTEND_PORT` | `3000` | Port for the Vite dev server |
+
+### Azure Authentication
+
+| Variable | Required | Description |
+|----------|----------|-------------|
+| `AZURE_CLIENT_ID` | No | Azure AD application (client) ID for authentication |
+
+### Azure OpenAI Configuration
+
+| Variable | Required | Description |
+|----------|----------|-------------|
+| `AZURE_OPENAI_ENDPOINT` | Yes | Azure OpenAI endpoint URL (e.g., `https://your-resource.openai.azure.com/`) |
+| `AZURE_OPENAI_GPT_MODEL` | Yes | GPT model deployment name (e.g., `gpt-4o`, `gpt-5.1`) |
+| `AZURE_OPENAI_IMAGE_MODEL` | Yes | Image generation model (`dall-e-3` or `gpt-image-1`) |
+| `AZURE_OPENAI_GPT_IMAGE_ENDPOINT` | No | Separate endpoint for gpt-image-1 (if different from main endpoint) |
+| `AZURE_OPENAI_API_VERSION` | Yes | API version (e.g., `2024-06-01`) |
+| `AZURE_OPENAI_PREVIEW_API_VERSION` | No | Preview API version for new features |
+| `AZURE_OPENAI_TEMPERATURE` | No | Generation temperature (default: `0.7`) |
+| `AZURE_OPENAI_MAX_TOKENS` | No | Max tokens for generation (default: `2000`) |
+
+### Image Generation Settings
+
+| Variable | Default | Description |
+|----------|---------|-------------|
+| `AZURE_OPENAI_IMAGE_SIZE` | `1024x1024` | Image dimensions |
+| `AZURE_OPENAI_IMAGE_QUALITY` | `medium` | Image quality setting |
+
+**DALL-E 3 Options:**
+- Sizes: `1024x1024`, `1024x1792`, `1792x1024`
+- Quality: `standard`, `hd`
+
+**GPT-Image-1 Options:**
+- Sizes: `1024x1024`, `1536x1024`, `1024x1536`, `auto`
+- Quality: `low`, `medium`, `high`, `auto`
+
+### Azure Cosmos DB
+
+| Variable | Required | Description |
+|----------|----------|-------------|
+| `AZURE_COSMOS_ENDPOINT` | Yes | Cosmos DB endpoint URL |
+| `AZURE_COSMOS_DATABASE_NAME` | Yes | Database name (default: `content-generation`) |
+| `AZURE_COSMOS_PRODUCTS_CONTAINER` | Yes | Products container name (default: `products`) |
+| `AZURE_COSMOS_CONVERSATIONS_CONTAINER` | Yes | Conversations container name (default: `conversations`) |
+
+### Azure Blob Storage
+
+| Variable | Required | Description |
+|----------|----------|-------------|
+| `AZURE_BLOB_ACCOUNT_NAME` | Yes | Storage account name |
+| `AZURE_BLOB_PRODUCT_IMAGES_CONTAINER` | Yes | Container for product images (default: `product-images`) |
+| `AZURE_BLOB_GENERATED_IMAGES_CONTAINER` | Yes | Container for AI-generated images (default: `generated-images`) |
+
+### Azure AI Search
+
+| Variable | Required | Description |
+|----------|----------|-------------|
+| `AZURE_AI_SEARCH_ENDPOINT` | No | AI Search service endpoint URL |
+| `AZURE_AI_SEARCH_PRODUCTS_INDEX` | No | Product search index name (default: `products`) |
+| `AZURE_AI_SEARCH_IMAGE_INDEX` | No | Image search index name (default: `product-images`) |
+
+### UI Configuration
+
+| Variable | Default | Description |
+|----------|---------|-------------|
+| `UI_APP_NAME` | `Content Generation Accelerator` | Application name shown in UI |
+| `UI_TITLE` | `Content Generation` | Browser tab title |
+| `UI_CHAT_TITLE` | `Marketing Content Generator` | Chat interface title |
+| `UI_CHAT_DESCRIPTION` | AI-powered multimodal content generation... | Chat interface description |
+
+### Brand Guidelines
+
+| Variable | Default | Description |
+|----------|---------|-------------|
+| `BRAND_TONE` | `Professional yet approachable` | Brand voice tone |
+| `BRAND_VOICE` | `Innovative, trustworthy, customer-focused` | Brand voice characteristics |
+| `BRAND_PRIMARY_COLOR` | `#0078D4` | Primary brand color (hex) |
+| `BRAND_SECONDARY_COLOR` | `#107C10` | Secondary brand color (hex) |
+| `BRAND_IMAGE_STYLE` | `Modern, clean, minimalist...` | Image generation style guidance |
+| `BRAND_MAX_HEADLINE_LENGTH` | `60` | Maximum headline character length |
+| `BRAND_MAX_BODY_LENGTH` | `500` | Maximum body text character length |
+| `BRAND_REQUIRE_CTA` | `true` | Require call-to-action in content |
+| `BRAND_PROHIBITED_WORDS` | `cheapest,guaranteed,...` | Comma-separated list of prohibited words |
+| `BRAND_REQUIRED_DISCLOSURES` | `` | Comma-separated required legal disclosures |
+
+### Application Settings
+
+| Variable | Default | Description |
+|----------|---------|-------------|
+| `AUTH_ENABLED` | `false` | Enable Azure AD authentication |
+| `SANITIZE_ANSWER` | `false` | Sanitize AI responses for safety |
+
+---
+
+## Example .env File
+
+```dotenv
+# Azure OpenAI
+AZURE_OPENAI_ENDPOINT=https://my-openai.openai.azure.com/
+AZURE_OPENAI_GPT_MODEL=gpt-4o
+AZURE_OPENAI_IMAGE_MODEL=gpt-image-1
+AZURE_OPENAI_GPT_IMAGE_ENDPOINT=https://my-openai.openai.azure.com
+AZURE_OPENAI_IMAGE_SIZE=1024x1024
+AZURE_OPENAI_IMAGE_QUALITY=medium
+AZURE_OPENAI_API_VERSION=2024-06-01
+
+# Cosmos DB
+AZURE_COSMOS_ENDPOINT=https://my-cosmos.documents.azure.com:443/
+AZURE_COSMOS_DATABASE_NAME=content-generation
+AZURE_COSMOS_PRODUCTS_CONTAINER=products
+AZURE_COSMOS_CONVERSATIONS_CONTAINER=conversations
+
+# Blob Storage
+AZURE_BLOB_ACCOUNT_NAME=mystorageaccount
+AZURE_BLOB_PRODUCT_IMAGES_CONTAINER=product-images
+AZURE_BLOB_GENERATED_IMAGES_CONTAINER=generated-images
+
+# AI Search (optional)
+AZURE_AI_SEARCH_ENDPOINT=https://my-search.search.windows.net
+AZURE_AI_SEARCH_PRODUCTS_INDEX=products
+
+# UI
+UI_APP_NAME=Content Generation Accelerator
+UI_TITLE=Content Generation
+
+# Brand
+BRAND_TONE=Professional yet approachable
+BRAND_VOICE=Innovative, trustworthy, customer-focused
+BRAND_PRIMARY_COLOR=#0078D4
+BRAND_PROHIBITED_WORDS=cheapest,guaranteed,best in class
+
+# Server
+PORT=5000
+AUTH_ENABLED=false
+```
+
+---
+
+## Utility Scripts
+
+### Data Scripts
+
+| Script | Description |
+|--------|-------------|
+| `load_sample_data.py` | Load sample products into Cosmos DB and images into Blob Storage |
+| `index_products.py` | Create and populate Azure AI Search index with product data |
+| `upload_images.py` | Upload images from local directory to Blob Storage |
+| `create_image_search_index.py` | Create image search index for visual similarity |
+
+**Usage:**
+```bash
+# Load sample data
+python scripts/load_sample_data.py
+
+# Create search index
+python scripts/index_products.py
+
+# Upload images
+python scripts/upload_images.py --source ./images --container product-images
+```
+
+### Testing Scripts
+
+| Script | Description |
+|--------|-------------|
+| `test_content_generation.py` | End-to-end test for content generation pipeline |
+
+**Usage:**
+```bash
+python scripts/test_content_generation.py
+```
+
+---
+
+## Troubleshooting
+
+### Port Already in Use
+
+```bash
+# Find and kill the process
+lsof -i :5000
+kill -9
+
+# Or use a different port
+BACKEND_PORT=8000 ./scripts/local_dev.sh backend
+```
+
+### Virtual Environment Issues
+
+```bash
+# Remove and recreate
+rm -rf .venv
+./scripts/local_dev.sh setup
+```
+
+### Node Modules Issues
+
+```bash
+# Clean and reinstall
+./scripts/local_dev.sh clean
+./scripts/local_dev.sh setup
+```
+
+### Azure Authentication
+
+```bash
+# Re-authenticate
+az login
+az account set --subscription
+
+# Verify authentication
+az account show
+```
+
+### Cosmos DB Access Denied
+
+Ensure your user has the "Cosmos DB Data Contributor" role:
+```bash
+az cosmosdb sql role assignment create \
+ --resource-group \
+ --account-name \
+ --role-definition-id "00000000-0000-0000-0000-000000000002" \
+ --principal-id $(az ad signed-in-user show --query id -o tsv) \
+ --scope "/"
+```
+
+### Storage Access Denied
+
+Ensure your user has the "Storage Blob Data Contributor" role:
+```bash
+az role assignment create \
+ --role "Storage Blob Data Contributor" \
+ --assignee $(az ad signed-in-user show --query userPrincipalName -o tsv) \
+ --scope /subscriptions//resourceGroups//providers/Microsoft.Storage/storageAccounts/
+```
+
+---
+
+## Related Documentation
+
+- [AZD Deployment Guide](AZD_DEPLOYMENT.md) - Deploy to Azure with `azd up`
+- [Manual Deployment Guide](DEPLOYMENT.md) - Step-by-step Azure deployment
+- [Image Generation Configuration](IMAGE_GENERATION.md) - DALL-E 3 and GPT-Image-1 setup
diff --git a/content-gen/docs/TRANSPARENCY_FAQ.md b/content-gen/docs/TRANSPARENCY_FAQ.md
new file mode 100644
index 000000000..06974bb12
--- /dev/null
+++ b/content-gen/docs/TRANSPARENCY_FAQ.md
@@ -0,0 +1,25 @@
+## Content Generation Solution Accelerator: Responsible AI FAQ
+- ### What is the Multi-Agent Content Generation?
+ This solution accelerator is an open-source GitHub Repository to help create context-aware, multimodal campaign content (text + images) using a multiagent solution. This can be used by anyone looking for reusable architecture and code snippets to build a ready-to-use, extensible, multi-agent system that generates marketing content. The repository showcases a scenario of a user who wants to generate a marketing ad campaign for a social media post based on a sample set of data.
+
+- ### What can the Multi-Agent Content Generation do?
+ This is a multimodal content generation solution for retail marketing campaigns. It uses Microsoft Agent Framework with Handoff Builder orchestration to interpret creative briefs and generate compliant marketing content (text + images) grounded in enterprise product data and brand guidelines.
+
+ **Key Capabilities:**
+ - Parse free-text creative briefs into structured fields
+ - Generate marketing copy using GPT models
+ - Generate marketing images using DALL-E 3
+ - Validate content against brand guidelines with severity-categorized compliance checks
+ - Ground content in product catalog data from Cosmos DB
+
+- ### What is/are Content Generation Solution Accelerator's intended use(s)?
+ This repository is to be used only as a solution accelerator following the open-source license terms listed in the GitHub repository. The example scenario's intended purpose is to help users generate a marketing ad campaign for a social media post based on a sample set of data and help them perform their work more efficiently.
+
+- ### How was Content Generation Solution Accelerator evaluated? What metrics are used to measure performance?
+ We have used the Azure AI evaluation SDK to test for harmful content, groundedness and potential risks.
+
+- ### What are the limitations of Content Generation Solution Accelerator? How can users minimize the impact of Content Generation Solution Accelerator's limitations when using the system?
+ This solution accelerator can only be used as a sample to accelerate the creation of an AI assistant. The repository showcases a sample scenario of a user generating a marketing ad campaign. Users should review the system prompts provided and update them as per their organizational guidance. Users should run their own evaluation flow either using the guidance provided in the GitHub repository or their choice of evaluation methods. AI-generated content may be inaccurate and should be manually reviewed. Currently, the sample repo is available in English only.
+
+- ### What operational factors and settings allow for effective and responsible use of Content Generation Solution Accelerator?
+ Users can try different values for some parameters like system prompt, image models, etc. shared as configurable environment variables while running run evaluations for AI assistants. Please note that these parameters are only provided as guidance to start the configuration but not as a complete available list to adjust the system behavior. Please always refer to the latest product documentation for these details or reach out to your Microsoft account team if you need assistance.
diff --git a/content-gen/infra/main.bicep b/content-gen/infra/main.bicep
new file mode 100644
index 000000000..092783a12
--- /dev/null
+++ b/content-gen/infra/main.bicep
@@ -0,0 +1,1019 @@
+// ========== main.bicep ========== //
+targetScope = 'resourceGroup'
+
+metadata name = 'Intelligent Content Generation Accelerator'
+metadata description = '''Solution Accelerator for multimodal marketing content generation using Microsoft Agent Framework.
+'''
+
+@minLength(3)
+@maxLength(15)
+@description('Optional. A unique application/solution name for all resources in this deployment.')
+param solutionName string = 'contentgen'
+
+@maxLength(5)
+@description('Optional. A unique text value for the solution.')
+param solutionUniqueText string = substring(uniqueString(subscription().id, resourceGroup().name, solutionName), 0, 5)
+
+@allowed([
+ 'australiaeast'
+ 'centralus'
+ 'eastasia'
+ 'eastus'
+ 'eastus2'
+ 'japaneast'
+ 'northeurope'
+ 'southeastasia'
+ 'swedencentral'
+ 'uksouth'
+ 'westus'
+ 'westus3'
+])
+@metadata({ azd: { type: 'location' } })
+@description('Required. Azure region for all services.')
+param location string
+
+@minLength(3)
+@description('Optional. Secondary location for databases creation.')
+param secondaryLocation string = 'uksouth'
+
+@description('Optional. Location for AI deployments. If not specified, uses the main location.')
+param azureAiServiceLocation string = ''
+
+@minLength(1)
+@allowed([
+ 'Standard'
+ 'GlobalStandard'
+])
+@description('Optional. GPT model deployment type.')
+param gptModelDeploymentType string = 'GlobalStandard'
+
+@minLength(1)
+@description('Optional. Name of the GPT model to deploy.')
+param gptModelName string = 'gpt-5.1'
+
+@description('Optional. Version of the GPT model to deploy.')
+param gptModelVersion string = '2025-11-13'
+
+@description('Optional. Image model to deploy: gpt-image-1, gpt-image-1.5, dall-e-3, or none to skip.')
+@allowed([
+ 'gpt-image-1'
+ 'gpt-image-1.5'
+ 'dall-e-3'
+ 'none'
+])
+param imageModelChoice string = 'gpt-image-1'
+
+@description('Optional. API version for Azure OpenAI service.')
+param azureOpenaiAPIVersion string = '2025-01-01-preview'
+
+@description('Optional. API version for Azure AI Agent service.')
+param azureAiAgentApiVersion string = '2025-05-01'
+
+@minValue(10)
+@description('Optional. AI model deployment token capacity.')
+param gptModelCapacity int = 150
+
+@minValue(1)
+@description('Optional. Image model deployment capacity (RPM).')
+param dalleModelCapacity int = 1
+
+@description('Optional. Existing Log Analytics Workspace Resource ID.')
+param existingLogAnalyticsWorkspaceId string = ''
+
+@description('Optional. Resource ID of an existing Foundry project.')
+param azureExistingAIProjectResourceId string = ''
+
+@description('Optional. Deploy Azure Bastion and Jumpbox VM for private network administration.')
+param deployBastionAndJumpbox bool = false
+
+@description('Optional. The tags to apply to all deployed Azure resources.')
+param tags object = {}
+
+@description('Optional. Enable monitoring for applicable resources (WAF-aligned).')
+param enableMonitoring bool = false
+
+@description('Optional. Enable Azure AI Foundry mode for multi-agent orchestration.')
+param useFoundryMode bool = true
+
+@description('Optional. Enable scalability for applicable resources (WAF-aligned).')
+param enableScalability bool = false
+
+@description('Optional. Enable redundancy for applicable resources (WAF-aligned).')
+param enableRedundancy bool = false
+
+@description('Optional. Enable private networking for applicable resources (WAF-aligned).')
+param enablePrivateNetworking bool = false
+
+@description('Required. The existing Container Registry name (without .azurecr.io). Must contain pre-built images: content-gen-app and content-gen-api.')
+param acrName string = 'contentgencontainerreg'
+
+@description('Optional. Image Tag.')
+param imageTag string = 'latest'
+
+@description('Optional. Enable/Disable usage telemetry.')
+param enableTelemetry bool = true
+
+@description('Optional. Created by user name.')
+param createdBy string = contains(deployer(), 'userPrincipalName')? split(deployer().userPrincipalName, '@')[0]: deployer().objectId
+
+// ============== //
+// Variables //
+// ============== //
+
+var solutionLocation = empty(location) ? resourceGroup().location : location
+
+// Regions that support GPT-5.1, GPT-Image-1, and text-embedding models with GlobalStandard SKU
+// Update this list as Azure expands model availability
+var validAiServiceRegions = [
+ 'australiaeast'
+ 'eastus'
+ 'eastus2'
+ 'francecentral'
+ 'japaneast'
+ 'koreacentral'
+ 'swedencentral'
+ 'switzerlandnorth'
+ 'uaenorth'
+ 'uksouth'
+ 'westus'
+ 'westus3'
+]
+
+// Map regions to recommended AI service regions (for when main region lacks model support)
+var aiServiceRegionFallback = {
+ australiaeast: 'australiaeast'
+ australiasoutheast: 'australiaeast'
+ brazilsouth: 'eastus2'
+ canadacentral: 'eastus2'
+ canadaeast: 'eastus2'
+ centralindia: 'uksouth'
+ centralus: 'eastus2'
+ eastasia: 'japaneast'
+ eastus: 'eastus'
+ eastus2: 'eastus2'
+ francecentral: 'francecentral'
+ germanywestcentral: 'swedencentral'
+ japaneast: 'japaneast'
+ japanwest: 'japaneast'
+ koreacentral: 'koreacentral'
+ koreasouth: 'koreacentral'
+ northcentralus: 'eastus2'
+ northeurope: 'swedencentral'
+ norwayeast: 'swedencentral'
+ polandcentral: 'swedencentral'
+ qatarcentral: 'uaenorth'
+ southafricanorth: 'uksouth'
+ southcentralus: 'eastus2'
+ southeastasia: 'japaneast'
+ southindia: 'uksouth'
+ swedencentral: 'swedencentral'
+ switzerlandnorth: 'switzerlandnorth'
+ uaenorth: 'uaenorth'
+ uksouth: 'uksouth'
+ ukwest: 'uksouth'
+ westcentralus: 'westus'
+ westeurope: 'swedencentral'
+ westindia: 'uksouth'
+ westus: 'westus'
+ westus2: 'westus'
+ westus3: 'westus3'
+}
+
+// Determine effective AI service location:
+// 1. If explicitly set via parameter, use that (user override)
+// 2. If main location is valid for AI services, use it
+// 3. Otherwise, use the fallback mapping
+var requestedAiLocation = empty(azureAiServiceLocation) ? solutionLocation : azureAiServiceLocation
+var aiServiceLocation = contains(validAiServiceRegions, requestedAiLocation)
+ ? requestedAiLocation
+ : (aiServiceRegionFallback[?solutionLocation] ?? 'eastus2')
+
+// acrName is required - points to existing ACR with pre-built images
+var acrResourceName = acrName
+var solutionSuffix = toLower(trim(replace(
+ replace(
+ replace(replace(replace(replace('${solutionName}${solutionUniqueText}', '-', ''), '_', ''), '.', ''), '/', ''),
+ ' ',
+ ''
+ ),
+ '*',
+ ''
+)))
+
+var cosmosDbZoneRedundantHaRegionPairs = {
+ australiaeast: 'uksouth'
+ centralus: 'eastus2'
+ eastasia: 'southeastasia'
+ eastus: 'centralus'
+ eastus2: 'centralus'
+ japaneast: 'australiaeast'
+ northeurope: 'westeurope'
+ southeastasia: 'eastasia'
+ uksouth: 'westeurope'
+ westus: 'westus3'
+ westus3: 'westus'
+}
+var cosmosDbHaLocation = cosmosDbZoneRedundantHaRegionPairs[?resourceGroup().location] ?? secondaryLocation
+
+var replicaRegionPairs = {
+ australiaeast: 'australiasoutheast'
+ centralus: 'westus'
+ eastasia: 'japaneast'
+ eastus: 'centralus'
+ eastus2: 'centralus'
+ japaneast: 'eastasia'
+ northeurope: 'westeurope'
+ southeastasia: 'eastasia'
+ uksouth: 'westeurope'
+ westus: 'westus3'
+ westus3: 'westus'
+}
+var replicaLocation = replicaRegionPairs[?resourceGroup().location] ?? secondaryLocation
+
+var azureSearchIndex = 'products'
+var aiSearchName = 'srch-${solutionSuffix}'
+var aiSearchConnectionName = 'foundry-search-connection-${solutionSuffix}'
+
+// Extracts subscription, resource group, and workspace name from the resource ID
+var useExistingLogAnalytics = !empty(existingLogAnalyticsWorkspaceId)
+var useExistingAiFoundryAiProject = !empty(azureExistingAIProjectResourceId)
+var aiFoundryAiServicesResourceGroupName = useExistingAiFoundryAiProject
+ ? split(azureExistingAIProjectResourceId, '/')[4]
+ : 'rg-${solutionSuffix}'
+// var aiFoundryAiServicesSubscriptionId = useExistingAiFoundryAiProject
+// ? split(azureExistingAIProjectResourceId, '/')[2]
+// : subscription().id
+var aiFoundryAiServicesResourceName = useExistingAiFoundryAiProject
+ ? split(azureExistingAIProjectResourceId, '/')[8]
+ : 'aif-${solutionSuffix}'
+var aiFoundryAiProjectResourceName = useExistingAiFoundryAiProject
+ ? split(azureExistingAIProjectResourceId, '/')[10]
+ : 'proj-${solutionSuffix}'
+
+// Base model deployments (GPT only - no embeddings needed for content generation)
+var baseModelDeployments = [
+ {
+ format: 'OpenAI'
+ name: gptModelName
+ model: gptModelName
+ sku: {
+ name: gptModelDeploymentType
+ capacity: gptModelCapacity
+ }
+ version: gptModelVersion
+ raiPolicyName: 'Microsoft.Default'
+ }
+]
+
+// Image model configuration based on choice
+var imageModelConfig = {
+ 'gpt-image-1': {
+ name: 'gpt-image-1'
+ version: '2025-04-15'
+ sku: 'GlobalStandard'
+ }
+ 'gpt-image-1.5': {
+ name: 'gpt-image-1.5'
+ version: '2025-12-16'
+ sku: 'GlobalStandard'
+ }
+ 'dall-e-3': {
+ name: 'dall-e-3'
+ version: '3.0'
+ sku: 'Standard'
+ }
+ none: {
+ name: ''
+ version: ''
+ sku: ''
+ }
+}
+
+// Image model deployment (optional)
+var imageModelDeployment = imageModelChoice != 'none' ? [
+ {
+ format: 'OpenAI'
+ name: imageModelConfig[imageModelChoice].name
+ model: imageModelConfig[imageModelChoice].name
+ sku: {
+ name: imageModelConfig[imageModelChoice].sku
+ capacity: dalleModelCapacity
+ }
+ version: imageModelConfig[imageModelChoice].version
+ raiPolicyName: 'Microsoft.Default'
+ }
+] : []
+
+// Combine deployments based on imageModelChoice
+var aiFoundryAiServicesModelDeployment = concat(baseModelDeployments, imageModelDeployment)
+
+var aiFoundryAiProjectDescription = 'Content Generation AI Foundry Project'
+
+// ============== //
+// Resources //
+// ============== //
+
+#disable-next-line no-deployments-resources
+resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableTelemetry) {
+ name: '46d3xbcp.ptn.sa-contentgen.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, solutionLocation), 0, 4)}'
+ properties: {
+ mode: 'Incremental'
+ template: {
+ '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#'
+ contentVersion: '1.0.0.0'
+ resources: []
+ outputs: {
+ telemetry: {
+ type: 'String'
+ value: 'For more information, see https://aka.ms/avm/TelemetryInfo'
+ }
+ }
+ }
+ }
+}
+
+// ========== Resource Group Tag ========== //
+resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = {
+ name: 'default'
+ properties: {
+ tags: {
+ ...resourceGroup().tags
+ ... tags
+ TemplateName: 'ContentGen'
+ Type: enablePrivateNetworking ? 'WAF' : 'Non-WAF'
+ CreatedBy: createdBy
+ }
+ }
+}
+
+// ========== Log Analytics Workspace ========== //
+var logAnalyticsWorkspaceResourceName = 'log-${solutionSuffix}'
+module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0.14.2' = if (enableMonitoring && !useExistingLogAnalytics) {
+ name: take('avm.res.operational-insights.workspace.${logAnalyticsWorkspaceResourceName}', 64)
+ params: {
+ name: logAnalyticsWorkspaceResourceName
+ tags: tags
+ location: solutionLocation
+ enableTelemetry: enableTelemetry
+ skuName: 'PerGB2018'
+ dataRetention: 365
+ features: { enableLogAccessUsingOnlyResourcePermissions: true }
+ diagnosticSettings: [{ useThisWorkspace: true }]
+ dailyQuotaGb: enableRedundancy ? 10 : null
+ replication: enableRedundancy
+ ? {
+ enabled: true
+ location: replicaLocation
+ }
+ : null
+ publicNetworkAccessForIngestion: enablePrivateNetworking ? 'Disabled' : 'Enabled'
+ publicNetworkAccessForQuery: enablePrivateNetworking ? 'Disabled' : 'Enabled'
+ }
+}
+var logAnalyticsWorkspaceResourceId = useExistingLogAnalytics
+ ? existingLogAnalyticsWorkspaceId
+ : (enableMonitoring ? logAnalyticsWorkspace!.outputs.resourceId : '')
+
+// ========== Application Insights ========== //
+var applicationInsightsResourceName = 'appi-${solutionSuffix}'
+module applicationInsights 'br/public:avm/res/insights/component:0.7.1' = if (enableMonitoring) {
+ name: take('avm.res.insights.component.${applicationInsightsResourceName}', 64)
+ params: {
+ name: applicationInsightsResourceName
+ tags: tags
+ location: solutionLocation
+ enableTelemetry: enableTelemetry
+ retentionInDays: 365
+ kind: 'web'
+ disableIpMasking: false
+ flowType: 'Bluefield'
+ workspaceResourceId: logAnalyticsWorkspaceResourceId
+ diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }]
+ }
+}
+
+// ========== User Assigned Identity ========== //
+var userAssignedIdentityResourceName = 'id-${solutionSuffix}'
+module userAssignedIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.4.3' = {
+ name: take('avm.res.managed-identity.user-assigned-identity.${userAssignedIdentityResourceName}', 64)
+ params: {
+ name: userAssignedIdentityResourceName
+ location: solutionLocation
+ tags: tags
+ enableTelemetry: enableTelemetry
+ }
+}
+
+// ========== Virtual Network and Networking Components ========== //
+module virtualNetwork 'modules/virtualNetwork.bicep' = if (enablePrivateNetworking) {
+ name: take('module.virtualNetwork.${solutionSuffix}', 64)
+ params: {
+ vnetName: 'vnet-${solutionSuffix}'
+ vnetLocation: solutionLocation
+ vnetAddressPrefixes: ['10.0.0.0/20']
+ tags: tags
+ logAnalyticsWorkspaceId: logAnalyticsWorkspaceResourceId
+ enableTelemetry: enableTelemetry
+ resourceSuffix: solutionSuffix
+ deployBastionAndJumpbox: deployBastionAndJumpbox
+ }
+ dependsOn: enableMonitoring ? [logAnalyticsWorkspace] : []
+}
+
+// ========== Private DNS Zones ========== //
+// Only create DNS zones for resources that need private endpoints:
+// - Cognitive Services (for AI Services)
+// - OpenAI (for Azure OpenAI endpoints)
+// - Blob Storage
+// - Cosmos DB (Documents)
+var privateDnsZones = [
+ 'privatelink.cognitiveservices.azure.com'
+ 'privatelink.openai.azure.com'
+ 'privatelink.blob.${environment().suffixes.storage}'
+ 'privatelink.documents.azure.com'
+]
+
+var dnsZoneIndex = {
+ cognitiveServices: 0
+ openAI: 1
+ storageBlob: 2
+ cosmosDB: 3
+}
+
+@batchSize(5)
+module avmPrivateDnsZones 'br/public:avm/res/network/private-dns-zone:0.8.0' = [
+ for (zone, i) in privateDnsZones: if (enablePrivateNetworking) {
+ name: take('avm.res.network.private-dns-zone.${replace(zone, '.', '-')}', 64)
+ params: {
+ name: zone
+ tags: tags
+ enableTelemetry: enableTelemetry
+ virtualNetworkLinks: [
+ {
+ virtualNetworkResourceId: enablePrivateNetworking ? virtualNetwork!.outputs.resourceId : ''
+ registrationEnabled: false
+ }
+ ]
+ }
+ }
+]
+
+// ========== AI Foundry: AI Services ========== //
+module aiFoundryAiServices 'br/public:avm/res/cognitive-services/account:0.14.0' = if (!useExistingAiFoundryAiProject) {
+ name: take('avm.res.cognitive-services.account.${aiFoundryAiServicesResourceName}', 64)
+ params: {
+ name: aiFoundryAiServicesResourceName
+ location: aiServiceLocation
+ tags: tags
+ sku: 'S0'
+ kind: 'AIServices'
+ disableLocalAuth: true
+ allowProjectManagement: true
+ customSubDomainName: aiFoundryAiServicesResourceName
+ restrictOutboundNetworkAccess: false
+ deployments: [
+ for deployment in aiFoundryAiServicesModelDeployment: {
+ name: deployment.name
+ model: {
+ format: deployment.format
+ name: deployment.name
+ version: deployment.version
+ }
+ raiPolicyName: deployment.raiPolicyName
+ sku: {
+ name: deployment.sku.name
+ capacity: deployment.sku.capacity
+ }
+ }
+ ]
+ networkAcls: {
+ defaultAction: 'Allow'
+ virtualNetworkRules: []
+ ipRules: []
+ }
+ managedIdentities: {
+ userAssignedResourceIds: [userAssignedIdentity!.outputs.resourceId]
+ }
+ roleAssignments: [
+ {
+ roleDefinitionIdOrName: '53ca6127-db72-4b80-b1b0-d745d6d5456d' // Azure AI User
+ principalId: userAssignedIdentity.outputs.principalId
+ principalType: 'ServicePrincipal'
+ }
+ {
+ roleDefinitionIdOrName: '64702f94-c441-49e6-a78b-ef80e0188fee' // Azure AI Developer
+ principalId: userAssignedIdentity.outputs.principalId
+ principalType: 'ServicePrincipal'
+ }
+ {
+ roleDefinitionIdOrName: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' // Cognitive Services OpenAI User
+ principalId: userAssignedIdentity.outputs.principalId
+ principalType: 'ServicePrincipal'
+ }
+ {
+ roleDefinitionIdOrName: '53ca6127-db72-4b80-b1b0-d745d6d5456d' // Azure AI User for deployer
+ principalId: deployer().objectId
+ }
+ ]
+ diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null
+ publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled'
+ // Note: Private endpoint is created separately to avoid timing issues with model deployments
+ }
+}
+
+// Create private endpoint for AI Services AFTER the account is fully provisioned
+module aiServicesPrivateEndpoint 'br/public:avm/res/network/private-endpoint:0.11.0' = if (!useExistingAiFoundryAiProject && enablePrivateNetworking) {
+ name: take('pep-ai-services-${aiFoundryAiServicesResourceName}', 64)
+ params: {
+ name: 'pep-${aiFoundryAiServicesResourceName}'
+ location: solutionLocation
+ tags: tags
+ subnetResourceId: virtualNetwork!.outputs.pepsSubnetResourceId
+ privateLinkServiceConnections: [
+ {
+ name: 'pep-${aiFoundryAiServicesResourceName}'
+ properties: {
+ privateLinkServiceId: aiFoundryAiServices!.outputs.resourceId
+ groupIds: ['account']
+ }
+ }
+ ]
+ privateDnsZoneGroup: {
+ privateDnsZoneGroupConfigs: [
+ {
+ name: 'cognitiveservices'
+ privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.cognitiveServices]!.outputs.resourceId
+ }
+ {
+ name: 'openai'
+ privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.openAI]!.outputs.resourceId
+ }
+ ]
+ }
+ }
+}
+
+module aiFoundryAiServicesProject 'modules/ai-project.bicep' = if (!useExistingAiFoundryAiProject) {
+ name: take('module.ai-project.${aiFoundryAiProjectResourceName}', 64)
+ params: {
+ name: aiFoundryAiProjectResourceName
+ location: aiServiceLocation
+ tags: tags
+ desc: aiFoundryAiProjectDescription
+ aiServicesName: aiFoundryAiServicesResourceName
+ azureExistingAIProjectResourceId: azureExistingAIProjectResourceId
+ }
+ dependsOn: [
+ aiFoundryAiServices
+ ]
+}
+
+var aiFoundryAiProjectEndpoint = useExistingAiFoundryAiProject
+ ? 'https://${aiFoundryAiServicesResourceName}.services.ai.azure.com/api/projects/${aiFoundryAiProjectResourceName}'
+ : aiFoundryAiServicesProject!.outputs.apiEndpoint
+
+// ========== AI Search ========== //
+module aiSearch 'br/public:avm/res/search/search-service:0.11.1' = {
+ name: take('avm.res.search.search-service.${aiSearchName}', 64)
+ params: {
+ name: aiSearchName
+ location: solutionLocation
+ tags: tags
+ enableTelemetry: enableTelemetry
+ sku: enableScalability ? 'standard' : 'basic'
+ replicaCount: enableRedundancy ? 2 : 1
+ partitionCount: 1
+ hostingMode: 'default'
+ semanticSearch: 'free'
+ authOptions: {
+ aadOrApiKey: {
+ aadAuthFailureMode: 'http401WithBearerChallenge'
+ }
+ }
+ disableLocalAuth: false
+ roleAssignments: [
+ {
+ principalId: userAssignedIdentity.outputs.principalId
+ roleDefinitionIdOrName: 'Search Index Data Contributor'
+ principalType: 'ServicePrincipal'
+ }
+ {
+ principalId: userAssignedIdentity.outputs.principalId
+ roleDefinitionIdOrName: 'Search Service Contributor'
+ principalType: 'ServicePrincipal'
+ }
+ ]
+ diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null
+ // AI Search remains publicly accessible - accessed from ACI via managed identity
+ publicNetworkAccess: 'Enabled'
+ }
+}
+
+// ========== AI Search Connection to AI Services ========== //
+resource aiSearchFoundryConnection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview' = if (!useExistingAiFoundryAiProject) {
+ name: '${aiFoundryAiServicesResourceName}/${aiFoundryAiProjectResourceName}/${aiSearchConnectionName}'
+ properties: {
+ category: 'CognitiveSearch'
+ target: 'https://${aiSearchName}.search.windows.net'
+ authType: 'AAD'
+ isSharedToAll: true
+ metadata: {
+ ApiVersion: '2024-05-01-preview'
+ ResourceId: aiSearch.outputs.resourceId
+ }
+ }
+ dependsOn: [aiFoundryAiServicesProject]
+}
+
+// ========== Storage Account ========== //
+var storageAccountName = 'st${solutionSuffix}'
+var productImagesContainer = 'product-images'
+var generatedImagesContainer = 'generated-images'
+var dataContainer = 'data'
+
+module storageAccount 'br/public:avm/res/storage/storage-account:0.30.0' = {
+ name: take('avm.res.storage.storage-account.${storageAccountName}', 64)
+ params: {
+ name: storageAccountName
+ location: solutionLocation
+ skuName: enableRedundancy ? 'Standard_ZRS' : 'Standard_LRS'
+ managedIdentities: { systemAssigned: true }
+ minimumTlsVersion: 'TLS1_2'
+ enableTelemetry: enableTelemetry
+ tags: tags
+ accessTier: 'Hot'
+ supportsHttpsTrafficOnly: true
+ blobServices: {
+ containerDeleteRetentionPolicyEnabled: true
+ containerDeleteRetentionPolicyDays: 7
+ deleteRetentionPolicyEnabled: true
+ deleteRetentionPolicyDays: 7
+ containers: [
+ {
+ name: productImagesContainer
+ publicAccess: 'None'
+ }
+ {
+ name: generatedImagesContainer
+ publicAccess: 'None'
+ }
+ {
+ name: dataContainer
+ publicAccess: 'None'
+ }
+ ]
+ }
+ roleAssignments: [
+ {
+ principalId: userAssignedIdentity.outputs.principalId
+ roleDefinitionIdOrName: 'Storage Blob Data Contributor'
+ principalType: 'ServicePrincipal'
+ }
+ ]
+ networkAcls: {
+ bypass: 'AzureServices'
+ defaultAction: enablePrivateNetworking ? 'Deny' : 'Allow'
+ }
+ allowBlobPublicAccess: false
+ publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled'
+ privateEndpoints: enablePrivateNetworking ? [
+ {
+ service: 'blob'
+ subnetResourceId: virtualNetwork!.outputs.pepsSubnetResourceId
+ privateDnsZoneGroup: {
+ privateDnsZoneGroupConfigs: [
+ { privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.storageBlob]!.outputs.resourceId }
+ ]
+ }
+ }
+ ] : null
+ diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null
+ }
+}
+
+// ========== Cosmos DB ========== //
+var cosmosDBResourceName = 'cosmos-${solutionSuffix}'
+var cosmosDBDatabaseName = 'content_generation_db'
+var cosmosDBConversationsContainer = 'conversations'
+var cosmosDBProductsContainer = 'products'
+
+module cosmosDB 'br/public:avm/res/document-db/database-account:0.18.0' = {
+ name: take('avm.res.document-db.database-account.${cosmosDBResourceName}', 64)
+ params: {
+ name: 'cosmos-${solutionSuffix}'
+ location: secondaryLocation
+ tags: tags
+ enableTelemetry: enableTelemetry
+ sqlDatabases: [
+ {
+ name: cosmosDBDatabaseName
+ containers: [
+ {
+ name: cosmosDBConversationsContainer
+ paths: [
+ '/userId'
+ ]
+ }
+ {
+ name: cosmosDBProductsContainer
+ paths: [
+ '/category'
+ ]
+ }
+ ]
+ }
+ ]
+ sqlRoleDefinitions: [
+ {
+ roleName: 'contentgen-data-contributor'
+ dataActions: [
+ 'Microsoft.DocumentDB/databaseAccounts/readMetadata'
+ 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*'
+ 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*'
+ ]
+ }
+ ]
+ sqlRoleAssignments: [
+ {
+ principalId: userAssignedIdentity.outputs.principalId
+ roleDefinitionId: '00000000-0000-0000-0000-000000000002' // Built-in Cosmos DB Data Contributor
+ }
+ {
+ principalId: deployer().objectId
+ roleDefinitionId: '00000000-0000-0000-0000-000000000002' // Built-in Cosmos DB Data Contributor to the deployer
+ }
+ ]
+ diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null
+ networkRestrictions: {
+ networkAclBypass: 'AzureServices'
+ publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled'
+ }
+ zoneRedundant: enableRedundancy
+ capabilitiesToAdd: enableRedundancy ? null : ['EnableServerless']
+ enableAutomaticFailover: enableRedundancy
+ failoverLocations: enableRedundancy
+ ? [
+ {
+ failoverPriority: 0
+ isZoneRedundant: true
+ locationName: secondaryLocation
+ }
+ {
+ failoverPriority: 1
+ isZoneRedundant: true
+ locationName: cosmosDbHaLocation
+ }
+ ]
+ : [
+ {
+ locationName: secondaryLocation
+ failoverPriority: 0
+ isZoneRedundant: false
+ }
+ ]
+ privateEndpoints: enablePrivateNetworking ? [
+ {
+ service: 'Sql'
+ subnetResourceId: virtualNetwork!.outputs.pepsSubnetResourceId
+ privateDnsZoneGroup: {
+ privateDnsZoneGroupConfigs: [
+ { privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.cosmosDB]!.outputs.resourceId }
+ ]
+ }
+ }
+ ] : null
+ }
+}
+
+// ========== App Service Plan ========== //
+var webServerFarmResourceName = 'asp-${solutionSuffix}'
+module webServerFarm 'br/public:avm/res/web/serverfarm:0.5.0' = {
+ name: take('avm.res.web.serverfarm.${webServerFarmResourceName}', 64)
+ params: {
+ name: webServerFarmResourceName
+ tags: tags
+ enableTelemetry: enableTelemetry
+ location: solutionLocation
+ reserved: true
+ kind: 'linux'
+ diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null
+ skuName: enableScalability || enableRedundancy ? 'P1v3' : 'B1'
+ skuCapacity: 1
+ zoneRedundant: enableRedundancy ? true : false
+ }
+ scope: resourceGroup(resourceGroup().name)
+}
+
+// ========== Web App ========== //
+var webSiteResourceName = 'app-${solutionSuffix}'
+// Backend URL: Use ACI IP (private or public) or FQDN depending on networking mode
+var aciPrivateIpFallback = '10.0.4.4'
+var aciPublicFqdnFallback = '${containerInstanceName}.${solutionLocation}.azurecontainer.io'
+// For private networking use IP, for public use FQDN
+var aciBackendUrl = enablePrivateNetworking
+ ? 'http://${aciPrivateIpFallback}:8000'
+ : 'http://${aciPublicFqdnFallback}:8000'
+module webSite 'modules/web-sites.bicep' = {
+ name: take('module.web-sites.${webSiteResourceName}', 64)
+ params: {
+ name: webSiteResourceName
+ tags: tags
+ location: solutionLocation
+ kind: 'app,linux,container'
+ serverFarmResourceId: webServerFarm.outputs.resourceId
+ managedIdentities: { userAssignedResourceIds: [userAssignedIdentity!.outputs.resourceId] }
+ siteConfig: {
+ // Frontend container - same for both modes
+ linuxFxVersion: 'DOCKER|${acrResourceName}.azurecr.io/content-gen-app:${imageTag}'
+ minTlsVersion: '1.2'
+ alwaysOn: true
+ ftpsState: 'FtpsOnly'
+ }
+ virtualNetworkSubnetId: enablePrivateNetworking ? virtualNetwork!.outputs.webSubnetResourceId : null
+ configs: concat([
+ {
+ // Frontend container proxies to ACI backend (both modes)
+ name: 'appsettings'
+ properties: {
+ DOCKER_REGISTRY_SERVER_URL: 'https://${acrResourceName}.azurecr.io'
+ BACKEND_URL: aciBackendUrl
+ AZURE_CLIENT_ID: userAssignedIdentity.outputs.clientId
+ }
+ applicationInsightResourceId: enableMonitoring ? applicationInsights!.outputs.resourceId : null
+ }
+ ], enableMonitoring ? [
+ {
+ name: 'logs'
+ properties: {}
+ }
+ ] : [])
+ enableMonitoring: enableMonitoring
+ diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null
+ vnetRouteAllEnabled: enablePrivateNetworking
+ vnetImagePullEnabled: enablePrivateNetworking
+ publicNetworkAccess: 'Enabled'
+ }
+}
+
+// ========== Container Instance (Backend API) ========== //
+var containerInstanceName = 'aci-${solutionSuffix}'
+module containerInstance 'modules/container-instance.bicep' = {
+ name: take('module.container-instance.${containerInstanceName}', 64)
+ params: {
+ name: containerInstanceName
+ location: solutionLocation
+ tags: tags
+ containerImage: '${acrResourceName}.azurecr.io/content-gen-api:${imageTag}'
+ cpu: 2
+ memoryInGB: 4
+ port: 8000
+ // Only pass subnetResourceId when private networking is enabled
+ subnetResourceId: enablePrivateNetworking ? virtualNetwork!.outputs.aciSubnetResourceId : ''
+ registryServer: '${acrResourceName}.azurecr.io'
+ userAssignedIdentityResourceId: userAssignedIdentity.outputs.resourceId
+ enableTelemetry: enableTelemetry
+ environmentVariables: [
+ // Azure OpenAI Settings
+ { name: 'AZURE_OPENAI_ENDPOINT', value: 'https://${aiFoundryAiServicesResourceName}.openai.azure.com/' }
+ { name: 'AZURE_OPENAI_GPT_MODEL', value: gptModelName }
+ { name: 'AZURE_OPENAI_IMAGE_MODEL', value: imageModelConfig[imageModelChoice].name }
+ { name: 'AZURE_OPENAI_GPT_IMAGE_ENDPOINT', value: imageModelChoice != 'none' ? 'https://${aiFoundryAiServicesResourceName}.openai.azure.com/' : '' }
+ { name: 'AZURE_OPENAI_API_VERSION', value: azureOpenaiAPIVersion }
+ // Azure Cosmos DB Settings
+ { name: 'AZURE_COSMOS_ENDPOINT', value: 'https://cosmos-${solutionSuffix}.documents.azure.com:443/' }
+ { name: 'AZURE_COSMOS_DATABASE_NAME', value: cosmosDBDatabaseName }
+ { name: 'AZURE_COSMOS_PRODUCTS_CONTAINER', value: cosmosDBProductsContainer }
+ { name: 'AZURE_COSMOS_CONVERSATIONS_CONTAINER', value: cosmosDBConversationsContainer }
+ // Azure Blob Storage Settings
+ { name: 'AZURE_BLOB_ACCOUNT_NAME', value: storageAccountName }
+ { name: 'AZURE_BLOB_PRODUCT_IMAGES_CONTAINER', value: productImagesContainer }
+ { name: 'AZURE_BLOB_GENERATED_IMAGES_CONTAINER', value: generatedImagesContainer }
+ // Azure AI Search Settings
+ { name: 'AZURE_AI_SEARCH_ENDPOINT', value: 'https://${aiSearchName}.search.windows.net' }
+ { name: 'AZURE_AI_SEARCH_PRODUCTS_INDEX', value: azureSearchIndex }
+ { name: 'AZURE_AI_SEARCH_IMAGE_INDEX', value: 'product-images' }
+ // App Settings
+ { name: 'AZURE_CLIENT_ID', value: userAssignedIdentity.outputs.clientId }
+ { name: 'PORT', value: '8000' }
+ { name: 'WORKERS', value: '4' }
+ { name: 'RUNNING_IN_PRODUCTION', value: 'true' }
+ // Azure AI Foundry Settings
+ { name: 'USE_FOUNDRY', value: useFoundryMode ? 'true' : 'false' }
+ { name: 'AZURE_AI_PROJECT_ENDPOINT', value: aiFoundryAiProjectEndpoint }
+ { name: 'AZURE_AI_MODEL_DEPLOYMENT_NAME', value: gptModelName }
+ { name: 'AZURE_AI_IMAGE_MODEL_DEPLOYMENT', value: imageModelConfig[imageModelChoice].name }
+ ]
+ }
+}
+
+// ========== Outputs ========== //
+@description('Contains App Service Name')
+output APP_SERVICE_NAME string = webSite.outputs.name
+
+@description('Contains WebApp URL')
+output WEB_APP_URL string = 'https://${webSite.outputs.name}.azurewebsites.net'
+
+@description('Contains Storage Account Name')
+output AZURE_BLOB_ACCOUNT_NAME string = storageAccount.outputs.name
+
+@description('Contains Product Images Container')
+output AZURE_BLOB_PRODUCT_IMAGES_CONTAINER string = productImagesContainer
+
+@description('Contains Generated Images Container')
+output AZURE_BLOB_GENERATED_IMAGES_CONTAINER string = generatedImagesContainer
+
+@description('Contains CosmosDB Account Name')
+output COSMOSDB_ACCOUNT_NAME string = cosmosDB.outputs.name
+
+@description('Contains CosmosDB Endpoint URL')
+output AZURE_COSMOS_ENDPOINT string = 'https://cosmos-${solutionSuffix}.documents.azure.com:443/'
+
+@description('Contains CosmosDB Database Name')
+output AZURE_COSMOS_DATABASE_NAME string = cosmosDBDatabaseName
+
+@description('Contains CosmosDB Products Container')
+output AZURE_COSMOS_PRODUCTS_CONTAINER string = cosmosDBProductsContainer
+
+@description('Contains CosmosDB Conversations Container')
+output AZURE_COSMOS_CONVERSATIONS_CONTAINER string = cosmosDBConversationsContainer
+
+@description('Contains Resource Group Name')
+output RESOURCE_GROUP_NAME string = resourceGroup().name
+
+@description('Contains AI Foundry Name')
+output AI_FOUNDRY_NAME string = aiFoundryAiProjectResourceName
+
+@description('Contains AI Foundry RG Name')
+output AI_FOUNDRY_RG_NAME string = aiFoundryAiServicesResourceGroupName
+
+@description('Contains AI Foundry Resource ID')
+output AI_FOUNDRY_RESOURCE_ID string = useExistingAiFoundryAiProject ? '' : aiFoundryAiServices!.outputs.resourceId
+
+@description('Contains existing AI project resource ID.')
+output AZURE_EXISTING_AI_PROJECT_RESOURCE_ID string = azureExistingAIProjectResourceId
+
+@description('Contains AI Search Service Endpoint URL')
+output AZURE_AI_SEARCH_ENDPOINT string = 'https://${aiSearch.outputs.name}.search.windows.net/'
+
+@description('Contains AI Search Service Name')
+output AI_SEARCH_SERVICE_NAME string = aiSearch.outputs.name
+
+@description('Contains AI Search Product Index')
+output AZURE_AI_SEARCH_PRODUCTS_INDEX string = azureSearchIndex
+
+@description('Contains AI Search Image Index')
+output AZURE_AI_SEARCH_IMAGE_INDEX string = 'product-images'
+
+@description('Contains Azure OpenAI endpoint URL')
+output AZURE_OPENAI_ENDPOINT string = 'https://${aiFoundryAiServicesResourceName}.openai.azure.com/'
+
+@description('Contains GPT Model')
+output AZURE_OPENAI_GPT_MODEL string = gptModelName
+
+@description('Contains Image Model (empty if none selected)')
+output AZURE_OPENAI_IMAGE_MODEL string = imageModelConfig[imageModelChoice].name
+
+@description('Contains Azure OpenAI GPT/Image endpoint URL (empty if no image model selected)')
+output AZURE_OPENAI_GPT_IMAGE_ENDPOINT string = imageModelChoice != 'none' ? 'https://${aiFoundryAiServicesResourceName}.openai.azure.com/' : ''
+
+@description('Contains Azure OpenAI API Version')
+output AZURE_OPENAI_API_VERSION string = azureOpenaiAPIVersion
+
+@description('Contains OpenAI Resource')
+output AZURE_OPENAI_RESOURCE string = aiFoundryAiServicesResourceName
+
+@description('Contains AI Agent Endpoint')
+output AZURE_AI_AGENT_ENDPOINT string = aiFoundryAiProjectEndpoint
+
+@description('Contains AI Agent API Version')
+output AZURE_AI_AGENT_API_VERSION string = azureAiAgentApiVersion
+
+@description('Contains Application Insights Connection String')
+output AZURE_APPLICATION_INSIGHTS_CONNECTION_STRING string = (enableMonitoring && !useExistingLogAnalytics) ? applicationInsights!.outputs.connectionString : ''
+
+@description('Contains the location used for AI Services deployment')
+output AI_SERVICE_LOCATION string = aiServiceLocation
+
+@description('Contains Container Instance Name')
+output CONTAINER_INSTANCE_NAME string = containerInstance.outputs.name
+
+@description('Contains Container Instance IP Address')
+output CONTAINER_INSTANCE_IP string = containerInstance.outputs.ipAddress
+
+@description('Contains Container Instance FQDN (only for non-private networking)')
+output CONTAINER_INSTANCE_FQDN string = enablePrivateNetworking ? '' : containerInstance.outputs.fqdn
+
+@description('Contains ACR Name')
+output ACR_NAME string = acrResourceName
+
+@description('Contains flag for Azure AI Foundry usage')
+output USE_FOUNDRY bool = useFoundryMode ? true : false
+
+@description('Contains Azure AI Project Endpoint')
+output AZURE_AI_PROJECT_ENDPOINT string = aiFoundryAiProjectEndpoint
+
+@description('Contains Azure AI Model Deployment Name')
+output AZURE_AI_MODEL_DEPLOYMENT_NAME string = gptModelName
+
+@description('Contains Azure AI Image Model Deployment Name (empty if none selected)')
+output AZURE_AI_IMAGE_MODEL_DEPLOYMENT string = imageModelConfig[imageModelChoice].name
diff --git a/content-gen/infra/main.json b/content-gen/infra/main.json
new file mode 100644
index 000000000..82f7c9b9a
--- /dev/null
+++ b/content-gen/infra/main.json
@@ -0,0 +1,33749 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "12629934330443935294"
+ },
+ "name": "Intelligent Content Generation Accelerator",
+ "description": "Solution Accelerator for multimodal marketing content generation using Microsoft Agent Framework.\n"
+ },
+ "parameters": {
+ "solutionName": {
+ "type": "string",
+ "defaultValue": "contentgen",
+ "minLength": 3,
+ "maxLength": 15,
+ "metadata": {
+ "description": "Optional. A unique application/solution name for all resources in this deployment."
+ }
+ },
+ "solutionUniqueText": {
+ "type": "string",
+ "defaultValue": "[substring(uniqueString(subscription().id, resourceGroup().name, parameters('solutionName')), 0, 5)]",
+ "maxLength": 5,
+ "metadata": {
+ "description": "Optional. A unique text value for the solution."
+ }
+ },
+ "location": {
+ "type": "string",
+ "allowedValues": [
+ "australiaeast",
+ "centralus",
+ "eastasia",
+ "eastus",
+ "eastus2",
+ "japaneast",
+ "northeurope",
+ "southeastasia",
+ "swedencentral",
+ "uksouth",
+ "westus",
+ "westus3"
+ ],
+ "metadata": {
+ "azd": {
+ "type": "location"
+ },
+ "description": "Required. Azure region for all services."
+ }
+ },
+ "secondaryLocation": {
+ "type": "string",
+ "defaultValue": "uksouth",
+ "minLength": 3,
+ "metadata": {
+ "description": "Optional. Secondary location for databases creation."
+ }
+ },
+ "azureAiServiceLocation": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. Location for AI deployments. If not specified, uses the main location."
+ }
+ },
+ "gptModelDeploymentType": {
+ "type": "string",
+ "defaultValue": "GlobalStandard",
+ "allowedValues": [
+ "Standard",
+ "GlobalStandard"
+ ],
+ "minLength": 1,
+ "metadata": {
+ "description": "Optional. GPT model deployment type."
+ }
+ },
+ "gptModelName": {
+ "type": "string",
+ "defaultValue": "gpt-5.1",
+ "minLength": 1,
+ "metadata": {
+ "description": "Optional. Name of the GPT model to deploy."
+ }
+ },
+ "gptModelVersion": {
+ "type": "string",
+ "defaultValue": "2025-11-13",
+ "metadata": {
+ "description": "Optional. Version of the GPT model to deploy."
+ }
+ },
+ "imageModelChoice": {
+ "type": "string",
+ "defaultValue": "gpt-image-1",
+ "allowedValues": [
+ "gpt-image-1",
+ "gpt-image-1.5",
+ "dall-e-3",
+ "none"
+ ],
+ "metadata": {
+ "description": "Optional. Image model to deploy: gpt-image-1, gpt-image-1.5, dall-e-3, or none to skip."
+ }
+ },
+ "azureOpenaiAPIVersion": {
+ "type": "string",
+ "defaultValue": "2025-01-01-preview",
+ "metadata": {
+ "description": "Optional. API version for Azure OpenAI service."
+ }
+ },
+ "azureAiAgentApiVersion": {
+ "type": "string",
+ "defaultValue": "2025-05-01",
+ "metadata": {
+ "description": "Optional. API version for Azure AI Agent service."
+ }
+ },
+ "gptModelCapacity": {
+ "type": "int",
+ "defaultValue": 150,
+ "minValue": 10,
+ "metadata": {
+ "description": "Optional. AI model deployment token capacity."
+ }
+ },
+ "dalleModelCapacity": {
+ "type": "int",
+ "defaultValue": 1,
+ "minValue": 1,
+ "metadata": {
+ "description": "Optional. Image model deployment capacity (RPM)."
+ }
+ },
+ "existingLogAnalyticsWorkspaceId": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. Existing Log Analytics Workspace Resource ID."
+ }
+ },
+ "azureExistingAIProjectResourceId": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. Resource ID of an existing Foundry project."
+ }
+ },
+ "deployBastionAndJumpbox": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Deploy Azure Bastion and Jumpbox VM for private network administration."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "defaultValue": {},
+ "metadata": {
+ "description": "Optional. The tags to apply to all deployed Azure resources."
+ }
+ },
+ "enableMonitoring": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Enable monitoring for applicable resources (WAF-aligned)."
+ }
+ },
+ "useFoundryMode": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable Azure AI Foundry mode for multi-agent orchestration."
+ }
+ },
+ "enableScalability": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Enable scalability for applicable resources (WAF-aligned)."
+ }
+ },
+ "enableRedundancy": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Enable redundancy for applicable resources (WAF-aligned)."
+ }
+ },
+ "enablePrivateNetworking": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Enable private networking for applicable resources (WAF-aligned)."
+ }
+ },
+ "acrName": {
+ "type": "string",
+ "defaultValue": "contentgencontainerreg",
+ "metadata": {
+ "description": "Required. The existing Container Registry name (without .azurecr.io). Must contain pre-built images: content-gen-app and content-gen-api."
+ }
+ },
+ "imageTag": {
+ "type": "string",
+ "defaultValue": "latest",
+ "metadata": {
+ "description": "Optional. Image Tag."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry."
+ }
+ },
+ "createdBy": {
+ "type": "string",
+ "defaultValue": "[if(contains(deployer(), 'userPrincipalName'), split(deployer().userPrincipalName, '@')[0], deployer().objectId)]",
+ "metadata": {
+ "description": "Optional. Created by user name."
+ }
+ }
+ },
+ "variables": {
+ "solutionLocation": "[if(empty(parameters('location')), resourceGroup().location, parameters('location'))]",
+ "validAiServiceRegions": [
+ "australiaeast",
+ "eastus",
+ "eastus2",
+ "francecentral",
+ "japaneast",
+ "koreacentral",
+ "swedencentral",
+ "switzerlandnorth",
+ "uaenorth",
+ "uksouth",
+ "westus",
+ "westus3"
+ ],
+ "aiServiceRegionFallback": {
+ "australiaeast": "australiaeast",
+ "australiasoutheast": "australiaeast",
+ "brazilsouth": "eastus2",
+ "canadacentral": "eastus2",
+ "canadaeast": "eastus2",
+ "centralindia": "uksouth",
+ "centralus": "eastus2",
+ "eastasia": "japaneast",
+ "eastus": "eastus",
+ "eastus2": "eastus2",
+ "francecentral": "francecentral",
+ "germanywestcentral": "swedencentral",
+ "japaneast": "japaneast",
+ "japanwest": "japaneast",
+ "koreacentral": "koreacentral",
+ "koreasouth": "koreacentral",
+ "northcentralus": "eastus2",
+ "northeurope": "swedencentral",
+ "norwayeast": "swedencentral",
+ "polandcentral": "swedencentral",
+ "qatarcentral": "uaenorth",
+ "southafricanorth": "uksouth",
+ "southcentralus": "eastus2",
+ "southeastasia": "japaneast",
+ "southindia": "uksouth",
+ "swedencentral": "swedencentral",
+ "switzerlandnorth": "switzerlandnorth",
+ "uaenorth": "uaenorth",
+ "uksouth": "uksouth",
+ "ukwest": "uksouth",
+ "westcentralus": "westus",
+ "westeurope": "swedencentral",
+ "westindia": "uksouth",
+ "westus": "westus",
+ "westus2": "westus",
+ "westus3": "westus3"
+ },
+ "requestedAiLocation": "[if(empty(parameters('azureAiServiceLocation')), variables('solutionLocation'), parameters('azureAiServiceLocation'))]",
+ "aiServiceLocation": "[if(contains(variables('validAiServiceRegions'), variables('requestedAiLocation')), variables('requestedAiLocation'), coalesce(tryGet(variables('aiServiceRegionFallback'), variables('solutionLocation')), 'eastus2'))]",
+ "acrResourceName": "[parameters('acrName')]",
+ "solutionSuffix": "[toLower(trim(replace(replace(replace(replace(replace(replace(format('{0}{1}', parameters('solutionName'), parameters('solutionUniqueText')), '-', ''), '_', ''), '.', ''), '/', ''), ' ', ''), '*', '')))]",
+ "cosmosDbZoneRedundantHaRegionPairs": {
+ "australiaeast": "uksouth",
+ "centralus": "eastus2",
+ "eastasia": "southeastasia",
+ "eastus": "centralus",
+ "eastus2": "centralus",
+ "japaneast": "australiaeast",
+ "northeurope": "westeurope",
+ "southeastasia": "eastasia",
+ "uksouth": "westeurope",
+ "westus": "westus3",
+ "westus3": "westus"
+ },
+ "cosmosDbHaLocation": "[coalesce(tryGet(variables('cosmosDbZoneRedundantHaRegionPairs'), resourceGroup().location), parameters('secondaryLocation'))]",
+ "replicaRegionPairs": {
+ "australiaeast": "australiasoutheast",
+ "centralus": "westus",
+ "eastasia": "japaneast",
+ "eastus": "centralus",
+ "eastus2": "centralus",
+ "japaneast": "eastasia",
+ "northeurope": "westeurope",
+ "southeastasia": "eastasia",
+ "uksouth": "westeurope",
+ "westus": "westus3",
+ "westus3": "westus"
+ },
+ "replicaLocation": "[coalesce(tryGet(variables('replicaRegionPairs'), resourceGroup().location), parameters('secondaryLocation'))]",
+ "azureSearchIndex": "products",
+ "aiSearchName": "[format('srch-{0}', variables('solutionSuffix'))]",
+ "aiSearchConnectionName": "[format('foundry-search-connection-{0}', variables('solutionSuffix'))]",
+ "useExistingLogAnalytics": "[not(empty(parameters('existingLogAnalyticsWorkspaceId')))]",
+ "useExistingAiFoundryAiProject": "[not(empty(parameters('azureExistingAIProjectResourceId')))]",
+ "aiFoundryAiServicesResourceGroupName": "[if(variables('useExistingAiFoundryAiProject'), split(parameters('azureExistingAIProjectResourceId'), '/')[4], format('rg-{0}', variables('solutionSuffix')))]",
+ "aiFoundryAiServicesResourceName": "[if(variables('useExistingAiFoundryAiProject'), split(parameters('azureExistingAIProjectResourceId'), '/')[8], format('aif-{0}', variables('solutionSuffix')))]",
+ "aiFoundryAiProjectResourceName": "[if(variables('useExistingAiFoundryAiProject'), split(parameters('azureExistingAIProjectResourceId'), '/')[10], format('proj-{0}', variables('solutionSuffix')))]",
+ "baseModelDeployments": [
+ {
+ "format": "OpenAI",
+ "name": "[parameters('gptModelName')]",
+ "model": "[parameters('gptModelName')]",
+ "sku": {
+ "name": "[parameters('gptModelDeploymentType')]",
+ "capacity": "[parameters('gptModelCapacity')]"
+ },
+ "version": "[parameters('gptModelVersion')]",
+ "raiPolicyName": "Microsoft.Default"
+ }
+ ],
+ "imageModelConfig": {
+ "gpt-image-1": {
+ "name": "gpt-image-1",
+ "version": "2025-04-15",
+ "sku": "GlobalStandard"
+ },
+ "gpt-image-1.5": {
+ "name": "gpt-image-1.5",
+ "version": "2025-12-16",
+ "sku": "GlobalStandard"
+ },
+ "dall-e-3": {
+ "name": "dall-e-3",
+ "version": "3.0",
+ "sku": "Standard"
+ },
+ "none": {
+ "name": "",
+ "version": "",
+ "sku": ""
+ }
+ },
+ "imageModelDeployment": "[if(not(equals(parameters('imageModelChoice'), 'none')), createArray(createObject('format', 'OpenAI', 'name', variables('imageModelConfig')[parameters('imageModelChoice')].name, 'model', variables('imageModelConfig')[parameters('imageModelChoice')].name, 'sku', createObject('name', variables('imageModelConfig')[parameters('imageModelChoice')].sku, 'capacity', parameters('dalleModelCapacity')), 'version', variables('imageModelConfig')[parameters('imageModelChoice')].version, 'raiPolicyName', 'Microsoft.Default')), createArray())]",
+ "aiFoundryAiServicesModelDeployment": "[concat(variables('baseModelDeployments'), variables('imageModelDeployment'))]",
+ "aiFoundryAiProjectDescription": "Content Generation AI Foundry Project",
+ "logAnalyticsWorkspaceResourceName": "[format('log-{0}', variables('solutionSuffix'))]",
+ "applicationInsightsResourceName": "[format('appi-{0}', variables('solutionSuffix'))]",
+ "userAssignedIdentityResourceName": "[format('id-{0}', variables('solutionSuffix'))]",
+ "privateDnsZones": [
+ "privatelink.cognitiveservices.azure.com",
+ "privatelink.openai.azure.com",
+ "[format('privatelink.blob.{0}', environment().suffixes.storage)]",
+ "privatelink.documents.azure.com"
+ ],
+ "dnsZoneIndex": {
+ "cognitiveServices": 0,
+ "openAI": 1,
+ "storageBlob": 2,
+ "cosmosDB": 3
+ },
+ "storageAccountName": "[format('st{0}', variables('solutionSuffix'))]",
+ "productImagesContainer": "product-images",
+ "generatedImagesContainer": "generated-images",
+ "dataContainer": "data",
+ "cosmosDBResourceName": "[format('cosmos-{0}', variables('solutionSuffix'))]",
+ "cosmosDBDatabaseName": "content_generation_db",
+ "cosmosDBConversationsContainer": "conversations",
+ "cosmosDBProductsContainer": "products",
+ "webServerFarmResourceName": "[format('asp-{0}', variables('solutionSuffix'))]",
+ "webSiteResourceName": "[format('app-{0}', variables('solutionSuffix'))]",
+ "aciPrivateIpFallback": "10.0.4.4",
+ "aciPublicFqdnFallback": "[format('{0}.{1}.azurecontainer.io', variables('containerInstanceName'), variables('solutionLocation'))]",
+ "aciBackendUrl": "[if(parameters('enablePrivateNetworking'), format('http://{0}:8000', variables('aciPrivateIpFallback')), format('http://{0}:8000', variables('aciPublicFqdnFallback')))]",
+ "containerInstanceName": "[format('aci-{0}', variables('solutionSuffix'))]"
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.ptn.sa-contentgen.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name, variables('solutionLocation')), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "resourceGroupTags": {
+ "type": "Microsoft.Resources/tags",
+ "apiVersion": "2021-04-01",
+ "name": "default",
+ "properties": {
+ "tags": "[shallowMerge(createArray(resourceGroup().tags, parameters('tags'), createObject('TemplateName', 'ContentGen', 'Type', if(parameters('enablePrivateNetworking'), 'WAF', 'Non-WAF'), 'CreatedBy', parameters('createdBy'))))]"
+ }
+ },
+ "aiSearchFoundryConnection": {
+ "condition": "[not(variables('useExistingAiFoundryAiProject'))]",
+ "type": "Microsoft.CognitiveServices/accounts/projects/connections",
+ "apiVersion": "2025-04-01-preview",
+ "name": "[format('{0}/{1}/{2}', variables('aiFoundryAiServicesResourceName'), variables('aiFoundryAiProjectResourceName'), variables('aiSearchConnectionName'))]",
+ "properties": {
+ "category": "CognitiveSearch",
+ "target": "[format('https://{0}.search.windows.net', variables('aiSearchName'))]",
+ "authType": "AAD",
+ "isSharedToAll": true,
+ "metadata": {
+ "ApiVersion": "2024-05-01-preview",
+ "ResourceId": "[reference('aiSearch').outputs.resourceId.value]"
+ }
+ },
+ "dependsOn": [
+ "aiFoundryAiServicesProject",
+ "aiSearch"
+ ]
+ },
+ "logAnalyticsWorkspace": {
+ "condition": "[and(parameters('enableMonitoring'), not(variables('useExistingLogAnalytics')))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[take(format('avm.res.operational-insights.workspace.{0}', variables('logAnalyticsWorkspaceResourceName')), 64)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[variables('logAnalyticsWorkspaceResourceName')]"
+ },
+ "tags": {
+ "value": "[parameters('tags')]"
+ },
+ "location": {
+ "value": "[variables('solutionLocation')]"
+ },
+ "enableTelemetry": {
+ "value": "[parameters('enableTelemetry')]"
+ },
+ "skuName": {
+ "value": "PerGB2018"
+ },
+ "dataRetention": {
+ "value": 365
+ },
+ "features": {
+ "value": {
+ "enableLogAccessUsingOnlyResourcePermissions": true
+ }
+ },
+ "diagnosticSettings": {
+ "value": [
+ {
+ "useThisWorkspace": true
+ }
+ ]
+ },
+ "dailyQuotaGb": "[if(parameters('enableRedundancy'), createObject('value', 10), createObject('value', null()))]",
+ "replication": "[if(parameters('enableRedundancy'), createObject('value', createObject('enabled', true(), 'location', variables('replicaLocation'))), createObject('value', null()))]",
+ "publicNetworkAccessForIngestion": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Disabled'), createObject('value', 'Enabled'))]",
+ "publicNetworkAccessForQuery": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Disabled'), createObject('value', 'Enabled'))]"
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "3322296220118676013"
+ },
+ "name": "Log Analytics Workspaces",
+ "description": "This module deploys a Log Analytics Workspace."
+ },
+ "definitions": {
+ "diagnosticSettingType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of diagnostic setting."
+ }
+ },
+ "logCategoriesAndGroups": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here."
+ }
+ },
+ "categoryGroup": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection."
+ }
+ },
+ "metricCategories": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection."
+ }
+ },
+ "logAnalyticsDestinationType": {
+ "type": "string",
+ "allowedValues": [
+ "AzureDiagnostics",
+ "Dedicated"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type."
+ }
+ },
+ "useThisWorkspace": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Instead of using an external reference, use the deployed instance as the target for its diagnostic settings. If set to `true`, the `workspaceResourceId` property is ignored."
+ }
+ },
+ "workspaceResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "storageAccountResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "eventHubAuthorizationRuleResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to."
+ }
+ },
+ "eventHubName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "marketplacePartnerResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs."
+ }
+ }
+ }
+ },
+ "gallerySolutionType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the solution.\nFor solutions authored by Microsoft, the name must be in the pattern: `SolutionType(WorkspaceName)`, for example: `AntiMalware(contoso-Logs)`.\nFor solutions authored by third parties, the name should be in the pattern: `SolutionType[WorkspaceName]`, for example `MySolution[contoso-Logs]`.\nThe solution type is case-sensitive."
+ }
+ },
+ "plan": {
+ "$ref": "#/definitions/solutionPlanType",
+ "metadata": {
+ "description": "Required. Plan for solution object supported by the OperationsManagement resource provider."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "Properties of the gallery solutions to be created in the log analytics workspace."
+ }
+ },
+ "storageInsightsConfigType": {
+ "type": "object",
+ "properties": {
+ "storageAccountResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Resource ID of the storage account to be linked."
+ }
+ },
+ "containers": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The names of the blob containers that the workspace should read."
+ }
+ },
+ "tables": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. List of tables to be read by the workspace."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "Properties of the storage insights configuration."
+ }
+ },
+ "linkedServiceType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the linked service. E.g., 'Automation' for an automation account, or 'Cluster' for a Log Analytics Cluster."
+ }
+ },
+ "resourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource id of the resource that will be linked to the workspace. This should be used for linking resources which require read access (e.g., Automation Accounts)."
+ }
+ },
+ "writeAccessResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource id of the resource that will be linked to the workspace. This should be used for linking resources which require write access (e.g., Log Analytics Clusters)."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "Properties of the linked service."
+ }
+ },
+ "linkedStorageAccountType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the link."
+ }
+ },
+ "storageAccountIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minLength": 1,
+ "metadata": {
+ "description": "Required. Linked storage accounts resources Ids."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "Properties of the linked storage account."
+ }
+ },
+ "savedSearchType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the saved search."
+ }
+ },
+ "etag": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The ETag of the saved search. To override an existing saved search, use \"*\" or specify the current Etag."
+ }
+ },
+ "category": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The category of the saved search. This helps the user to find a saved search faster."
+ }
+ },
+ "displayName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Display name for the search."
+ }
+ },
+ "functionAlias": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The function alias if query serves as a function."
+ }
+ },
+ "functionParameters": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The optional function parameters if query serves as a function. Value should be in the following format: 'param-name1:type1 = default_value1, param-name2:type2 = default_value2'. For more examples and proper syntax please refer to /azure/kusto/query/functions/user-defined-functions."
+ }
+ },
+ "query": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The query expression for the saved search."
+ }
+ },
+ "tags": {
+ "type": "array",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The tags attached to the saved search."
+ }
+ },
+ "version": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The version number of the query language. The current version is 2 and is the default."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "Properties of the saved search."
+ }
+ },
+ "dataExportType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the data export."
+ }
+ },
+ "destination": {
+ "$ref": "#/definitions/destinationType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The destination of the data export."
+ }
+ },
+ "enable": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the data export."
+ }
+ },
+ "tableNames": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. The list of table names to export."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "Properties of the data export."
+ }
+ },
+ "dataSourceType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the data source."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The kind of data source."
+ }
+ },
+ "linkedResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource id of the resource that will be linked to the workspace."
+ }
+ },
+ "eventLogName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the event log to configure when kind is WindowsEvent."
+ }
+ },
+ "eventTypes": {
+ "type": "array",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The event types to configure when kind is WindowsEvent."
+ }
+ },
+ "objectName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of the object to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject."
+ }
+ },
+ "instanceName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of the instance to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject."
+ }
+ },
+ "intervalSeconds": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Interval in seconds to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject."
+ }
+ },
+ "performanceCounters": {
+ "type": "array",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. List of counters to configure when the kind is LinuxPerformanceObject."
+ }
+ },
+ "counterName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Counter name to configure when kind is WindowsPerformanceCounter."
+ }
+ },
+ "state": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. State to configure when kind is IISLogs or LinuxSyslogCollection or LinuxPerformanceCollection."
+ }
+ },
+ "syslogName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. System log to configure when kind is LinuxSyslog."
+ }
+ },
+ "syslogSeverities": {
+ "type": "array",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Severities to configure when kind is LinuxSyslog."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.OperationalInsights/workspaces/dataSources@2025-02-01#properties/tags"
+ },
+ "description": "Optional. Tags to configure in the resource."
+ },
+ "nullable": true
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "Properties of the data source."
+ }
+ },
+ "tableType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the table."
+ }
+ },
+ "plan": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The plan for the table."
+ }
+ },
+ "restoredLogs": {
+ "$ref": "#/definitions/restoredLogsType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The restored logs for the table."
+ }
+ },
+ "schema": {
+ "$ref": "#/definitions/schemaType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The schema for the table."
+ }
+ },
+ "searchResults": {
+ "$ref": "#/definitions/searchResultsType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The search results for the table."
+ }
+ },
+ "retentionInDays": {
+ "type": "int",
+ "nullable": true,
+ "minValue": 4,
+ "maxValue": 730,
+ "metadata": {
+ "description": "Optional. The retention in days for the table. Don't provide to use the default workspace retention."
+ }
+ },
+ "totalRetentionInDays": {
+ "type": "int",
+ "nullable": true,
+ "minValue": 4,
+ "maxValue": 2555,
+ "metadata": {
+ "description": "Optional. The total retention in days for the table. Don't provide use the default table retention."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The role assignments for the table."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "Properties of the custom table."
+ }
+ },
+ "workspaceFeaturesType": {
+ "type": "object",
+ "properties": {
+ "disableLocalAuth": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Disable Non-EntraID based Auth. Default is true."
+ }
+ },
+ "enableDataExport": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Flag that indicate if data should be exported."
+ }
+ },
+ "enableLogAccessUsingOnlyResourcePermissions": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable log access using only resource permissions. Default is false."
+ }
+ },
+ "immediatePurgeDataOn30Days": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Flag that describes if we want to remove the data after 30 days."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "Features of the workspace."
+ }
+ },
+ "workspaceReplicationType": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specifies whether the replication is enabled or not. When true, workspace configuration and data is replicated to the specified location."
+ }
+ },
+ "location": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Conditional. The location to which the workspace is replicated. Required if replication is enabled."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "Replication properties of the workspace."
+ }
+ },
+ "_1.columnType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The column name."
+ }
+ },
+ "type": {
+ "type": "string",
+ "allowedValues": [
+ "boolean",
+ "dateTime",
+ "dynamic",
+ "guid",
+ "int",
+ "long",
+ "real",
+ "string"
+ ],
+ "metadata": {
+ "description": "Required. The column type."
+ }
+ },
+ "dataTypeHint": {
+ "type": "string",
+ "allowedValues": [
+ "armPath",
+ "guid",
+ "ip",
+ "uri"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The column data type logical hint."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The column description."
+ }
+ },
+ "displayName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Column display name."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The parameters of the table column.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "table/main.bicep"
+ }
+ }
+ },
+ "destinationType": {
+ "type": "object",
+ "properties": {
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The destination resource ID."
+ }
+ },
+ "metaData": {
+ "type": "object",
+ "properties": {
+ "eventHubName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Allows to define an Event Hub name. Not applicable when destination is Storage Account."
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The destination metadata."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The data export destination properties.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "data-export/main.bicep"
+ }
+ }
+ },
+ "lockType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the name of lock."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "CanNotDelete",
+ "None",
+ "ReadOnly"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ },
+ "notes": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the notes of the lock."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a lock.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "managedIdentityAllType": {
+ "type": "object",
+ "properties": {
+ "systemAssigned": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enables system assigned managed identity on the resource."
+ }
+ },
+ "userAssignedResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID(s) to assign to the resource. Required if a user assigned identity is used for encryption."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "restoredLogsType": {
+ "type": "object",
+ "properties": {
+ "sourceTable": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The table to restore data from."
+ }
+ },
+ "startRestoreTime": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The timestamp to start the restore from (UTC)."
+ }
+ },
+ "endRestoreTime": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The timestamp to end the restore by (UTC)."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The parameters of the restore operation that initiated the table.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "table/main.bicep"
+ }
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "schemaType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The table name."
+ }
+ },
+ "columns": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/_1.columnType"
+ },
+ "metadata": {
+ "description": "Required. A list of table custom columns."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The table description."
+ }
+ },
+ "displayName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The table display name."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The table schema.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "table/main.bicep"
+ }
+ }
+ },
+ "searchResultsType": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The search job query."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The search description."
+ }
+ },
+ "limit": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Limit the search job to return up to specified number of rows."
+ }
+ },
+ "startSearchTime": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The timestamp to start the search from (UTC)."
+ }
+ },
+ "endSearchTime": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The timestamp to end the search by (UTC)."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The parameters of the search job that initiated the table.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "table/main.bicep"
+ }
+ }
+ },
+ "solutionPlanType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of the solution to be created.\nFor solutions authored by Microsoft, the name must be in the pattern: `SolutionType(WorkspaceName)`, for example: `AntiMalware(contoso-Logs)`.\nFor solutions authored by third parties, it can be anything.\nThe solution type is case-sensitive.\nIf not provided, the value of the `name` parameter will be used."
+ }
+ },
+ "product": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The product name of the deployed solution.\nFor Microsoft published gallery solution it should be `OMSGallery/{solutionType}`, for example `OMSGallery/AntiMalware`.\nFor a third party solution, it can be anything.\nThis is case sensitive."
+ }
+ },
+ "publisher": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The publisher name of the deployed solution. For Microsoft published gallery solution, it is `Microsoft`, which is the default value."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/operations-management/solution:0.3.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the Log Analytics workspace."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Optional. Location for all resources."
+ }
+ },
+ "skuName": {
+ "type": "string",
+ "defaultValue": "PerGB2018",
+ "allowedValues": [
+ "CapacityReservation",
+ "Free",
+ "LACluster",
+ "PerGB2018",
+ "PerNode",
+ "Premium",
+ "Standalone",
+ "Standard"
+ ],
+ "metadata": {
+ "description": "Optional. The name of the SKU. Must be 'LACluster' to be linked to a Log Analytics cluster."
+ }
+ },
+ "skuCapacityReservationLevel": {
+ "type": "int",
+ "defaultValue": 100,
+ "minValue": 100,
+ "maxValue": 5000,
+ "metadata": {
+ "description": "Optional. The capacity reservation level in GB for this workspace, when CapacityReservation sku is selected. Must be in increments of 100 between 100 and 5000."
+ }
+ },
+ "storageInsightsConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/storageInsightsConfigType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. List of storage accounts to be read by the workspace."
+ }
+ },
+ "linkedServices": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/linkedServiceType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. List of services to be linked."
+ }
+ },
+ "linkedStorageAccounts": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/linkedStorageAccountType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Conditional. List of Storage Accounts to be linked. Required if 'forceCmkForQuery' is set to 'true' and 'savedSearches' is not empty."
+ }
+ },
+ "savedSearches": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/savedSearchType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Kusto Query Language searches to save."
+ }
+ },
+ "dataExports": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/dataExportType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. LAW data export instances to be deployed."
+ }
+ },
+ "dataSources": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/dataSourceType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. LAW data sources to configure."
+ }
+ },
+ "tables": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/tableType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. LAW custom tables to be deployed."
+ }
+ },
+ "gallerySolutions": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/gallerySolutionType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. List of gallerySolutions to be created in the log analytics workspace."
+ }
+ },
+ "onboardWorkspaceToSentinel": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Onboard the Log Analytics Workspace to Sentinel. Requires 'SecurityInsights' solution to be in gallerySolutions."
+ }
+ },
+ "dataRetention": {
+ "type": "int",
+ "defaultValue": 365,
+ "minValue": 0,
+ "maxValue": 730,
+ "metadata": {
+ "description": "Optional. Number of days data will be retained for."
+ }
+ },
+ "dailyQuotaGb": {
+ "type": "int",
+ "defaultValue": -1,
+ "minValue": -1,
+ "metadata": {
+ "description": "Optional. The workspace daily quota for ingestion."
+ }
+ },
+ "defaultDataCollectionRuleResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID of the default Data Collection Rule to use for this workspace. Note: the default DCR is not applicable on workspace creation and the workspace must be listed as a destination in the DCR."
+ }
+ },
+ "publicNetworkAccessForIngestion": {
+ "type": "string",
+ "defaultValue": "Enabled",
+ "allowedValues": [
+ "Enabled",
+ "Disabled",
+ "SecuredByPerimeter"
+ ],
+ "metadata": {
+ "description": "Optional. The network access type for accessing Log Analytics ingestion."
+ }
+ },
+ "publicNetworkAccessForQuery": {
+ "type": "string",
+ "defaultValue": "Enabled",
+ "allowedValues": [
+ "Enabled",
+ "Disabled",
+ "SecuredByPerimeter"
+ ],
+ "metadata": {
+ "description": "Optional. The network access type for accessing Log Analytics query."
+ }
+ },
+ "managedIdentities": {
+ "$ref": "#/definitions/managedIdentityAllType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The managed identity definition for this resource. Only one type of identity is supported: system-assigned or user-assigned, but not both."
+ }
+ },
+ "features": {
+ "$ref": "#/definitions/workspaceFeaturesType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The workspace features."
+ }
+ },
+ "replication": {
+ "$ref": "#/definitions/workspaceReplicationType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The workspace replication properties."
+ }
+ },
+ "diagnosticSettings": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/diagnosticSettingType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The diagnostic settings of the service."
+ }
+ },
+ "forceCmkForQuery": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Indicates whether customer managed storage is mandatory for query management."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The lock settings of the service."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.OperationalInsights/workspaces@2025-02-01#properties/tags"
+ },
+ "description": "Optional. Tags of the resource."
+ },
+ "nullable": true
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "enableReferencedModulesTelemetry": false,
+ "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]",
+ "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), 'SystemAssigned', if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', 'None')), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]",
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Log Analytics Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293')]",
+ "Log Analytics Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '73c42c96-874c-492b-b04d-ab87d138a893')]",
+ "Monitoring Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '749f88d5-cbae-40b8-bcfc-e573ddc772fa')]",
+ "Monitoring Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '43d0d8ad-25c7-4714-9337-8ba259a9fe05')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "Security Admin": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'fb1c8493-542b-48eb-b624-b4c8fea62acd')]",
+ "Security Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '39bc4728-0917-49c7-9d2c-d95423bc2eb4')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.operationalinsights-workspace.{0}.{1}', replace('0.14.2', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "logAnalyticsWorkspace": {
+ "type": "Microsoft.OperationalInsights/workspaces",
+ "apiVersion": "2025-07-01",
+ "name": "[parameters('name')]",
+ "location": "[parameters('location')]",
+ "tags": "[parameters('tags')]",
+ "properties": {
+ "features": {
+ "searchVersion": 1,
+ "enableLogAccessUsingOnlyResourcePermissions": "[coalesce(tryGet(parameters('features'), 'enableLogAccessUsingOnlyResourcePermissions'), false())]",
+ "disableLocalAuth": "[coalesce(tryGet(parameters('features'), 'disableLocalAuth'), true())]",
+ "enableDataExport": "[tryGet(parameters('features'), 'enableDataExport')]",
+ "immediatePurgeDataOn30Days": "[tryGet(parameters('features'), 'immediatePurgeDataOn30Days')]"
+ },
+ "sku": {
+ "name": "[parameters('skuName')]",
+ "capacityReservationLevel": "[if(equals(parameters('skuName'), 'CapacityReservation'), parameters('skuCapacityReservationLevel'), null())]"
+ },
+ "retentionInDays": "[parameters('dataRetention')]",
+ "workspaceCapping": {
+ "dailyQuotaGb": "[parameters('dailyQuotaGb')]"
+ },
+ "publicNetworkAccessForIngestion": "[parameters('publicNetworkAccessForIngestion')]",
+ "publicNetworkAccessForQuery": "[parameters('publicNetworkAccessForQuery')]",
+ "forceCmkForQuery": "[parameters('forceCmkForQuery')]",
+ "replication": "[parameters('replication')]",
+ "defaultDataCollectionRuleResourceId": "[parameters('defaultDataCollectionRuleResourceId')]"
+ },
+ "identity": "[variables('identity')]"
+ },
+ "logAnalyticsWorkspace_diagnosticSettings": {
+ "copy": {
+ "name": "logAnalyticsWorkspace_diagnosticSettings",
+ "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]"
+ },
+ "type": "Microsoft.Insights/diagnosticSettings",
+ "apiVersion": "2021-05-01-preview",
+ "scope": "[format('Microsoft.OperationalInsights/workspaces/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]",
+ "properties": {
+ "copy": [
+ {
+ "name": "metrics",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]",
+ "input": {
+ "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]",
+ "timeGrain": null
+ }
+ },
+ {
+ "name": "logs",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]",
+ "input": {
+ "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]",
+ "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]"
+ }
+ }
+ ],
+ "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]",
+ "workspaceId": "[if(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'useThisWorkspace'), false()), resourceId('Microsoft.OperationalInsights/workspaces', parameters('name')), tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId'))]",
+ "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]",
+ "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]",
+ "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]",
+ "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]"
+ },
+ "dependsOn": [
+ "logAnalyticsWorkspace"
+ ]
+ },
+ "logAnalyticsWorkspace_sentinelOnboarding": {
+ "condition": "[and(not(empty(filter(coalesce(parameters('gallerySolutions'), createArray()), lambda('item', startsWith(lambdaVariables('item').name, 'SecurityInsights'))))), parameters('onboardWorkspaceToSentinel'))]",
+ "type": "Microsoft.SecurityInsights/onboardingStates",
+ "apiVersion": "2025-09-01",
+ "scope": "[format('Microsoft.OperationalInsights/workspaces/{0}', parameters('name'))]",
+ "name": "default",
+ "properties": {},
+ "dependsOn": [
+ "logAnalyticsWorkspace"
+ ]
+ },
+ "logAnalyticsWorkspace_lock": {
+ "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]",
+ "type": "Microsoft.Authorization/locks",
+ "apiVersion": "2020-05-01",
+ "scope": "[format('Microsoft.OperationalInsights/workspaces/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]",
+ "properties": {
+ "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]",
+ "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]"
+ },
+ "dependsOn": [
+ "logAnalyticsWorkspace"
+ ]
+ },
+ "logAnalyticsWorkspace_roleAssignments": {
+ "copy": {
+ "name": "logAnalyticsWorkspace_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.OperationalInsights/workspaces/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.OperationalInsights/workspaces', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "logAnalyticsWorkspace"
+ ]
+ },
+ "logAnalyticsWorkspace_storageInsightConfigs": {
+ "copy": {
+ "name": "logAnalyticsWorkspace_storageInsightConfigs",
+ "count": "[length(coalesce(parameters('storageInsightsConfigs'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-LAW-StorageInsightsConfig-{1}', uniqueString(subscription().id, resourceGroup().id, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "logAnalyticsWorkspaceName": {
+ "value": "[parameters('name')]"
+ },
+ "containers": {
+ "value": "[tryGet(coalesce(parameters('storageInsightsConfigs'), createArray())[copyIndex()], 'containers')]"
+ },
+ "tables": {
+ "value": "[tryGet(coalesce(parameters('storageInsightsConfigs'), createArray())[copyIndex()], 'tables')]"
+ },
+ "storageAccountResourceId": {
+ "value": "[coalesce(parameters('storageInsightsConfigs'), createArray())[copyIndex()].storageAccountResourceId]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "15555486835943827858"
+ },
+ "name": "Log Analytics Workspace Storage Insight Configs",
+ "description": "This module deploys a Log Analytics Workspace Storage Insight Config."
+ },
+ "parameters": {
+ "logAnalyticsWorkspaceName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "defaultValue": "[format('{0}-stinsconfig', last(split(parameters('storageAccountResourceId'), '/')))]",
+ "metadata": {
+ "description": "Optional. The name of the storage insights config."
+ }
+ },
+ "storageAccountResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The Azure Resource Manager ID of the storage account resource."
+ }
+ },
+ "containers": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The names of the blob containers that the workspace should read."
+ }
+ },
+ "tables": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The names of the Azure tables that the workspace should read."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.OperationalInsights/workspaces/storageInsightConfigs@2025-02-01#properties/tags"
+ },
+ "description": "Optional. Tags to configure in the resource."
+ },
+ "nullable": true
+ }
+ },
+ "resources": {
+ "storageAccount": {
+ "existing": true,
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2024-01-01",
+ "name": "[last(split(parameters('storageAccountResourceId'), '/'))]"
+ },
+ "workspace": {
+ "existing": true,
+ "type": "Microsoft.OperationalInsights/workspaces",
+ "apiVersion": "2025-02-01",
+ "name": "[parameters('logAnalyticsWorkspaceName')]"
+ },
+ "storageinsightconfig": {
+ "type": "Microsoft.OperationalInsights/workspaces/storageInsightConfigs",
+ "apiVersion": "2025-02-01",
+ "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]",
+ "tags": "[parameters('tags')]",
+ "properties": {
+ "containers": "[parameters('containers')]",
+ "tables": "[parameters('tables')]",
+ "storageAccount": {
+ "id": "[parameters('storageAccountResourceId')]",
+ "key": "[listKeys('storageAccount', '2024-01-01').keys[0].value]"
+ }
+ }
+ }
+ },
+ "outputs": {
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed storage insights configuration."
+ },
+ "value": "[resourceId('Microsoft.OperationalInsights/workspaces/storageInsightConfigs', parameters('logAnalyticsWorkspaceName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group where the storage insight configuration is deployed."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the storage insights configuration."
+ },
+ "value": "[parameters('name')]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "logAnalyticsWorkspace"
+ ]
+ },
+ "logAnalyticsWorkspace_linkedServices": {
+ "copy": {
+ "name": "logAnalyticsWorkspace_linkedServices",
+ "count": "[length(coalesce(parameters('linkedServices'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-LAW-LinkedService-{1}', uniqueString(subscription().id, resourceGroup().id, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "logAnalyticsWorkspaceName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('linkedServices'), createArray())[copyIndex()].name]"
+ },
+ "resourceId": {
+ "value": "[tryGet(coalesce(parameters('linkedServices'), createArray())[copyIndex()], 'resourceId')]"
+ },
+ "writeAccessResourceId": {
+ "value": "[tryGet(coalesce(parameters('linkedServices'), createArray())[copyIndex()], 'writeAccessResourceId')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "8517561285031465616"
+ },
+ "name": "Log Analytics Workspace Linked Services",
+ "description": "This module deploys a Log Analytics Workspace Linked Service."
+ },
+ "parameters": {
+ "logAnalyticsWorkspaceName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the link."
+ }
+ },
+ "resourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID of the resource that will be linked to the workspace. This should be used for linking resources which require read access."
+ }
+ },
+ "writeAccessResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID of the resource that will be linked to the workspace. This should be used for linking resources which require write access."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.OperationalInsights/workspaces/linkedServices@2025-02-01#properties/tags"
+ },
+ "description": "Optional. Tags to configure in the resource."
+ },
+ "nullable": true
+ }
+ },
+ "resources": {
+ "workspace": {
+ "existing": true,
+ "type": "Microsoft.OperationalInsights/workspaces",
+ "apiVersion": "2025-02-01",
+ "name": "[parameters('logAnalyticsWorkspaceName')]"
+ },
+ "linkedService": {
+ "type": "Microsoft.OperationalInsights/workspaces/linkedServices",
+ "apiVersion": "2025-02-01",
+ "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]",
+ "tags": "[parameters('tags')]",
+ "properties": {
+ "resourceId": "[parameters('resourceId')]",
+ "writeAccessResourceId": "[parameters('writeAccessResourceId')]"
+ }
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed linked service."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed linked service."
+ },
+ "value": "[resourceId('Microsoft.OperationalInsights/workspaces/linkedServices', parameters('logAnalyticsWorkspaceName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group where the linked service is deployed."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "logAnalyticsWorkspace"
+ ]
+ },
+ "logAnalyticsWorkspace_linkedStorageAccounts": {
+ "copy": {
+ "name": "logAnalyticsWorkspace_linkedStorageAccounts",
+ "count": "[length(coalesce(parameters('linkedStorageAccounts'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-LAW-LinkedStorageAccount-{1}', uniqueString(subscription().id, resourceGroup().id, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "logAnalyticsWorkspaceName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('linkedStorageAccounts'), createArray())[copyIndex()].name]"
+ },
+ "storageAccountIds": {
+ "value": "[coalesce(parameters('linkedStorageAccounts'), createArray())[copyIndex()].storageAccountIds]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "3889914477453955601"
+ },
+ "name": "Log Analytics Workspace Linked Storage Accounts",
+ "description": "This module deploys a Log Analytics Workspace Linked Storage Account."
+ },
+ "parameters": {
+ "logAnalyticsWorkspaceName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "allowedValues": [
+ "Query",
+ "Alerts",
+ "CustomLogs",
+ "AzureWatson"
+ ],
+ "metadata": {
+ "description": "Required. Name of the link."
+ }
+ },
+ "storageAccountIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minLength": 1,
+ "metadata": {
+ "description": "Required. Linked storage accounts resources Ids."
+ }
+ }
+ },
+ "resources": {
+ "workspace": {
+ "existing": true,
+ "type": "Microsoft.OperationalInsights/workspaces",
+ "apiVersion": "2025-02-01",
+ "name": "[parameters('logAnalyticsWorkspaceName')]"
+ },
+ "linkedStorageAccount": {
+ "type": "Microsoft.OperationalInsights/workspaces/linkedStorageAccounts",
+ "apiVersion": "2025-02-01",
+ "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]",
+ "properties": {
+ "storageAccountIds": "[parameters('storageAccountIds')]"
+ }
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed linked storage account."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed linked storage account."
+ },
+ "value": "[resourceId('Microsoft.OperationalInsights/workspaces/linkedStorageAccounts', parameters('logAnalyticsWorkspaceName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group where the linked storage account is deployed."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "logAnalyticsWorkspace"
+ ]
+ },
+ "logAnalyticsWorkspace_savedSearches": {
+ "copy": {
+ "name": "logAnalyticsWorkspace_savedSearches",
+ "count": "[length(coalesce(parameters('savedSearches'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-LAW-SavedSearch-{1}', uniqueString(subscription().id, resourceGroup().id, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "logAnalyticsWorkspaceName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[format('{0}{1}', coalesce(parameters('savedSearches'), createArray())[copyIndex()].name, uniqueString(subscription().id, resourceGroup().id))]"
+ },
+ "etag": {
+ "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'etag')]"
+ },
+ "displayName": {
+ "value": "[coalesce(parameters('savedSearches'), createArray())[copyIndex()].displayName]"
+ },
+ "category": {
+ "value": "[coalesce(parameters('savedSearches'), createArray())[copyIndex()].category]"
+ },
+ "query": {
+ "value": "[coalesce(parameters('savedSearches'), createArray())[copyIndex()].query]"
+ },
+ "functionAlias": {
+ "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'functionAlias')]"
+ },
+ "functionParameters": {
+ "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'functionParameters')]"
+ },
+ "tags": {
+ "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'tags')]"
+ },
+ "version": {
+ "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'version')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "3727577253995784529"
+ },
+ "name": "Log Analytics Workspace Saved Searches",
+ "description": "This module deploys a Log Analytics Workspace Saved Search."
+ },
+ "parameters": {
+ "logAnalyticsWorkspaceName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the saved search."
+ }
+ },
+ "displayName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Display name for the search."
+ }
+ },
+ "category": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Query category."
+ }
+ },
+ "query": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Kusto Query to be stored."
+ }
+ },
+ "tags": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.OperationalInsights/workspaces/savedSearches@2025-02-01#properties/properties/properties/tags"
+ },
+ "description": "Optional. Tags to configure in the resource."
+ },
+ "nullable": true
+ },
+ "functionAlias": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. The function alias if query serves as a function."
+ }
+ },
+ "functionParameters": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. The optional function parameters if query serves as a function. Value should be in the following format: \"param-name1:type1 = default_value1, param-name2:type2 = default_value2\". For more examples and proper syntax please refer to /azure/kusto/query/functions/user-defined-functions."
+ }
+ },
+ "version": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The version number of the query language."
+ }
+ },
+ "etag": {
+ "type": "string",
+ "defaultValue": "*",
+ "metadata": {
+ "description": "Optional. The ETag of the saved search. To override an existing saved search, use \"*\" or specify the current Etag."
+ }
+ }
+ },
+ "resources": {
+ "workspace": {
+ "existing": true,
+ "type": "Microsoft.OperationalInsights/workspaces",
+ "apiVersion": "2025-02-01",
+ "name": "[parameters('logAnalyticsWorkspaceName')]"
+ },
+ "savedSearch": {
+ "type": "Microsoft.OperationalInsights/workspaces/savedSearches",
+ "apiVersion": "2025-02-01",
+ "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]",
+ "properties": {
+ "etag": "[parameters('etag')]",
+ "tags": "[coalesce(parameters('tags'), createArray())]",
+ "displayName": "[parameters('displayName')]",
+ "category": "[parameters('category')]",
+ "query": "[parameters('query')]",
+ "functionAlias": "[parameters('functionAlias')]",
+ "functionParameters": "[parameters('functionParameters')]",
+ "version": "[parameters('version')]"
+ }
+ }
+ },
+ "outputs": {
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed saved search."
+ },
+ "value": "[resourceId('Microsoft.OperationalInsights/workspaces/savedSearches', parameters('logAnalyticsWorkspaceName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group where the saved search is deployed."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed saved search."
+ },
+ "value": "[parameters('name')]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "logAnalyticsWorkspace",
+ "logAnalyticsWorkspace_linkedStorageAccounts"
+ ]
+ },
+ "logAnalyticsWorkspace_dataExports": {
+ "copy": {
+ "name": "logAnalyticsWorkspace_dataExports",
+ "count": "[length(coalesce(parameters('dataExports'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-LAW-DataExport-{1}', uniqueString(subscription().id, resourceGroup().id, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "workspaceName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('dataExports'), createArray())[copyIndex()].name]"
+ },
+ "destination": {
+ "value": "[tryGet(coalesce(parameters('dataExports'), createArray())[copyIndex()], 'destination')]"
+ },
+ "enable": {
+ "value": "[tryGet(coalesce(parameters('dataExports'), createArray())[copyIndex()], 'enable')]"
+ },
+ "tableNames": {
+ "value": "[tryGet(coalesce(parameters('dataExports'), createArray())[copyIndex()], 'tableNames')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "6434695407713682713"
+ },
+ "name": "Log Analytics Workspace Data Exports",
+ "description": "This module deploys a Log Analytics Workspace Data Export."
+ },
+ "definitions": {
+ "destinationType": {
+ "type": "object",
+ "properties": {
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The destination resource ID."
+ }
+ },
+ "metaData": {
+ "type": "object",
+ "properties": {
+ "eventHubName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Allows to define an Event Hub name. Not applicable when destination is Storage Account."
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The destination metadata."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The data export destination properties."
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "minLength": 4,
+ "maxLength": 63,
+ "metadata": {
+ "description": "Required. The data export rule name."
+ }
+ },
+ "workspaceName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent workspaces. Required if the template is used in a standalone deployment."
+ }
+ },
+ "destination": {
+ "$ref": "#/definitions/destinationType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Destination properties."
+ }
+ },
+ "enable": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Active when enabled."
+ }
+ },
+ "tableNames": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minLength": 1,
+ "metadata": {
+ "description": "Required. An array of tables to export, for example: ['Heartbeat', 'SecurityEvent']."
+ }
+ }
+ },
+ "resources": {
+ "workspace": {
+ "existing": true,
+ "type": "Microsoft.OperationalInsights/workspaces",
+ "apiVersion": "2025-02-01",
+ "name": "[parameters('workspaceName')]"
+ },
+ "dataExport": {
+ "type": "Microsoft.OperationalInsights/workspaces/dataExports",
+ "apiVersion": "2025-02-01",
+ "name": "[format('{0}/{1}', parameters('workspaceName'), parameters('name'))]",
+ "properties": {
+ "destination": "[parameters('destination')]",
+ "enable": "[parameters('enable')]",
+ "tableNames": "[parameters('tableNames')]"
+ }
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the data export."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the data export."
+ },
+ "value": "[resourceId('Microsoft.OperationalInsights/workspaces/dataExports', parameters('workspaceName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the data export was created in."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "logAnalyticsWorkspace"
+ ]
+ },
+ "logAnalyticsWorkspace_dataSources": {
+ "copy": {
+ "name": "logAnalyticsWorkspace_dataSources",
+ "count": "[length(coalesce(parameters('dataSources'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-LAW-DataSource-{1}', uniqueString(subscription().id, resourceGroup().id, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "logAnalyticsWorkspaceName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('dataSources'), createArray())[copyIndex()].name]"
+ },
+ "kind": {
+ "value": "[coalesce(parameters('dataSources'), createArray())[copyIndex()].kind]"
+ },
+ "linkedResourceId": {
+ "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'linkedResourceId')]"
+ },
+ "eventLogName": {
+ "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'eventLogName')]"
+ },
+ "eventTypes": {
+ "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'eventTypes')]"
+ },
+ "objectName": {
+ "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'objectName')]"
+ },
+ "instanceName": {
+ "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'instanceName')]"
+ },
+ "intervalSeconds": {
+ "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'intervalSeconds')]"
+ },
+ "counterName": {
+ "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'counterName')]"
+ },
+ "state": {
+ "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'state')]"
+ },
+ "syslogName": {
+ "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'syslogName')]"
+ },
+ "syslogSeverities": {
+ "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'syslogSeverities')]"
+ },
+ "performanceCounters": {
+ "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'performanceCounters')]"
+ },
+ "tags": {
+ "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'tags')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "8636447638029661740"
+ },
+ "name": "Log Analytics Workspace Datasources",
+ "description": "This module deploys a Log Analytics Workspace Data Source."
+ },
+ "parameters": {
+ "logAnalyticsWorkspaceName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the data source."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "defaultValue": "AzureActivityLog",
+ "allowedValues": [
+ "AzureActivityLog",
+ "WindowsEvent",
+ "WindowsPerformanceCounter",
+ "IISLogs",
+ "LinuxSyslog",
+ "LinuxSyslogCollection",
+ "LinuxPerformanceObject",
+ "LinuxPerformanceCollection"
+ ],
+ "metadata": {
+ "description": "Optional. The kind of the data source."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.OperationalInsights/workspaces/dataSources@2025-02-01#properties/tags"
+ },
+ "description": "Optional. Tags to configure in the resource."
+ },
+ "nullable": true
+ },
+ "linkedResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the resource to be linked."
+ }
+ },
+ "eventLogName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Windows event log name to configure when kind is WindowsEvent."
+ }
+ },
+ "eventTypes": {
+ "type": "array",
+ "defaultValue": [],
+ "metadata": {
+ "description": "Optional. Windows event types to configure when kind is WindowsEvent."
+ }
+ },
+ "objectName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of the object to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject."
+ }
+ },
+ "instanceName": {
+ "type": "string",
+ "defaultValue": "*",
+ "metadata": {
+ "description": "Optional. Name of the instance to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject."
+ }
+ },
+ "intervalSeconds": {
+ "type": "int",
+ "defaultValue": 60,
+ "metadata": {
+ "description": "Optional. Interval in seconds to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject."
+ }
+ },
+ "performanceCounters": {
+ "type": "array",
+ "defaultValue": [],
+ "metadata": {
+ "description": "Optional. List of counters to configure when the kind is LinuxPerformanceObject."
+ }
+ },
+ "counterName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Counter name to configure when kind is WindowsPerformanceCounter."
+ }
+ },
+ "state": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. State to configure when kind is IISLogs or LinuxSyslogCollection or LinuxPerformanceCollection."
+ }
+ },
+ "syslogName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. System log to configure when kind is LinuxSyslog."
+ }
+ },
+ "syslogSeverities": {
+ "type": "array",
+ "defaultValue": [],
+ "metadata": {
+ "description": "Optional. Severities to configure when kind is LinuxSyslog."
+ }
+ }
+ },
+ "resources": {
+ "workspace": {
+ "existing": true,
+ "type": "Microsoft.OperationalInsights/workspaces",
+ "apiVersion": "2025-02-01",
+ "name": "[parameters('logAnalyticsWorkspaceName')]"
+ },
+ "dataSource": {
+ "type": "Microsoft.OperationalInsights/workspaces/dataSources",
+ "apiVersion": "2025-02-01",
+ "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]",
+ "kind": "[parameters('kind')]",
+ "tags": "[parameters('tags')]",
+ "properties": {
+ "linkedResourceId": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'AzureActivityLog')), parameters('linkedResourceId'), null())]",
+ "eventLogName": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'WindowsEvent')), parameters('eventLogName'), null())]",
+ "eventTypes": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'WindowsEvent')), parameters('eventTypes'), null())]",
+ "objectName": "[if(and(not(empty(parameters('kind'))), or(equals(parameters('kind'), 'WindowsPerformanceCounter'), equals(parameters('kind'), 'LinuxPerformanceObject'))), parameters('objectName'), null())]",
+ "instanceName": "[if(and(not(empty(parameters('kind'))), or(equals(parameters('kind'), 'WindowsPerformanceCounter'), equals(parameters('kind'), 'LinuxPerformanceObject'))), parameters('instanceName'), null())]",
+ "intervalSeconds": "[if(and(not(empty(parameters('kind'))), or(equals(parameters('kind'), 'WindowsPerformanceCounter'), equals(parameters('kind'), 'LinuxPerformanceObject'))), parameters('intervalSeconds'), null())]",
+ "counterName": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'WindowsPerformanceCounter')), parameters('counterName'), null())]",
+ "state": "[if(and(not(empty(parameters('kind'))), or(or(equals(parameters('kind'), 'IISLogs'), equals(parameters('kind'), 'LinuxSyslogCollection')), equals(parameters('kind'), 'LinuxPerformanceCollection'))), parameters('state'), null())]",
+ "syslogName": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'LinuxSyslog')), parameters('syslogName'), null())]",
+ "syslogSeverities": "[if(and(not(empty(parameters('kind'))), or(equals(parameters('kind'), 'LinuxSyslog'), equals(parameters('kind'), 'LinuxPerformanceObject'))), parameters('syslogSeverities'), null())]",
+ "performanceCounters": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'LinuxPerformanceObject')), parameters('performanceCounters'), null())]"
+ }
+ }
+ },
+ "outputs": {
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed data source."
+ },
+ "value": "[resourceId('Microsoft.OperationalInsights/workspaces/dataSources', parameters('logAnalyticsWorkspaceName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group where the data source is deployed."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed data source."
+ },
+ "value": "[parameters('name')]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "logAnalyticsWorkspace"
+ ]
+ },
+ "logAnalyticsWorkspace_tables": {
+ "copy": {
+ "name": "logAnalyticsWorkspace_tables",
+ "count": "[length(coalesce(parameters('tables'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-LAW-Table-{1}', uniqueString(subscription().id, resourceGroup().id, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "workspaceName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('tables'), createArray())[copyIndex()].name]"
+ },
+ "plan": {
+ "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'plan')]"
+ },
+ "schema": {
+ "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'schema')]"
+ },
+ "retentionInDays": {
+ "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'retentionInDays')]"
+ },
+ "totalRetentionInDays": {
+ "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'totalRetentionInDays')]"
+ },
+ "restoredLogs": {
+ "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'restoredLogs')]"
+ },
+ "searchResults": {
+ "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'searchResults')]"
+ },
+ "roleAssignments": {
+ "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'roleAssignments')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "13928174215528939368"
+ },
+ "name": "Log Analytics Workspace Tables",
+ "description": "This module deploys a Log Analytics Workspace Table."
+ },
+ "definitions": {
+ "restoredLogsType": {
+ "type": "object",
+ "properties": {
+ "sourceTable": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The table to restore data from."
+ }
+ },
+ "startRestoreTime": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The timestamp to start the restore from (UTC)."
+ }
+ },
+ "endRestoreTime": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The timestamp to end the restore by (UTC)."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The parameters of the restore operation that initiated the table."
+ }
+ },
+ "schemaType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The table name."
+ }
+ },
+ "columns": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/columnType"
+ },
+ "metadata": {
+ "description": "Required. A list of table custom columns."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The table description."
+ }
+ },
+ "displayName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The table display name."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The table schema."
+ }
+ },
+ "columnType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The column name."
+ }
+ },
+ "type": {
+ "type": "string",
+ "allowedValues": [
+ "boolean",
+ "dateTime",
+ "dynamic",
+ "guid",
+ "int",
+ "long",
+ "real",
+ "string"
+ ],
+ "metadata": {
+ "description": "Required. The column type."
+ }
+ },
+ "dataTypeHint": {
+ "type": "string",
+ "allowedValues": [
+ "armPath",
+ "guid",
+ "ip",
+ "uri"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The column data type logical hint."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The column description."
+ }
+ },
+ "displayName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Column display name."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The parameters of the table column."
+ }
+ },
+ "searchResultsType": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The search job query."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The search description."
+ }
+ },
+ "limit": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Limit the search job to return up to specified number of rows."
+ }
+ },
+ "startSearchTime": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The timestamp to start the search from (UTC)."
+ }
+ },
+ "endSearchTime": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The timestamp to end the search by (UTC)."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The parameters of the search job that initiated the table."
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the table."
+ }
+ },
+ "workspaceName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent workspaces. Required if the template is used in a standalone deployment."
+ }
+ },
+ "plan": {
+ "type": "string",
+ "defaultValue": "Analytics",
+ "allowedValues": [
+ "Basic",
+ "Analytics"
+ ],
+ "metadata": {
+ "description": "Optional. Instruct the system how to handle and charge the logs ingested to this table."
+ }
+ },
+ "restoredLogs": {
+ "$ref": "#/definitions/restoredLogsType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Restore parameters."
+ }
+ },
+ "retentionInDays": {
+ "type": "int",
+ "nullable": true,
+ "minValue": 4,
+ "maxValue": 730,
+ "metadata": {
+ "description": "Optional. The table retention in days, between 4 and 730. Don't provide to use the default workspace retention."
+ }
+ },
+ "schema": {
+ "$ref": "#/definitions/schemaType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Table's schema."
+ }
+ },
+ "searchResults": {
+ "$ref": "#/definitions/searchResultsType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Parameters of the search job that initiated this table."
+ }
+ },
+ "totalRetentionInDays": {
+ "type": "int",
+ "nullable": true,
+ "minValue": 4,
+ "maxValue": 2555,
+ "metadata": {
+ "description": "Optional. The table total retention in days, between 4 and 2555. Don't provide use the default table retention."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Log Analytics Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293')]",
+ "Log Analytics Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '73c42c96-874c-492b-b04d-ab87d138a893')]",
+ "Monitoring Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '749f88d5-cbae-40b8-bcfc-e573ddc772fa')]",
+ "Monitoring Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '43d0d8ad-25c7-4714-9337-8ba259a9fe05')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ }
+ },
+ "resources": {
+ "workspace": {
+ "existing": true,
+ "type": "Microsoft.OperationalInsights/workspaces",
+ "apiVersion": "2025-02-01",
+ "name": "[parameters('workspaceName')]"
+ },
+ "table": {
+ "type": "Microsoft.OperationalInsights/workspaces/tables",
+ "apiVersion": "2025-02-01",
+ "name": "[format('{0}/{1}', parameters('workspaceName'), parameters('name'))]",
+ "properties": {
+ "plan": "[parameters('plan')]",
+ "restoredLogs": "[parameters('restoredLogs')]",
+ "retentionInDays": "[coalesce(parameters('retentionInDays'), -1)]",
+ "schema": "[parameters('schema')]",
+ "searchResults": "[parameters('searchResults')]",
+ "totalRetentionInDays": "[coalesce(parameters('totalRetentionInDays'), -1)]"
+ }
+ },
+ "table_roleAssignments": {
+ "copy": {
+ "name": "table_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.OperationalInsights/workspaces/{0}/tables/{1}', parameters('workspaceName'), parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.OperationalInsights/workspaces/tables', parameters('workspaceName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "table"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the table."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the table."
+ },
+ "value": "[resourceId('Microsoft.OperationalInsights/workspaces/tables', parameters('workspaceName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the table was created in."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "logAnalyticsWorkspace"
+ ]
+ },
+ "logAnalyticsWorkspace_solutions": {
+ "copy": {
+ "name": "logAnalyticsWorkspace_solutions",
+ "count": "[length(coalesce(parameters('gallerySolutions'), createArray()))]"
+ },
+ "condition": "[not(empty(parameters('gallerySolutions')))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-LAW-Solution-{1}', uniqueString(subscription().id, resourceGroup().id, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[coalesce(parameters('gallerySolutions'), createArray())[copyIndex()].name]"
+ },
+ "location": {
+ "value": "[parameters('location')]"
+ },
+ "logAnalyticsWorkspaceName": {
+ "value": "[parameters('name')]"
+ },
+ "plan": {
+ "value": "[coalesce(parameters('gallerySolutions'), createArray())[copyIndex()].plan]"
+ },
+ "enableTelemetry": {
+ "value": "[variables('enableReferencedModulesTelemetry')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.32.4.45862",
+ "templateHash": "10255889523646649592"
+ },
+ "name": "Operations Management Solutions",
+ "description": "This module deploys an Operations Management Solution.",
+ "owner": "Azure/module-maintainers"
+ },
+ "definitions": {
+ "solutionPlanType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of the solution to be created.\nFor solutions authored by Microsoft, the name must be in the pattern: `SolutionType(WorkspaceName)`, for example: `AntiMalware(contoso-Logs)`.\nFor solutions authored by third parties, it can be anything.\nThe solution type is case-sensitive.\nIf not provided, the value of the `name` parameter will be used."
+ }
+ },
+ "product": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The product name of the deployed solution.\nFor Microsoft published gallery solution it should be `OMSGallery/{solutionType}`, for example `OMSGallery/AntiMalware`.\nFor a third party solution, it can be anything.\nThis is case sensitive."
+ }
+ },
+ "publisher": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The publisher name of the deployed solution. For Microsoft published gallery solution, it is `Microsoft`, which is the default value."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the solution.\nFor solutions authored by Microsoft, the name must be in the pattern: `SolutionType(WorkspaceName)`, for example: `AntiMalware(contoso-Logs)`.\nFor solutions authored by third parties, the name should be in the pattern: `SolutionType[WorkspaceName]`, for example `MySolution[contoso-Logs]`.\nThe solution type is case-sensitive."
+ }
+ },
+ "plan": {
+ "$ref": "#/definitions/solutionPlanType",
+ "metadata": {
+ "description": "Required. Plan for solution object supported by the OperationsManagement resource provider."
+ }
+ },
+ "logAnalyticsWorkspaceName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the Log Analytics workspace where the solution will be deployed/enabled."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Optional. Location for all resources."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.operationsmanagement-solution.{0}.{1}', replace('0.3.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "logAnalyticsWorkspace": {
+ "existing": true,
+ "type": "Microsoft.OperationalInsights/workspaces",
+ "apiVersion": "2021-06-01",
+ "name": "[parameters('logAnalyticsWorkspaceName')]"
+ },
+ "solution": {
+ "type": "Microsoft.OperationsManagement/solutions",
+ "apiVersion": "2015-11-01-preview",
+ "name": "[parameters('name')]",
+ "location": "[parameters('location')]",
+ "properties": {
+ "workspaceResourceId": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsWorkspaceName'))]"
+ },
+ "plan": {
+ "name": "[coalesce(tryGet(parameters('plan'), 'name'), parameters('name'))]",
+ "promotionCode": "",
+ "product": "[parameters('plan').product]",
+ "publisher": "[coalesce(tryGet(parameters('plan'), 'publisher'), 'Microsoft')]"
+ }
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed solution."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed solution."
+ },
+ "value": "[resourceId('Microsoft.OperationsManagement/solutions', parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group where the solution is deployed."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "location": {
+ "type": "string",
+ "metadata": {
+ "description": "The location the resource was deployed into."
+ },
+ "value": "[reference('solution', '2015-11-01-preview', 'full').location]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "logAnalyticsWorkspace"
+ ]
+ }
+ },
+ "outputs": {
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed log analytics workspace."
+ },
+ "value": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group of the deployed log analytics workspace."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed log analytics workspace."
+ },
+ "value": "[parameters('name')]"
+ },
+ "logAnalyticsWorkspaceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The ID associated with the workspace."
+ },
+ "value": "[reference('logAnalyticsWorkspace').customerId]"
+ },
+ "location": {
+ "type": "string",
+ "metadata": {
+ "description": "The location the resource was deployed into."
+ },
+ "value": "[reference('logAnalyticsWorkspace', '2025-07-01', 'full').location]"
+ },
+ "systemAssignedMIPrincipalId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "The principal ID of the system assigned identity."
+ },
+ "value": "[tryGet(tryGet(reference('logAnalyticsWorkspace', '2025-07-01', 'full'), 'identity'), 'principalId')]"
+ },
+ "primarySharedKey": {
+ "type": "securestring",
+ "metadata": {
+ "description": "The primary shared key of the log analytics workspace."
+ },
+ "value": "[listKeys('logAnalyticsWorkspace', '2025-07-01').primarySharedKey]"
+ },
+ "secondarySharedKey": {
+ "type": "securestring",
+ "metadata": {
+ "description": "The secondary shared key of the log analytics workspace."
+ },
+ "value": "[listKeys('logAnalyticsWorkspace', '2025-07-01').secondarySharedKey]"
+ }
+ }
+ }
+ }
+ },
+ "applicationInsights": {
+ "condition": "[parameters('enableMonitoring')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[take(format('avm.res.insights.component.{0}', variables('applicationInsightsResourceName')), 64)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[variables('applicationInsightsResourceName')]"
+ },
+ "tags": {
+ "value": "[parameters('tags')]"
+ },
+ "location": {
+ "value": "[variables('solutionLocation')]"
+ },
+ "enableTelemetry": {
+ "value": "[parameters('enableTelemetry')]"
+ },
+ "retentionInDays": {
+ "value": 365
+ },
+ "kind": {
+ "value": "web"
+ },
+ "disableIpMasking": {
+ "value": false
+ },
+ "flowType": {
+ "value": "Bluefield"
+ },
+ "workspaceResourceId": "[if(variables('useExistingLogAnalytics'), createObject('value', parameters('existingLogAnalyticsWorkspaceId')), if(parameters('enableMonitoring'), createObject('value', reference('logAnalyticsWorkspace').outputs.resourceId.value), createObject('value', '')))]",
+ "diagnosticSettings": {
+ "value": [
+ {
+ "workspaceResourceId": "[if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), if(parameters('enableMonitoring'), reference('logAnalyticsWorkspace').outputs.resourceId.value, ''))]"
+ }
+ ]
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "17358780145253914698"
+ },
+ "name": "Application Insights",
+ "description": "This component deploys an Application Insights instance."
+ },
+ "definitions": {
+ "diagnosticSettingFullType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the diagnostic setting."
+ }
+ },
+ "logCategoriesAndGroups": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here."
+ }
+ },
+ "categoryGroup": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection."
+ }
+ },
+ "metricCategories": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection."
+ }
+ },
+ "logAnalyticsDestinationType": {
+ "type": "string",
+ "allowedValues": [
+ "AzureDiagnostics",
+ "Dedicated"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type."
+ }
+ },
+ "workspaceResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "storageAccountResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "eventHubAuthorizationRuleResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to."
+ }
+ },
+ "eventHubName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "marketplacePartnerResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "lockType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the name of lock."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "CanNotDelete",
+ "None",
+ "ReadOnly"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ },
+ "notes": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the notes of the lock."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a lock.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the Application Insights."
+ }
+ },
+ "applicationType": {
+ "type": "string",
+ "defaultValue": "web",
+ "allowedValues": [
+ "web",
+ "other"
+ ],
+ "metadata": {
+ "description": "Optional. Application type."
+ }
+ },
+ "workspaceResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Resource ID of the log analytics workspace which the data will be ingested to. This property is required to create an application with this API version. Applications from older versions will not have this property."
+ }
+ },
+ "disableIpMasking": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Disable IP masking. Default value is set to true."
+ }
+ },
+ "disableLocalAuth": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Disable Non-AAD based Auth. Default value is set to false."
+ }
+ },
+ "forceCustomerStorageForProfiler": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Force users to create their own storage account for profiler and debugger."
+ }
+ },
+ "linkedStorageAccountResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Linked storage account resource ID."
+ }
+ },
+ "publicNetworkAccessForIngestion": {
+ "type": "string",
+ "defaultValue": "Enabled",
+ "allowedValues": [
+ "Enabled",
+ "Disabled"
+ ],
+ "metadata": {
+ "description": "Optional. The network access type for accessing Application Insights ingestion. - Enabled or Disabled."
+ }
+ },
+ "publicNetworkAccessForQuery": {
+ "type": "string",
+ "defaultValue": "Enabled",
+ "allowedValues": [
+ "Enabled",
+ "Disabled"
+ ],
+ "metadata": {
+ "description": "Optional. The network access type for accessing Application Insights query. - Enabled or Disabled."
+ }
+ },
+ "retentionInDays": {
+ "type": "int",
+ "defaultValue": 365,
+ "allowedValues": [
+ 30,
+ 60,
+ 90,
+ 120,
+ 180,
+ 270,
+ 365,
+ 550,
+ 730
+ ],
+ "metadata": {
+ "description": "Optional. Retention period in days."
+ }
+ },
+ "samplingPercentage": {
+ "type": "int",
+ "defaultValue": 100,
+ "minValue": 0,
+ "maxValue": 100,
+ "metadata": {
+ "description": "Optional. Percentage of the data produced by the application being monitored that is being sampled for Application Insights telemetry."
+ }
+ },
+ "flowType": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Used by the Application Insights system to determine what kind of flow this component was created by. This is to be set to 'Bluefield' when creating/updating a component via the REST API."
+ }
+ },
+ "requestSource": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Describes what tool created this Application Insights component. Customers using this API should set this to the default 'rest'."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. The kind of application that this component refers to, used to customize UI. This value is a freeform string, values should typically be one of the following: web, ios, other, store, java, phone."
+ }
+ },
+ "immediatePurgeDataOn30Days": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Purge data immediately after 30 days."
+ }
+ },
+ "ingestionMode": {
+ "type": "string",
+ "nullable": true,
+ "allowedValues": [
+ "ApplicationInsights",
+ "ApplicationInsightsWithDiagnosticSettings",
+ "LogAnalytics"
+ ],
+ "metadata": {
+ "description": "Optional. Indicates the flow of the ingestion."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Optional. Location for all Resources."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The lock settings of the service."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Insights/components@2020-02-02#properties/tags"
+ },
+ "description": "Optional. Tags of the resource."
+ },
+ "nullable": true
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ },
+ "diagnosticSettings": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/diagnosticSettingFullType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The diagnostic settings of the service."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]",
+ "Monitoring Metrics Publisher": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '3913510d-42f4-4e42-8a64-420c390055eb')]",
+ "Application Insights Component Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ae349356-3a1b-4a5e-921d-050484c6347e')]",
+ "Application Insights Snapshot Debugger": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '08954f03-6346-4c2e-81c0-ec3a5cfae23b')]",
+ "Monitoring Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '749f88d5-cbae-40b8-bcfc-e573ddc772fa')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.insights-component.{0}.{1}', replace('0.7.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "appInsights": {
+ "type": "Microsoft.Insights/components",
+ "apiVersion": "2020-02-02",
+ "name": "[parameters('name')]",
+ "location": "[parameters('location')]",
+ "tags": "[parameters('tags')]",
+ "kind": "[parameters('kind')]",
+ "properties": {
+ "Application_Type": "[parameters('applicationType')]",
+ "DisableIpMasking": "[parameters('disableIpMasking')]",
+ "DisableLocalAuth": "[parameters('disableLocalAuth')]",
+ "ForceCustomerStorageForProfiler": "[parameters('forceCustomerStorageForProfiler')]",
+ "WorkspaceResourceId": "[parameters('workspaceResourceId')]",
+ "publicNetworkAccessForIngestion": "[parameters('publicNetworkAccessForIngestion')]",
+ "publicNetworkAccessForQuery": "[parameters('publicNetworkAccessForQuery')]",
+ "RetentionInDays": "[parameters('retentionInDays')]",
+ "SamplingPercentage": "[parameters('samplingPercentage')]",
+ "Flow_Type": "[parameters('flowType')]",
+ "Request_Source": "[parameters('requestSource')]",
+ "ImmediatePurgeDataOn30Days": "[parameters('immediatePurgeDataOn30Days')]",
+ "IngestionMode": "[parameters('ingestionMode')]"
+ }
+ },
+ "appInsights_roleAssignments": {
+ "copy": {
+ "name": "appInsights_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Insights/components/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Insights/components', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "appInsights"
+ ]
+ },
+ "appInsights_lock": {
+ "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]",
+ "type": "Microsoft.Authorization/locks",
+ "apiVersion": "2020-05-01",
+ "scope": "[format('Microsoft.Insights/components/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]",
+ "properties": {
+ "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]",
+ "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]"
+ },
+ "dependsOn": [
+ "appInsights"
+ ]
+ },
+ "appInsights_diagnosticSettings": {
+ "copy": {
+ "name": "appInsights_diagnosticSettings",
+ "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]"
+ },
+ "type": "Microsoft.Insights/diagnosticSettings",
+ "apiVersion": "2021-05-01-preview",
+ "scope": "[format('Microsoft.Insights/components/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]",
+ "properties": {
+ "copy": [
+ {
+ "name": "metrics",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]",
+ "input": {
+ "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]",
+ "timeGrain": null
+ }
+ },
+ {
+ "name": "logs",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]",
+ "input": {
+ "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]",
+ "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]"
+ }
+ }
+ ],
+ "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]",
+ "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]",
+ "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]",
+ "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]",
+ "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]",
+ "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]"
+ },
+ "dependsOn": [
+ "appInsights"
+ ]
+ },
+ "linkedStorageAccount": {
+ "condition": "[not(empty(parameters('linkedStorageAccountResourceId')))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-appInsights-linkedStorageAccount', uniqueString(deployment().name, parameters('location')))]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "appInsightsName": {
+ "value": "[parameters('name')]"
+ },
+ "storageAccountResourceId": {
+ "value": "[coalesce(parameters('linkedStorageAccountResourceId'), '')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "5059808225314360251"
+ },
+ "name": "Application Insights Linked Storage Account",
+ "description": "This component deploys an Application Insights Linked Storage Account."
+ },
+ "parameters": {
+ "appInsightsName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Application Insights instance. Required if the template is used in a standalone deployment."
+ }
+ },
+ "storageAccountResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Linked storage account resource ID."
+ }
+ }
+ },
+ "resources": [
+ {
+ "type": "microsoft.insights/components/linkedStorageAccounts",
+ "apiVersion": "2020-03-01-preview",
+ "name": "[format('{0}/{1}', parameters('appInsightsName'), 'ServiceProfiler')]",
+ "properties": {
+ "linkedStorageAccount": "[parameters('storageAccountResourceId')]"
+ }
+ }
+ ],
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the Linked Storage Account."
+ },
+ "value": "ServiceProfiler"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the Linked Storage Account."
+ },
+ "value": "[resourceId('microsoft.insights/components/linkedStorageAccounts', parameters('appInsightsName'), 'ServiceProfiler')]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the agent pool was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "appInsights"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the application insights component."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the application insights component."
+ },
+ "value": "[resourceId('Microsoft.Insights/components', parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the application insights component was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "applicationId": {
+ "type": "string",
+ "metadata": {
+ "description": "The application ID of the application insights component."
+ },
+ "value": "[reference('appInsights').AppId]"
+ },
+ "location": {
+ "type": "string",
+ "metadata": {
+ "description": "The location the resource was deployed into."
+ },
+ "value": "[reference('appInsights', '2020-02-02', 'full').location]"
+ },
+ "instrumentationKey": {
+ "type": "string",
+ "metadata": {
+ "description": "Application Insights Instrumentation key. A read-only value that applications can use to identify the destination for all telemetry sent to Azure Application Insights. This value will be supplied upon construction of each new Application Insights component."
+ },
+ "value": "[reference('appInsights').InstrumentationKey]"
+ },
+ "connectionString": {
+ "type": "string",
+ "metadata": {
+ "description": "Application Insights Connection String."
+ },
+ "value": "[reference('appInsights').ConnectionString]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "logAnalyticsWorkspace"
+ ]
+ },
+ "userAssignedIdentity": {
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[take(format('avm.res.managed-identity.user-assigned-identity.{0}', variables('userAssignedIdentityResourceName')), 64)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[variables('userAssignedIdentityResourceName')]"
+ },
+ "location": {
+ "value": "[variables('solutionLocation')]"
+ },
+ "tags": {
+ "value": "[parameters('tags')]"
+ },
+ "enableTelemetry": {
+ "value": "[parameters('enableTelemetry')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "1856697743030659118"
+ },
+ "name": "User Assigned Identities",
+ "description": "This module deploys a User Assigned Identity."
+ },
+ "definitions": {
+ "federatedIdentityCredentialType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the federated identity credential."
+ }
+ },
+ "audiences": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. The list of audiences that can appear in the issued token."
+ }
+ },
+ "issuer": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The URL of the issuer to be trusted."
+ }
+ },
+ "subject": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The identifier of the external identity."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for the federated identity credential."
+ }
+ },
+ "lockType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the name of lock."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "CanNotDelete",
+ "None",
+ "ReadOnly"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ },
+ "notes": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the notes of the lock."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a lock.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0"
+ }
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the User Assigned Identity."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Optional. Location for all resources."
+ }
+ },
+ "federatedIdentityCredentials": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/federatedIdentityCredentialType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The federated identity credentials list to indicate which token from the external IdP should be trusted by your application. Federated identity credentials are supported on applications only. A maximum of 20 federated identity credentials can be added per application object."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The lock settings of the service."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30#properties/tags"
+ },
+ "description": "Optional. Tags of the resource."
+ },
+ "nullable": true
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Managed Identity Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e40ec5ca-96e0-45a2-b4ff-59039f2c2b59')]",
+ "Managed Identity Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f1a07417-d97a-45cb-824c-7a7467783830')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.managedidentity-userassignedidentity.{0}.{1}', replace('0.4.3', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "userAssignedIdentity": {
+ "type": "Microsoft.ManagedIdentity/userAssignedIdentities",
+ "apiVersion": "2024-11-30",
+ "name": "[parameters('name')]",
+ "location": "[parameters('location')]",
+ "tags": "[parameters('tags')]"
+ },
+ "userAssignedIdentity_lock": {
+ "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]",
+ "type": "Microsoft.Authorization/locks",
+ "apiVersion": "2020-05-01",
+ "scope": "[format('Microsoft.ManagedIdentity/userAssignedIdentities/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]",
+ "properties": {
+ "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]",
+ "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]"
+ },
+ "dependsOn": [
+ "userAssignedIdentity"
+ ]
+ },
+ "userAssignedIdentity_roleAssignments": {
+ "copy": {
+ "name": "userAssignedIdentity_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.ManagedIdentity/userAssignedIdentities/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "userAssignedIdentity"
+ ]
+ },
+ "userAssignedIdentity_federatedIdentityCredentials": {
+ "copy": {
+ "name": "userAssignedIdentity_federatedIdentityCredentials",
+ "count": "[length(coalesce(parameters('federatedIdentityCredentials'), createArray()))]",
+ "mode": "serial",
+ "batchSize": 1
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-UserMSI-FederatedIdentityCred-{1}', uniqueString(subscription().id, resourceGroup().id, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[coalesce(parameters('federatedIdentityCredentials'), createArray())[copyIndex()].name]"
+ },
+ "userAssignedIdentityName": {
+ "value": "[parameters('name')]"
+ },
+ "audiences": {
+ "value": "[coalesce(parameters('federatedIdentityCredentials'), createArray())[copyIndex()].audiences]"
+ },
+ "issuer": {
+ "value": "[coalesce(parameters('federatedIdentityCredentials'), createArray())[copyIndex()].issuer]"
+ },
+ "subject": {
+ "value": "[coalesce(parameters('federatedIdentityCredentials'), createArray())[copyIndex()].subject]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "1387931959101373036"
+ },
+ "name": "User Assigned Identity Federated Identity Credential",
+ "description": "This module deploys a User Assigned Identity Federated Identity Credential."
+ },
+ "parameters": {
+ "userAssignedIdentityName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent user assigned identity. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the secret."
+ }
+ },
+ "audiences": {
+ "type": "array",
+ "metadata": {
+ "description": "Required. The list of audiences that can appear in the issued token. Should be set to api://AzureADTokenExchange for Azure AD. It says what Microsoft identity platform should accept in the aud claim in the incoming token. This value represents Azure AD in your external identity provider and has no fixed value across identity providers - you might need to create a new application registration in your IdP to serve as the audience of this token."
+ }
+ },
+ "issuer": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The URL of the issuer to be trusted. Must match the issuer claim of the external token being exchanged."
+ }
+ },
+ "subject": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The identifier of the external software workload within the external identity provider. Like the audience value, it has no fixed format, as each IdP uses their own - sometimes a GUID, sometimes a colon delimited identifier, sometimes arbitrary strings. The value here must match the sub claim within the token presented to Azure AD."
+ }
+ }
+ },
+ "resources": [
+ {
+ "type": "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials",
+ "apiVersion": "2024-11-30",
+ "name": "[format('{0}/{1}', parameters('userAssignedIdentityName'), parameters('name'))]",
+ "properties": {
+ "audiences": "[parameters('audiences')]",
+ "issuer": "[parameters('issuer')]",
+ "subject": "[parameters('subject')]"
+ }
+ }
+ ],
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the federated identity credential."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the federated identity credential."
+ },
+ "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials', parameters('userAssignedIdentityName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the federated identity credential was created in."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "userAssignedIdentity"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the user assigned identity."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the user assigned identity."
+ },
+ "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name'))]"
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "The principal ID (object ID) of the user assigned identity."
+ },
+ "value": "[reference('userAssignedIdentity').principalId]"
+ },
+ "clientId": {
+ "type": "string",
+ "metadata": {
+ "description": "The client ID (application ID) of the user assigned identity."
+ },
+ "value": "[reference('userAssignedIdentity').clientId]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the user assigned identity was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "location": {
+ "type": "string",
+ "metadata": {
+ "description": "The location the resource was deployed into."
+ },
+ "value": "[reference('userAssignedIdentity', '2024-11-30', 'full').location]"
+ }
+ }
+ }
+ }
+ },
+ "virtualNetwork": {
+ "condition": "[parameters('enablePrivateNetworking')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[take(format('module.virtualNetwork.{0}', variables('solutionSuffix')), 64)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "vnetName": {
+ "value": "[format('vnet-{0}', variables('solutionSuffix'))]"
+ },
+ "vnetLocation": {
+ "value": "[variables('solutionLocation')]"
+ },
+ "vnetAddressPrefixes": {
+ "value": [
+ "10.0.0.0/20"
+ ]
+ },
+ "tags": {
+ "value": "[parameters('tags')]"
+ },
+ "logAnalyticsWorkspaceId": "[if(variables('useExistingLogAnalytics'), createObject('value', parameters('existingLogAnalyticsWorkspaceId')), if(parameters('enableMonitoring'), createObject('value', reference('logAnalyticsWorkspace').outputs.resourceId.value), createObject('value', '')))]",
+ "enableTelemetry": {
+ "value": "[parameters('enableTelemetry')]"
+ },
+ "resourceSuffix": {
+ "value": "[variables('solutionSuffix')]"
+ },
+ "deployBastionAndJumpbox": {
+ "value": "[parameters('deployBastionAndJumpbox')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "16294706585455769047"
+ }
+ },
+ "parameters": {
+ "vnetName": {
+ "type": "string",
+ "metadata": {
+ "description": "Name of the virtual network."
+ }
+ },
+ "vnetLocation": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Azure region to deploy resources."
+ }
+ },
+ "vnetAddressPrefixes": {
+ "type": "array",
+ "defaultValue": [
+ "10.0.0.0/20"
+ ],
+ "metadata": {
+ "description": "Required. An Array of 1 or more IP Address Prefixes for the Virtual Network."
+ }
+ },
+ "deployBastionAndJumpbox": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Deploy Azure Bastion and Jumpbox subnets for VM-based administration."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "defaultValue": {},
+ "metadata": {
+ "description": "Optional. Tags to be applied to the resources."
+ }
+ },
+ "logAnalyticsWorkspaceId": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. The resource ID of the Log Analytics Workspace to send diagnostic logs to."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ },
+ "resourceSuffix": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Suffix for resource naming."
+ }
+ }
+ },
+ "variables": {
+ "coreSubnets": [
+ {
+ "name": "web",
+ "addressPrefixes": [
+ "10.0.0.0/23"
+ ],
+ "delegation": "Microsoft.Web/serverFarms",
+ "networkSecurityGroup": {
+ "name": "nsg-web",
+ "securityRules": [
+ {
+ "name": "AllowHttpsInbound",
+ "properties": {
+ "access": "Allow",
+ "direction": "Inbound",
+ "priority": 100,
+ "protocol": "Tcp",
+ "sourcePortRange": "*",
+ "destinationPortRange": "443",
+ "sourceAddressPrefixes": [
+ "0.0.0.0/0"
+ ],
+ "destinationAddressPrefixes": [
+ "10.0.0.0/23"
+ ]
+ }
+ },
+ {
+ "name": "AllowIntraSubnetTraffic",
+ "properties": {
+ "access": "Allow",
+ "direction": "Inbound",
+ "priority": 200,
+ "protocol": "*",
+ "sourcePortRange": "*",
+ "destinationPortRange": "*",
+ "sourceAddressPrefixes": [
+ "10.0.0.0/23"
+ ],
+ "destinationAddressPrefixes": [
+ "10.0.0.0/23"
+ ]
+ }
+ },
+ {
+ "name": "AllowAzureLoadBalancer",
+ "properties": {
+ "access": "Allow",
+ "direction": "Inbound",
+ "priority": 300,
+ "protocol": "*",
+ "sourcePortRange": "*",
+ "destinationPortRange": "*",
+ "sourceAddressPrefix": "AzureLoadBalancer",
+ "destinationAddressPrefix": "10.0.0.0/23"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "peps",
+ "addressPrefixes": [
+ "10.0.2.0/23"
+ ],
+ "privateEndpointNetworkPolicies": "Disabled",
+ "privateLinkServiceNetworkPolicies": "Disabled",
+ "networkSecurityGroup": {
+ "name": "nsg-peps",
+ "securityRules": []
+ }
+ },
+ {
+ "name": "aci",
+ "addressPrefixes": [
+ "10.0.4.0/24"
+ ],
+ "delegation": "Microsoft.ContainerInstance/containerGroups",
+ "networkSecurityGroup": {
+ "name": "nsg-aci",
+ "securityRules": [
+ {
+ "name": "AllowHttpsInbound",
+ "properties": {
+ "access": "Allow",
+ "direction": "Inbound",
+ "priority": 100,
+ "protocol": "Tcp",
+ "sourcePortRange": "*",
+ "destinationPortRange": "8000",
+ "sourceAddressPrefixes": [
+ "10.0.0.0/20"
+ ],
+ "destinationAddressPrefixes": [
+ "10.0.4.0/24"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ ],
+ "bastionSubnets": "[if(parameters('deployBastionAndJumpbox'), createArray(createObject('name', 'AzureBastionSubnet', 'addressPrefixes', createArray('10.0.10.0/26'), 'networkSecurityGroup', createObject('name', 'nsg-bastion', 'securityRules', createArray(createObject('name', 'AllowGatewayManager', 'properties', createObject('access', 'Allow', 'direction', 'Inbound', 'priority', 2702, 'protocol', '*', 'sourcePortRange', '*', 'destinationPortRange', '443', 'sourceAddressPrefix', 'GatewayManager', 'destinationAddressPrefix', '*')), createObject('name', 'AllowHttpsInBound', 'properties', createObject('access', 'Allow', 'direction', 'Inbound', 'priority', 2703, 'protocol', '*', 'sourcePortRange', '*', 'destinationPortRange', '443', 'sourceAddressPrefix', 'Internet', 'destinationAddressPrefix', '*')), createObject('name', 'AllowSshRdpOutbound', 'properties', createObject('access', 'Allow', 'direction', 'Outbound', 'priority', 100, 'protocol', '*', 'sourcePortRange', '*', 'destinationPortRanges', createArray('22', '3389'), 'sourceAddressPrefix', '*', 'destinationAddressPrefix', 'VirtualNetwork')), createObject('name', 'AllowAzureCloudOutbound', 'properties', createObject('access', 'Allow', 'direction', 'Outbound', 'priority', 110, 'protocol', 'Tcp', 'sourcePortRange', '*', 'destinationPortRange', '443', 'sourceAddressPrefix', '*', 'destinationAddressPrefix', 'AzureCloud'))))), createObject('name', 'jumpbox', 'addressPrefixes', createArray('10.0.12.0/23'), 'networkSecurityGroup', createObject('name', 'nsg-jumpbox', 'securityRules', createArray(createObject('name', 'AllowRdpFromBastion', 'properties', createObject('access', 'Allow', 'direction', 'Inbound', 'priority', 100, 'protocol', 'Tcp', 'sourcePortRange', '*', 'destinationPortRange', '3389', 'sourceAddressPrefixes', createArray('10.0.10.0/26'), 'destinationAddressPrefixes', createArray('10.0.12.0/23'))))))), createArray())]",
+ "vnetSubnets": "[concat(variables('coreSubnets'), variables('bastionSubnets'))]"
+ },
+ "resources": [
+ {
+ "copy": {
+ "name": "nsgs",
+ "count": "[length(variables('vnetSubnets'))]",
+ "mode": "serial",
+ "batchSize": 1
+ },
+ "condition": "[not(empty(tryGet(variables('vnetSubnets')[copyIndex()], 'networkSecurityGroup')))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[take(format('avm.res.network.network-security-group.{0}.{1}', tryGet(variables('vnetSubnets')[copyIndex()], 'networkSecurityGroup', 'name'), parameters('resourceSuffix')), 64)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[format('{0}-{1}', tryGet(variables('vnetSubnets')[copyIndex()], 'networkSecurityGroup', 'name'), parameters('resourceSuffix'))]"
+ },
+ "location": {
+ "value": "[parameters('vnetLocation')]"
+ },
+ "securityRules": {
+ "value": "[tryGet(variables('vnetSubnets')[copyIndex()], 'networkSecurityGroup', 'securityRules')]"
+ },
+ "tags": {
+ "value": "[parameters('tags')]"
+ },
+ "enableTelemetry": {
+ "value": "[parameters('enableTelemetry')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.5.1644",
+ "templateHash": "11959948740766233645"
+ },
+ "name": "Network Security Groups",
+ "description": "This module deploys a Network security Group (NSG)."
+ },
+ "definitions": {
+ "securityRuleType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the security rule."
+ }
+ },
+ "properties": {
+ "type": "object",
+ "properties": {
+ "access": {
+ "type": "string",
+ "allowedValues": [
+ "Allow",
+ "Deny"
+ ],
+ "metadata": {
+ "description": "Required. Whether network traffic is allowed or denied."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the security rule."
+ }
+ },
+ "destinationAddressPrefix": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Optional. The destination address prefix. CIDR or destination IP range. Asterisk \"*\" can also be used to match all source IPs. Default tags such as \"VirtualNetwork\", \"AzureLoadBalancer\" and \"Internet\" can also be used."
+ }
+ },
+ "destinationAddressPrefixes": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The destination address prefixes. CIDR or destination IP ranges."
+ }
+ },
+ "destinationApplicationSecurityGroupResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource IDs of the application security groups specified as destination."
+ }
+ },
+ "destinationPortRange": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The destination port or range. Integer or range between 0 and 65535. Asterisk \"*\" can also be used to match all ports."
+ }
+ },
+ "destinationPortRanges": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The destination port ranges."
+ }
+ },
+ "direction": {
+ "type": "string",
+ "allowedValues": [
+ "Inbound",
+ "Outbound"
+ ],
+ "metadata": {
+ "description": "Required. The direction of the rule. The direction specifies if rule will be evaluated on incoming or outgoing traffic."
+ }
+ },
+ "priority": {
+ "type": "int",
+ "minValue": 100,
+ "maxValue": 4096,
+ "metadata": {
+ "description": "Required. Required. The priority of the rule. The value can be between 100 and 4096. The priority number must be unique for each rule in the collection. The lower the priority number, the higher the priority of the rule."
+ }
+ },
+ "protocol": {
+ "type": "string",
+ "allowedValues": [
+ "*",
+ "Ah",
+ "Esp",
+ "Icmp",
+ "Tcp",
+ "Udp"
+ ],
+ "metadata": {
+ "description": "Required. Network protocol this rule applies to."
+ }
+ },
+ "sourceAddressPrefix": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The CIDR or source IP range. Asterisk \"*\" can also be used to match all source IPs. Default tags such as \"VirtualNetwork\", \"AzureLoadBalancer\" and \"Internet\" can also be used. If this is an ingress rule, specifies where network traffic originates from."
+ }
+ },
+ "sourceAddressPrefixes": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The CIDR or source IP ranges."
+ }
+ },
+ "sourceApplicationSecurityGroupResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource IDs of the application security groups specified as source."
+ }
+ },
+ "sourcePortRange": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The source port or range. Integer or range between 0 and 65535. Asterisk \"*\" can also be used to match all ports."
+ }
+ },
+ "sourcePortRanges": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The source port ranges."
+ }
+ }
+ },
+ "metadata": {
+ "description": "Required. The properties of the security rule."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of a security rule."
+ }
+ },
+ "diagnosticSettingLogsOnlyType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of diagnostic setting."
+ }
+ },
+ "logCategoriesAndGroups": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here."
+ }
+ },
+ "categoryGroup": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection."
+ }
+ },
+ "logAnalyticsDestinationType": {
+ "type": "string",
+ "allowedValues": [
+ "AzureDiagnostics",
+ "Dedicated"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type."
+ }
+ },
+ "workspaceResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "storageAccountResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "eventHubAuthorizationRuleResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to."
+ }
+ },
+ "eventHubName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "marketplacePartnerResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a diagnostic setting. To be used if only logs are supported by the resource provider.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ },
+ "lockType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the name of lock."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "CanNotDelete",
+ "None",
+ "ReadOnly"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ },
+ "notes": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the notes of the lock."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a lock.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0"
+ }
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the Network Security Group."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Optional. Location for all resources."
+ }
+ },
+ "securityRules": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/securityRuleType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of Security Rules to deploy to the Network Security Group. When not provided, an NSG including only the built-in roles will be deployed."
+ }
+ },
+ "flushConnection": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. When enabled, flows created from Network Security Group connections will be re-evaluated when rules are updates. Initial enablement will trigger re-evaluation. Network Security Group connection flushing is not available in all regions."
+ }
+ },
+ "diagnosticSettings": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/diagnosticSettingLogsOnlyType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The diagnostic settings of the service."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The lock settings of the service."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/networkSecurityGroups@2024-07-01#properties/tags"
+ },
+ "description": "Optional. Tags of the NSG resource."
+ },
+ "nullable": true
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.network-networksecuritygroup.{0}.{1}', replace('0.5.2', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "networkSecurityGroup": {
+ "type": "Microsoft.Network/networkSecurityGroups",
+ "apiVersion": "2023-11-01",
+ "name": "[parameters('name')]",
+ "location": "[parameters('location')]",
+ "tags": "[parameters('tags')]",
+ "properties": {
+ "copy": [
+ {
+ "name": "securityRules",
+ "count": "[length(coalesce(parameters('securityRules'), createArray()))]",
+ "input": {
+ "name": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].name]",
+ "properties": {
+ "access": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.access]",
+ "description": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'description'), '')]",
+ "destinationAddressPrefix": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationAddressPrefix'), '')]",
+ "destinationAddressPrefixes": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationAddressPrefixes'), createArray())]",
+ "destinationApplicationSecurityGroups": "[map(coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationApplicationSecurityGroupResourceIds'), createArray()), lambda('destinationApplicationSecurityGroupResourceId', createObject('id', lambdaVariables('destinationApplicationSecurityGroupResourceId'))))]",
+ "destinationPortRange": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationPortRange'), '')]",
+ "destinationPortRanges": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationPortRanges'), createArray())]",
+ "direction": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.direction]",
+ "priority": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.priority]",
+ "protocol": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.protocol]",
+ "sourceAddressPrefix": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceAddressPrefix'), '')]",
+ "sourceAddressPrefixes": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceAddressPrefixes'), createArray())]",
+ "sourceApplicationSecurityGroups": "[map(coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceApplicationSecurityGroupResourceIds'), createArray()), lambda('sourceApplicationSecurityGroupResourceId', createObject('id', lambdaVariables('sourceApplicationSecurityGroupResourceId'))))]",
+ "sourcePortRange": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourcePortRange'), '')]",
+ "sourcePortRanges": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourcePortRanges'), createArray())]"
+ }
+ }
+ }
+ ],
+ "flushConnection": "[parameters('flushConnection')]"
+ }
+ },
+ "networkSecurityGroup_lock": {
+ "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]",
+ "type": "Microsoft.Authorization/locks",
+ "apiVersion": "2020-05-01",
+ "scope": "[format('Microsoft.Network/networkSecurityGroups/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]",
+ "properties": {
+ "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]",
+ "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]"
+ },
+ "dependsOn": [
+ "networkSecurityGroup"
+ ]
+ },
+ "networkSecurityGroup_diagnosticSettings": {
+ "copy": {
+ "name": "networkSecurityGroup_diagnosticSettings",
+ "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]"
+ },
+ "type": "Microsoft.Insights/diagnosticSettings",
+ "apiVersion": "2021-05-01-preview",
+ "scope": "[format('Microsoft.Network/networkSecurityGroups/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]",
+ "properties": {
+ "copy": [
+ {
+ "name": "logs",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]",
+ "input": {
+ "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]",
+ "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]"
+ }
+ }
+ ],
+ "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]",
+ "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]",
+ "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]",
+ "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]",
+ "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]",
+ "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]"
+ },
+ "dependsOn": [
+ "networkSecurityGroup"
+ ]
+ },
+ "networkSecurityGroup_roleAssignments": {
+ "copy": {
+ "name": "networkSecurityGroup_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Network/networkSecurityGroups/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/networkSecurityGroups', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "networkSecurityGroup"
+ ]
+ }
+ },
+ "outputs": {
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the network security group was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the network security group."
+ },
+ "value": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('name'))]"
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the network security group."
+ },
+ "value": "[parameters('name')]"
+ },
+ "location": {
+ "type": "string",
+ "metadata": {
+ "description": "The location the resource was deployed into."
+ },
+ "value": "[reference('networkSecurityGroup', '2023-11-01', 'full').location]"
+ }
+ }
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[take(format('avm.res.network.virtual-network.{0}', parameters('vnetName')), 64)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[parameters('vnetName')]"
+ },
+ "location": {
+ "value": "[parameters('vnetLocation')]"
+ },
+ "addressPrefixes": {
+ "value": "[parameters('vnetAddressPrefixes')]"
+ },
+ "subnets": {
+ "copy": [
+ {
+ "name": "value",
+ "count": "[length(variables('vnetSubnets'))]",
+ "input": "[createObject('name', variables('vnetSubnets')[copyIndex('value')].name, 'addressPrefixes', tryGet(variables('vnetSubnets')[copyIndex('value')], 'addressPrefixes'), 'networkSecurityGroupResourceId', if(not(empty(tryGet(variables('vnetSubnets')[copyIndex('value')], 'networkSecurityGroup'))), reference(resourceId('Microsoft.Resources/deployments', take(format('avm.res.network.network-security-group.{0}.{1}', tryGet(variables('vnetSubnets')[copyIndex('value')], 'networkSecurityGroup', 'name'), parameters('resourceSuffix')), 64)), '2025-04-01').outputs.resourceId.value, null()), 'privateEndpointNetworkPolicies', tryGet(variables('vnetSubnets')[copyIndex('value')], 'privateEndpointNetworkPolicies'), 'privateLinkServiceNetworkPolicies', tryGet(variables('vnetSubnets')[copyIndex('value')], 'privateLinkServiceNetworkPolicies'), 'delegation', tryGet(variables('vnetSubnets')[copyIndex('value')], 'delegation'))]"
+ }
+ ]
+ },
+ "diagnosticSettings": "[if(not(empty(parameters('logAnalyticsWorkspaceId'))), createObject('value', createArray(createObject('name', 'vnetDiagnostics', 'workspaceResourceId', parameters('logAnalyticsWorkspaceId'), 'logCategoriesAndGroups', createArray(createObject('categoryGroup', 'allLogs', 'enabled', true())), 'metricCategories', createArray(createObject('category', 'AllMetrics', 'enabled', true()))))), createObject('value', createArray()))]",
+ "tags": {
+ "value": "[parameters('tags')]"
+ },
+ "enableTelemetry": {
+ "value": "[parameters('enableTelemetry')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "15393147213974991892"
+ },
+ "name": "Virtual Networks",
+ "description": "This module deploys a Virtual Network (vNet)."
+ },
+ "definitions": {
+ "peeringType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Name of VNET Peering resource. If not provided, default value will be peer-localVnetName-remoteVnetName."
+ }
+ },
+ "remoteVirtualNetworkResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The Resource ID of the VNet that is this Local VNet is being peered to. Should be in the format of a Resource ID."
+ }
+ },
+ "allowForwardedTraffic": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Whether the forwarded traffic from the VMs in the local virtual network will be allowed/disallowed in remote virtual network. Default is true."
+ }
+ },
+ "allowGatewayTransit": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. If gateway links can be used in remote virtual networking to link to this virtual network. Default is false."
+ }
+ },
+ "allowVirtualNetworkAccess": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Whether the VMs in the local virtual network space would be able to access the VMs in remote virtual network space. Default is true."
+ }
+ },
+ "doNotVerifyRemoteGateways": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Do not verify the provisioning state of the remote gateway. Default is true."
+ }
+ },
+ "useRemoteGateways": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. If remote gateways can be used on this virtual network. If the flag is set to true, and allowGatewayTransit on remote peering is also true, virtual network will use gateways of remote virtual network for transit. Only one peering can have this flag set to true. This flag cannot be set if virtual network already has a gateway. Default is false."
+ }
+ },
+ "remotePeeringEnabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Deploy the outbound and the inbound peering."
+ }
+ },
+ "remotePeeringName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the VNET Peering resource in the remove Virtual Network. If not provided, default value will be peer-remoteVnetName-localVnetName."
+ }
+ },
+ "remotePeeringAllowForwardedTraffic": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Whether the forwarded traffic from the VMs in the local virtual network will be allowed/disallowed in remote virtual network. Default is true."
+ }
+ },
+ "remotePeeringAllowGatewayTransit": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. If gateway links can be used in remote virtual networking to link to this virtual network. Default is false."
+ }
+ },
+ "remotePeeringAllowVirtualNetworkAccess": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Whether the VMs in the local virtual network space would be able to access the VMs in remote virtual network space. Default is true."
+ }
+ },
+ "remotePeeringDoNotVerifyRemoteGateways": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Do not verify the provisioning state of the remote gateway. Default is true."
+ }
+ },
+ "remotePeeringUseRemoteGateways": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. If remote gateways can be used on this virtual network. If the flag is set to true, and allowGatewayTransit on remote peering is also true, virtual network will use gateways of remote virtual network for transit. Only one peering can have this flag set to true. This flag cannot be set if virtual network already has a gateway. Default is false."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ },
+ "subnetType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The Name of the subnet resource."
+ }
+ },
+ "addressPrefix": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Conditional. The address prefix for the subnet. Required if `addressPrefixes` is empty."
+ }
+ },
+ "addressPrefixes": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Conditional. List of address prefixes for the subnet. Required if `addressPrefix` is empty."
+ }
+ },
+ "ipamPoolPrefixAllocations": {
+ "type": "array",
+ "prefixItems": [
+ {
+ "type": "object",
+ "properties": {
+ "pool": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The Resource ID of the IPAM pool."
+ }
+ }
+ },
+ "metadata": {
+ "description": "Required. The Resource ID of the IPAM pool."
+ }
+ },
+ "numberOfIpAddresses": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Number of IP addresses allocated from the pool."
+ }
+ }
+ }
+ }
+ ],
+ "items": false,
+ "nullable": true,
+ "metadata": {
+ "description": "Conditional. The address space for the subnet, deployed from IPAM Pool. Required if `addressPrefixes` and `addressPrefix` is empty and the VNet address space configured to use IPAM Pool."
+ }
+ },
+ "applicationGatewayIPConfigurations": {
+ "type": "array",
+ "items": {
+ "type": "object"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Application gateway IP configurations of virtual network resource."
+ }
+ },
+ "delegation": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The delegation to enable on the subnet."
+ }
+ },
+ "natGatewayResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID of the NAT Gateway to use for the subnet."
+ }
+ },
+ "networkSecurityGroupResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID of the network security group to assign to the subnet."
+ }
+ },
+ "privateEndpointNetworkPolicies": {
+ "type": "string",
+ "allowedValues": [
+ "Disabled",
+ "Enabled",
+ "NetworkSecurityGroupEnabled",
+ "RouteTableEnabled"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. enable or disable apply network policies on private endpoint in the subnet."
+ }
+ },
+ "privateLinkServiceNetworkPolicies": {
+ "type": "string",
+ "allowedValues": [
+ "Disabled",
+ "Enabled"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. enable or disable apply network policies on private link service in the subnet."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "routeTableResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID of the route table to assign to the subnet."
+ }
+ },
+ "serviceEndpointPolicies": {
+ "type": "array",
+ "items": {
+ "type": "object"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. An array of service endpoint policies."
+ }
+ },
+ "serviceEndpoints": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The service endpoints to enable on the subnet."
+ }
+ },
+ "defaultOutboundAccess": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Set this property to false to disable default outbound connectivity for all VMs in the subnet. This property can only be set at the time of subnet creation and cannot be updated for an existing subnet."
+ }
+ },
+ "sharingScope": {
+ "type": "string",
+ "allowedValues": [
+ "DelegatedServices",
+ "Tenant"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Set this property to Tenant to allow sharing subnet with other subscriptions in your AAD tenant. This property can only be set if defaultOutboundAccess is set to false, both properties can only be set if subnet is empty."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ },
+ "diagnosticSettingFullType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the diagnostic setting."
+ }
+ },
+ "logCategoriesAndGroups": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here."
+ }
+ },
+ "categoryGroup": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection."
+ }
+ },
+ "metricCategories": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection."
+ }
+ },
+ "logAnalyticsDestinationType": {
+ "type": "string",
+ "allowedValues": [
+ "AzureDiagnostics",
+ "Dedicated"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type."
+ }
+ },
+ "workspaceResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "storageAccountResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "eventHubAuthorizationRuleResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to."
+ }
+ },
+ "eventHubName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "marketplacePartnerResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1"
+ }
+ }
+ },
+ "lockType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the name of lock."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "CanNotDelete",
+ "None",
+ "ReadOnly"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ },
+ "notes": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the notes of the lock."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a lock.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0"
+ }
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the Virtual Network (vNet)."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Optional. Location for all resources."
+ }
+ },
+ "addressPrefixes": {
+ "type": "array",
+ "metadata": {
+ "description": "Required. An Array of 1 or more IP Address Prefixes OR the resource ID of the IPAM pool to be used for the Virtual Network. When specifying an IPAM pool resource ID you must also set a value for the parameter called `ipamPoolNumberOfIpAddresses`."
+ }
+ },
+ "ipamPoolNumberOfIpAddresses": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Number of IP addresses allocated from the pool. To be used only when the addressPrefix param is defined with a resource ID of an IPAM pool."
+ }
+ },
+ "virtualNetworkBgpCommunity": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The BGP community associated with the virtual network."
+ }
+ },
+ "subnets": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/subnetType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. An Array of subnets to deploy to the Virtual Network."
+ }
+ },
+ "dnsServers": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. DNS Servers associated to the Virtual Network."
+ }
+ },
+ "ddosProtectionPlanResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the DDoS protection plan to assign the VNET to. If it's left blank, DDoS protection will not be configured. If it's provided, the VNET created by this template will be attached to the referenced DDoS protection plan. The DDoS protection plan can exist in the same or in a different subscription."
+ }
+ },
+ "peerings": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/peeringType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Virtual Network Peering configurations."
+ }
+ },
+ "vnetEncryption": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Indicates if encryption is enabled on virtual network and if VM without encryption is allowed in encrypted VNet. Requires the EnableVNetEncryption feature to be registered for the subscription and a supported region to use this property."
+ }
+ },
+ "vnetEncryptionEnforcement": {
+ "type": "string",
+ "defaultValue": "AllowUnencrypted",
+ "allowedValues": [
+ "AllowUnencrypted",
+ "DropUnencrypted"
+ ],
+ "metadata": {
+ "description": "Optional. If the encrypted VNet allows VM that does not support encryption. Can only be used when vnetEncryption is enabled."
+ }
+ },
+ "flowTimeoutInMinutes": {
+ "type": "int",
+ "defaultValue": 0,
+ "maxValue": 30,
+ "metadata": {
+ "description": "Optional. The flow timeout in minutes for the Virtual Network, which is used to enable connection tracking for intra-VM flows. Possible values are between 4 and 30 minutes. Default value 0 will set the property to null."
+ }
+ },
+ "diagnosticSettings": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/diagnosticSettingFullType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The diagnostic settings of the service."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The lock settings of the service."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Tags of the resource."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ },
+ "enableVmProtection": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Indicates if VM protection is enabled for all the subnets in the virtual network."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "enableReferencedModulesTelemetry": false,
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.network-virtualnetwork.{0}.{1}', replace('0.7.2', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "virtualNetwork": {
+ "type": "Microsoft.Network/virtualNetworks",
+ "apiVersion": "2024-05-01",
+ "name": "[parameters('name')]",
+ "location": "[parameters('location')]",
+ "tags": "[parameters('tags')]",
+ "properties": {
+ "addressSpace": "[if(contains(parameters('addressPrefixes')[0], '/Microsoft.Network/networkManagers/'), createObject('ipamPoolPrefixAllocations', createArray(createObject('pool', createObject('id', parameters('addressPrefixes')[0]), 'numberOfIpAddresses', parameters('ipamPoolNumberOfIpAddresses')))), createObject('addressPrefixes', parameters('addressPrefixes')))]",
+ "bgpCommunities": "[if(not(empty(parameters('virtualNetworkBgpCommunity'))), createObject('virtualNetworkCommunity', parameters('virtualNetworkBgpCommunity')), null())]",
+ "ddosProtectionPlan": "[if(not(empty(parameters('ddosProtectionPlanResourceId'))), createObject('id', parameters('ddosProtectionPlanResourceId')), null())]",
+ "dhcpOptions": "[if(not(empty(parameters('dnsServers'))), createObject('dnsServers', array(parameters('dnsServers'))), null())]",
+ "enableDdosProtection": "[not(empty(parameters('ddosProtectionPlanResourceId')))]",
+ "encryption": "[if(equals(parameters('vnetEncryption'), true()), createObject('enabled', parameters('vnetEncryption'), 'enforcement', parameters('vnetEncryptionEnforcement')), null())]",
+ "flowTimeoutInMinutes": "[if(not(equals(parameters('flowTimeoutInMinutes'), 0)), parameters('flowTimeoutInMinutes'), null())]",
+ "enableVmProtection": "[parameters('enableVmProtection')]"
+ }
+ },
+ "virtualNetwork_lock": {
+ "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]",
+ "type": "Microsoft.Authorization/locks",
+ "apiVersion": "2020-05-01",
+ "scope": "[format('Microsoft.Network/virtualNetworks/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]",
+ "properties": {
+ "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]",
+ "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]"
+ },
+ "dependsOn": [
+ "virtualNetwork"
+ ]
+ },
+ "virtualNetwork_diagnosticSettings": {
+ "copy": {
+ "name": "virtualNetwork_diagnosticSettings",
+ "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]"
+ },
+ "type": "Microsoft.Insights/diagnosticSettings",
+ "apiVersion": "2021-05-01-preview",
+ "scope": "[format('Microsoft.Network/virtualNetworks/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]",
+ "properties": {
+ "copy": [
+ {
+ "name": "metrics",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]",
+ "input": {
+ "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]",
+ "timeGrain": null
+ }
+ },
+ {
+ "name": "logs",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]",
+ "input": {
+ "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]",
+ "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]"
+ }
+ }
+ ],
+ "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]",
+ "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]",
+ "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]",
+ "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]",
+ "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]",
+ "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]"
+ },
+ "dependsOn": [
+ "virtualNetwork"
+ ]
+ },
+ "virtualNetwork_roleAssignments": {
+ "copy": {
+ "name": "virtualNetwork_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Network/virtualNetworks/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/virtualNetworks', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "virtualNetwork"
+ ]
+ },
+ "virtualNetwork_subnets": {
+ "copy": {
+ "name": "virtualNetwork_subnets",
+ "count": "[length(coalesce(parameters('subnets'), createArray()))]",
+ "mode": "serial",
+ "batchSize": 1
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-subnet-{1}', uniqueString(subscription().id, resourceGroup().id, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "virtualNetworkName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('subnets'), createArray())[copyIndex()].name]"
+ },
+ "addressPrefix": {
+ "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'addressPrefix')]"
+ },
+ "addressPrefixes": {
+ "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'addressPrefixes')]"
+ },
+ "ipamPoolPrefixAllocations": {
+ "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'ipamPoolPrefixAllocations')]"
+ },
+ "applicationGatewayIPConfigurations": {
+ "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'applicationGatewayIPConfigurations')]"
+ },
+ "delegation": {
+ "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'delegation')]"
+ },
+ "natGatewayResourceId": {
+ "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'natGatewayResourceId')]"
+ },
+ "networkSecurityGroupResourceId": {
+ "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'networkSecurityGroupResourceId')]"
+ },
+ "privateEndpointNetworkPolicies": {
+ "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'privateEndpointNetworkPolicies')]"
+ },
+ "privateLinkServiceNetworkPolicies": {
+ "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'privateLinkServiceNetworkPolicies')]"
+ },
+ "roleAssignments": {
+ "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'roleAssignments')]"
+ },
+ "routeTableResourceId": {
+ "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'routeTableResourceId')]"
+ },
+ "serviceEndpointPolicies": {
+ "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'serviceEndpointPolicies')]"
+ },
+ "serviceEndpoints": {
+ "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'serviceEndpoints')]"
+ },
+ "defaultOutboundAccess": {
+ "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'defaultOutboundAccess')]"
+ },
+ "sharingScope": {
+ "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'sharingScope')]"
+ },
+ "enableTelemetry": {
+ "value": "[variables('enableReferencedModulesTelemetry')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "11087937906182478872"
+ },
+ "name": "Virtual Network Subnets",
+ "description": "This module deploys a Virtual Network Subnet."
+ },
+ "definitions": {
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The Name of the subnet resource."
+ }
+ },
+ "virtualNetworkName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent virtual network. Required if the template is used in a standalone deployment."
+ }
+ },
+ "addressPrefix": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Conditional. The address prefix for the subnet. Required if `addressPrefixes` is empty."
+ }
+ },
+ "ipamPoolPrefixAllocations": {
+ "type": "array",
+ "items": {
+ "type": "object"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Conditional. The address space for the subnet, deployed from IPAM Pool. Required if `addressPrefixes` and `addressPrefix` is empty."
+ }
+ },
+ "networkSecurityGroupResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID of the network security group to assign to the subnet."
+ }
+ },
+ "routeTableResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID of the route table to assign to the subnet."
+ }
+ },
+ "serviceEndpoints": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "defaultValue": [],
+ "metadata": {
+ "description": "Optional. The service endpoints to enable on the subnet."
+ }
+ },
+ "delegation": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The delegation to enable on the subnet."
+ }
+ },
+ "natGatewayResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID of the NAT Gateway to use for the subnet."
+ }
+ },
+ "privateEndpointNetworkPolicies": {
+ "type": "string",
+ "nullable": true,
+ "allowedValues": [
+ "Disabled",
+ "Enabled",
+ "NetworkSecurityGroupEnabled",
+ "RouteTableEnabled"
+ ],
+ "metadata": {
+ "description": "Optional. Enable or disable apply network policies on private endpoint in the subnet."
+ }
+ },
+ "privateLinkServiceNetworkPolicies": {
+ "type": "string",
+ "nullable": true,
+ "allowedValues": [
+ "Disabled",
+ "Enabled"
+ ],
+ "metadata": {
+ "description": "Optional. Enable or disable apply network policies on private link service in the subnet."
+ }
+ },
+ "addressPrefixes": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Conditional. List of address prefixes for the subnet. Required if `addressPrefix` is empty."
+ }
+ },
+ "defaultOutboundAccess": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Set this property to false to disable default outbound connectivity for all VMs in the subnet. This property can only be set at the time of subnet creation and cannot be updated for an existing subnet."
+ }
+ },
+ "sharingScope": {
+ "type": "string",
+ "allowedValues": [
+ "DelegatedServices",
+ "Tenant"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Set this property to Tenant to allow sharing the subnet with other subscriptions in your AAD tenant. This property can only be set if defaultOutboundAccess is set to false, both properties can only be set if the subnet is empty."
+ }
+ },
+ "applicationGatewayIPConfigurations": {
+ "type": "array",
+ "defaultValue": [],
+ "metadata": {
+ "description": "Optional. Application gateway IP configurations of virtual network resource."
+ }
+ },
+ "serviceEndpointPolicies": {
+ "type": "array",
+ "defaultValue": [],
+ "metadata": {
+ "description": "Optional. An array of service endpoint policies."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.network-virtualnetworksubnet.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "virtualNetwork": {
+ "existing": true,
+ "type": "Microsoft.Network/virtualNetworks",
+ "apiVersion": "2024-01-01",
+ "name": "[parameters('virtualNetworkName')]"
+ },
+ "subnet": {
+ "type": "Microsoft.Network/virtualNetworks/subnets",
+ "apiVersion": "2024-05-01",
+ "name": "[format('{0}/{1}', parameters('virtualNetworkName'), parameters('name'))]",
+ "properties": {
+ "copy": [
+ {
+ "name": "serviceEndpoints",
+ "count": "[length(parameters('serviceEndpoints'))]",
+ "input": {
+ "service": "[parameters('serviceEndpoints')[copyIndex('serviceEndpoints')]]"
+ }
+ }
+ ],
+ "addressPrefix": "[parameters('addressPrefix')]",
+ "addressPrefixes": "[parameters('addressPrefixes')]",
+ "ipamPoolPrefixAllocations": "[parameters('ipamPoolPrefixAllocations')]",
+ "networkSecurityGroup": "[if(not(empty(parameters('networkSecurityGroupResourceId'))), createObject('id', parameters('networkSecurityGroupResourceId')), null())]",
+ "routeTable": "[if(not(empty(parameters('routeTableResourceId'))), createObject('id', parameters('routeTableResourceId')), null())]",
+ "natGateway": "[if(not(empty(parameters('natGatewayResourceId'))), createObject('id', parameters('natGatewayResourceId')), null())]",
+ "delegations": "[if(not(empty(parameters('delegation'))), createArray(createObject('name', parameters('delegation'), 'properties', createObject('serviceName', parameters('delegation')))), createArray())]",
+ "privateEndpointNetworkPolicies": "[parameters('privateEndpointNetworkPolicies')]",
+ "privateLinkServiceNetworkPolicies": "[parameters('privateLinkServiceNetworkPolicies')]",
+ "applicationGatewayIPConfigurations": "[parameters('applicationGatewayIPConfigurations')]",
+ "serviceEndpointPolicies": "[parameters('serviceEndpointPolicies')]",
+ "defaultOutboundAccess": "[parameters('defaultOutboundAccess')]",
+ "sharingScope": "[parameters('sharingScope')]"
+ }
+ },
+ "subnet_roleAssignments": {
+ "copy": {
+ "name": "subnet_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Network/virtualNetworks/{0}/subnets/{1}', parameters('virtualNetworkName'), parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "subnet"
+ ]
+ }
+ },
+ "outputs": {
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the virtual network peering was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the virtual network peering."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the virtual network peering."
+ },
+ "value": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('name'))]"
+ },
+ "addressPrefix": {
+ "type": "string",
+ "metadata": {
+ "description": "The address prefix for the subnet."
+ },
+ "value": "[coalesce(tryGet(reference('subnet'), 'addressPrefix'), '')]"
+ },
+ "addressPrefixes": {
+ "type": "array",
+ "metadata": {
+ "description": "List of address prefixes for the subnet."
+ },
+ "value": "[coalesce(tryGet(reference('subnet'), 'addressPrefixes'), createArray())]"
+ },
+ "ipamPoolPrefixAllocations": {
+ "type": "array",
+ "metadata": {
+ "description": "The IPAM pool prefix allocations for the subnet."
+ },
+ "value": "[coalesce(tryGet(reference('subnet'), 'ipamPoolPrefixAllocations'), createArray())]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "virtualNetwork"
+ ]
+ },
+ "virtualNetwork_peering_local": {
+ "copy": {
+ "name": "virtualNetwork_peering_local",
+ "count": "[length(coalesce(parameters('peerings'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-virtualNetworkPeering-local-{1}', uniqueString(subscription().id, resourceGroup().id, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "localVnetName": {
+ "value": "[parameters('name')]"
+ },
+ "remoteVirtualNetworkResourceId": {
+ "value": "[coalesce(parameters('peerings'), createArray())[copyIndex()].remoteVirtualNetworkResourceId]"
+ },
+ "name": {
+ "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'name')]"
+ },
+ "allowForwardedTraffic": {
+ "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'allowForwardedTraffic')]"
+ },
+ "allowGatewayTransit": {
+ "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'allowGatewayTransit')]"
+ },
+ "allowVirtualNetworkAccess": {
+ "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'allowVirtualNetworkAccess')]"
+ },
+ "doNotVerifyRemoteGateways": {
+ "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'doNotVerifyRemoteGateways')]"
+ },
+ "useRemoteGateways": {
+ "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'useRemoteGateways')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "4173704498931603856"
+ },
+ "name": "Virtual Network Peerings",
+ "description": "This module deploys a Virtual Network Peering."
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "defaultValue": "[format('peer-{0}-{1}', parameters('localVnetName'), last(split(parameters('remoteVirtualNetworkResourceId'), '/')))]",
+ "metadata": {
+ "description": "Optional. The Name of VNET Peering resource. If not provided, default value will be localVnetName-remoteVnetName."
+ }
+ },
+ "localVnetName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Virtual Network to add the peering to. Required if the template is used in a standalone deployment."
+ }
+ },
+ "remoteVirtualNetworkResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The Resource ID of the VNet that is this Local VNet is being peered to. Should be in the format of a Resource ID."
+ }
+ },
+ "allowForwardedTraffic": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Whether the forwarded traffic from the VMs in the local virtual network will be allowed/disallowed in remote virtual network. Default is true."
+ }
+ },
+ "allowGatewayTransit": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. If gateway links can be used in remote virtual networking to link to this virtual network. Default is false."
+ }
+ },
+ "allowVirtualNetworkAccess": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Whether the VMs in the local virtual network space would be able to access the VMs in remote virtual network space. Default is true."
+ }
+ },
+ "doNotVerifyRemoteGateways": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. If we need to verify the provisioning state of the remote gateway. Default is true."
+ }
+ },
+ "useRemoteGateways": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. If remote gateways can be used on this virtual network. If the flag is set to true, and allowGatewayTransit on remote peering is also true, virtual network will use gateways of remote virtual network for transit. Only one peering can have this flag set to true. This flag cannot be set if virtual network already has a gateway. Default is false."
+ }
+ }
+ },
+ "resources": [
+ {
+ "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings",
+ "apiVersion": "2024-01-01",
+ "name": "[format('{0}/{1}', parameters('localVnetName'), parameters('name'))]",
+ "properties": {
+ "allowForwardedTraffic": "[parameters('allowForwardedTraffic')]",
+ "allowGatewayTransit": "[parameters('allowGatewayTransit')]",
+ "allowVirtualNetworkAccess": "[parameters('allowVirtualNetworkAccess')]",
+ "doNotVerifyRemoteGateways": "[parameters('doNotVerifyRemoteGateways')]",
+ "useRemoteGateways": "[parameters('useRemoteGateways')]",
+ "remoteVirtualNetwork": {
+ "id": "[parameters('remoteVirtualNetworkResourceId')]"
+ }
+ }
+ }
+ ],
+ "outputs": {
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the virtual network peering was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the virtual network peering."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the virtual network peering."
+ },
+ "value": "[resourceId('Microsoft.Network/virtualNetworks/virtualNetworkPeerings', parameters('localVnetName'), parameters('name'))]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "virtualNetwork",
+ "virtualNetwork_subnets"
+ ]
+ },
+ "virtualNetwork_peering_remote": {
+ "copy": {
+ "name": "virtualNetwork_peering_remote",
+ "count": "[length(coalesce(parameters('peerings'), createArray()))]"
+ },
+ "condition": "[coalesce(tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringEnabled'), false())]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-virtualNetworkPeering-remote-{1}', uniqueString(subscription().id, resourceGroup().id, parameters('location')), copyIndex())]",
+ "subscriptionId": "[split(coalesce(parameters('peerings'), createArray())[copyIndex()].remoteVirtualNetworkResourceId, '/')[2]]",
+ "resourceGroup": "[split(coalesce(parameters('peerings'), createArray())[copyIndex()].remoteVirtualNetworkResourceId, '/')[4]]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "localVnetName": {
+ "value": "[last(split(coalesce(parameters('peerings'), createArray())[copyIndex()].remoteVirtualNetworkResourceId, '/'))]"
+ },
+ "remoteVirtualNetworkResourceId": {
+ "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('name'))]"
+ },
+ "name": {
+ "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringName')]"
+ },
+ "allowForwardedTraffic": {
+ "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringAllowForwardedTraffic')]"
+ },
+ "allowGatewayTransit": {
+ "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringAllowGatewayTransit')]"
+ },
+ "allowVirtualNetworkAccess": {
+ "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringAllowVirtualNetworkAccess')]"
+ },
+ "doNotVerifyRemoteGateways": {
+ "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringDoNotVerifyRemoteGateways')]"
+ },
+ "useRemoteGateways": {
+ "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringUseRemoteGateways')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "4173704498931603856"
+ },
+ "name": "Virtual Network Peerings",
+ "description": "This module deploys a Virtual Network Peering."
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "defaultValue": "[format('peer-{0}-{1}', parameters('localVnetName'), last(split(parameters('remoteVirtualNetworkResourceId'), '/')))]",
+ "metadata": {
+ "description": "Optional. The Name of VNET Peering resource. If not provided, default value will be localVnetName-remoteVnetName."
+ }
+ },
+ "localVnetName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Virtual Network to add the peering to. Required if the template is used in a standalone deployment."
+ }
+ },
+ "remoteVirtualNetworkResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The Resource ID of the VNet that is this Local VNet is being peered to. Should be in the format of a Resource ID."
+ }
+ },
+ "allowForwardedTraffic": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Whether the forwarded traffic from the VMs in the local virtual network will be allowed/disallowed in remote virtual network. Default is true."
+ }
+ },
+ "allowGatewayTransit": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. If gateway links can be used in remote virtual networking to link to this virtual network. Default is false."
+ }
+ },
+ "allowVirtualNetworkAccess": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Whether the VMs in the local virtual network space would be able to access the VMs in remote virtual network space. Default is true."
+ }
+ },
+ "doNotVerifyRemoteGateways": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. If we need to verify the provisioning state of the remote gateway. Default is true."
+ }
+ },
+ "useRemoteGateways": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. If remote gateways can be used on this virtual network. If the flag is set to true, and allowGatewayTransit on remote peering is also true, virtual network will use gateways of remote virtual network for transit. Only one peering can have this flag set to true. This flag cannot be set if virtual network already has a gateway. Default is false."
+ }
+ }
+ },
+ "resources": [
+ {
+ "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings",
+ "apiVersion": "2024-01-01",
+ "name": "[format('{0}/{1}', parameters('localVnetName'), parameters('name'))]",
+ "properties": {
+ "allowForwardedTraffic": "[parameters('allowForwardedTraffic')]",
+ "allowGatewayTransit": "[parameters('allowGatewayTransit')]",
+ "allowVirtualNetworkAccess": "[parameters('allowVirtualNetworkAccess')]",
+ "doNotVerifyRemoteGateways": "[parameters('doNotVerifyRemoteGateways')]",
+ "useRemoteGateways": "[parameters('useRemoteGateways')]",
+ "remoteVirtualNetwork": {
+ "id": "[parameters('remoteVirtualNetworkResourceId')]"
+ }
+ }
+ }
+ ],
+ "outputs": {
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the virtual network peering was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the virtual network peering."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the virtual network peering."
+ },
+ "value": "[resourceId('Microsoft.Network/virtualNetworks/virtualNetworkPeerings', parameters('localVnetName'), parameters('name'))]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "virtualNetwork",
+ "virtualNetwork_subnets"
+ ]
+ }
+ },
+ "outputs": {
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the virtual network was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the virtual network."
+ },
+ "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('name'))]"
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the virtual network."
+ },
+ "value": "[parameters('name')]"
+ },
+ "subnetNames": {
+ "type": "array",
+ "metadata": {
+ "description": "The names of the deployed subnets."
+ },
+ "copy": {
+ "count": "[length(coalesce(parameters('subnets'), createArray()))]",
+ "input": "[reference(format('virtualNetwork_subnets[{0}]', copyIndex())).outputs.name.value]"
+ }
+ },
+ "subnetResourceIds": {
+ "type": "array",
+ "metadata": {
+ "description": "The resource IDs of the deployed subnets."
+ },
+ "copy": {
+ "count": "[length(coalesce(parameters('subnets'), createArray()))]",
+ "input": "[reference(format('virtualNetwork_subnets[{0}]', copyIndex())).outputs.resourceId.value]"
+ }
+ },
+ "location": {
+ "type": "string",
+ "metadata": {
+ "description": "The location the resource was deployed into."
+ },
+ "value": "[reference('virtualNetwork', '2024-05-01', 'full').location]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "nsgs"
+ ]
+ }
+ ],
+ "outputs": {
+ "name": {
+ "type": "string",
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('avm.res.network.virtual-network.{0}', parameters('vnetName')), 64)), '2025-04-01').outputs.name.value]"
+ },
+ "resourceId": {
+ "type": "string",
+ "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('avm.res.network.virtual-network.{0}', parameters('vnetName')), 64)), '2025-04-01').outputs.resourceId.value]"
+ },
+ "webSubnetResourceId": {
+ "type": "string",
+ "value": "[if(contains(map(variables('vnetSubnets'), lambda('subnet', lambdaVariables('subnet').name)), 'web'), reference(resourceId('Microsoft.Resources/deployments', take(format('avm.res.network.virtual-network.{0}', parameters('vnetName')), 64)), '2025-04-01').outputs.subnetResourceIds.value[indexOf(map(variables('vnetSubnets'), lambda('subnet', lambdaVariables('subnet').name)), 'web')], '')]"
+ },
+ "pepsSubnetResourceId": {
+ "type": "string",
+ "value": "[if(contains(map(variables('vnetSubnets'), lambda('subnet', lambdaVariables('subnet').name)), 'peps'), reference(resourceId('Microsoft.Resources/deployments', take(format('avm.res.network.virtual-network.{0}', parameters('vnetName')), 64)), '2025-04-01').outputs.subnetResourceIds.value[indexOf(map(variables('vnetSubnets'), lambda('subnet', lambdaVariables('subnet').name)), 'peps')], '')]"
+ },
+ "aciSubnetResourceId": {
+ "type": "string",
+ "value": "[if(contains(map(variables('vnetSubnets'), lambda('subnet', lambdaVariables('subnet').name)), 'aci'), reference(resourceId('Microsoft.Resources/deployments', take(format('avm.res.network.virtual-network.{0}', parameters('vnetName')), 64)), '2025-04-01').outputs.subnetResourceIds.value[indexOf(map(variables('vnetSubnets'), lambda('subnet', lambdaVariables('subnet').name)), 'aci')], '')]"
+ },
+ "bastionSubnetResourceId": {
+ "type": "string",
+ "value": "[if(and(parameters('deployBastionAndJumpbox'), contains(map(variables('vnetSubnets'), lambda('subnet', lambdaVariables('subnet').name)), 'AzureBastionSubnet')), reference(resourceId('Microsoft.Resources/deployments', take(format('avm.res.network.virtual-network.{0}', parameters('vnetName')), 64)), '2025-04-01').outputs.subnetResourceIds.value[indexOf(map(variables('vnetSubnets'), lambda('subnet', lambdaVariables('subnet').name)), 'AzureBastionSubnet')], '')]"
+ },
+ "jumpboxSubnetResourceId": {
+ "type": "string",
+ "value": "[if(and(parameters('deployBastionAndJumpbox'), contains(map(variables('vnetSubnets'), lambda('subnet', lambdaVariables('subnet').name)), 'jumpbox')), reference(resourceId('Microsoft.Resources/deployments', take(format('avm.res.network.virtual-network.{0}', parameters('vnetName')), 64)), '2025-04-01').outputs.subnetResourceIds.value[indexOf(map(variables('vnetSubnets'), lambda('subnet', lambdaVariables('subnet').name)), 'jumpbox')], '')]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "logAnalyticsWorkspace"
+ ]
+ },
+ "avmPrivateDnsZones": {
+ "copy": {
+ "name": "avmPrivateDnsZones",
+ "count": "[length(variables('privateDnsZones'))]",
+ "mode": "serial",
+ "batchSize": 5
+ },
+ "condition": "[parameters('enablePrivateNetworking')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[take(format('avm.res.network.private-dns-zone.{0}', replace(variables('privateDnsZones')[copyIndex()], '.', '-')), 64)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[variables('privateDnsZones')[copyIndex()]]"
+ },
+ "tags": {
+ "value": "[parameters('tags')]"
+ },
+ "enableTelemetry": {
+ "value": "[parameters('enableTelemetry')]"
+ },
+ "virtualNetworkLinks": {
+ "value": [
+ {
+ "virtualNetworkResourceId": "[if(parameters('enablePrivateNetworking'), reference('virtualNetwork').outputs.resourceId.value, '')]",
+ "registrationEnabled": false
+ }
+ ]
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.37.4.10188",
+ "templateHash": "17921343070314002065"
+ },
+ "name": "Private DNS Zones",
+ "description": "This module deploys a Private DNS zone."
+ },
+ "definitions": {
+ "aType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the record."
+ }
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/A@2024-06-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. The metadata of the record."
+ },
+ "nullable": true
+ },
+ "ttl": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The TTL of the record."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "aRecords": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/A@2024-06-01#properties/properties/properties/aRecords"
+ },
+ "description": "Optional. The list of A records in the record set."
+ },
+ "nullable": true
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for the A record."
+ }
+ },
+ "aaaaType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the record."
+ }
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/AAAA@2024-06-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. The metadata of the record."
+ },
+ "nullable": true
+ },
+ "ttl": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The TTL of the record."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "aaaaRecords": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/AAAA@2024-06-01#properties/properties/properties/aaaaRecords"
+ },
+ "description": "Optional. The list of AAAA records in the record set."
+ },
+ "nullable": true
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for the AAAA record."
+ }
+ },
+ "cnameType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the record."
+ }
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/CNAME@2024-06-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. The metadata of the record."
+ },
+ "nullable": true
+ },
+ "ttl": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The TTL of the record."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "cnameRecord": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/CNAME@2024-06-01#properties/properties/properties/cnameRecord"
+ },
+ "description": "Optional. The CNAME record in the record set."
+ },
+ "nullable": true
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for the CNAME record."
+ }
+ },
+ "mxType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the record."
+ }
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/MX@2024-06-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. The metadata of the record."
+ },
+ "nullable": true
+ },
+ "ttl": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The TTL of the record."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "mxRecords": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/MX@2024-06-01#properties/properties/properties/mxRecords"
+ },
+ "description": "Optional. The list of MX records in the record set."
+ },
+ "nullable": true
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for the MX record."
+ }
+ },
+ "ptrType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the record."
+ }
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/PTR@2024-06-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. The metadata of the record."
+ },
+ "nullable": true
+ },
+ "ttl": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The TTL of the record."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "ptrRecords": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/PTR@2024-06-01#properties/properties/properties/ptrRecords"
+ },
+ "description": "Optional. The list of PTR records in the record set."
+ },
+ "nullable": true
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for the PTR record."
+ }
+ },
+ "soaType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the record."
+ }
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/SOA@2024-06-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. The metadata of the record."
+ },
+ "nullable": true
+ },
+ "ttl": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The TTL of the record."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "soaRecord": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/SOA@2024-06-01#properties/properties/properties/soaRecord"
+ },
+ "description": "Optional. The SOA record in the record set."
+ },
+ "nullable": true
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for the SOA record."
+ }
+ },
+ "srvType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the record."
+ }
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/SRV@2024-06-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. The metadata of the record."
+ },
+ "nullable": true
+ },
+ "ttl": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The TTL of the record."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "srvRecords": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/SRV@2024-06-01#properties/properties/properties/srvRecords"
+ },
+ "description": "Optional. The list of SRV records in the record set."
+ },
+ "nullable": true
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for the SRV record."
+ }
+ },
+ "txtType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the record."
+ }
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/TXT@2024-06-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. The metadata of the record."
+ },
+ "nullable": true
+ },
+ "ttl": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The TTL of the record."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "txtRecords": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/TXT@2024-06-01#properties/properties/properties/txtRecords"
+ },
+ "description": "Optional. The list of TXT records in the record set."
+ },
+ "nullable": true
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for the TXT record."
+ }
+ },
+ "virtualNetworkLinkType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "minLength": 1,
+ "maxLength": 80,
+ "metadata": {
+ "description": "Optional. The resource name."
+ }
+ },
+ "virtualNetworkResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource ID of the virtual network to link."
+ }
+ },
+ "location": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Azure Region where the resource lives."
+ }
+ },
+ "registrationEnabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Is auto-registration of virtual machine records in the virtual network in the Private DNS zone enabled?."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01#properties/tags"
+ },
+ "description": "Optional. Resource tags."
+ },
+ "nullable": true
+ },
+ "resolutionPolicy": {
+ "type": "string",
+ "allowedValues": [
+ "Default",
+ "NxDomainRedirect"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resolution type of the private-dns-zone fallback machanism."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for the virtual network link."
+ }
+ },
+ "lockType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the name of lock."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "CanNotDelete",
+ "None",
+ "ReadOnly"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ },
+ "notes": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the notes of the lock."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a lock.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0"
+ }
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Private DNS zone name."
+ }
+ },
+ "a": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/aType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of A records."
+ }
+ },
+ "aaaa": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/aaaaType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of AAAA records."
+ }
+ },
+ "cname": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/cnameType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of CNAME records."
+ }
+ },
+ "mx": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/mxType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of MX records."
+ }
+ },
+ "ptr": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ptrType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of PTR records."
+ }
+ },
+ "soa": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/soaType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of SOA records."
+ }
+ },
+ "srv": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/srvType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of SRV records."
+ }
+ },
+ "txt": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/txtType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of TXT records."
+ }
+ },
+ "virtualNetworkLinks": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/virtualNetworkLinkType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of custom objects describing vNet links of the DNS zone. Each object should contain properties 'virtualNetworkResourceId' and 'registrationEnabled'. The 'vnetResourceId' is a resource ID of a vNet to link, 'registrationEnabled' (bool) enables automatic DNS registration in the zone for the linked vNet."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "global",
+ "metadata": {
+ "description": "Optional. The location of the PrivateDNSZone. Should be global."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Tags of the resource."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The lock settings of the service."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]"
+ },
+ "enableReferencedModulesTelemetry": false
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.network-privatednszone.{0}.{1}', replace('0.8.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "privateDnsZone": {
+ "type": "Microsoft.Network/privateDnsZones",
+ "apiVersion": "2020-06-01",
+ "name": "[parameters('name')]",
+ "location": "[parameters('location')]",
+ "tags": "[parameters('tags')]"
+ },
+ "privateDnsZone_lock": {
+ "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]",
+ "type": "Microsoft.Authorization/locks",
+ "apiVersion": "2020-05-01",
+ "scope": "[format('Microsoft.Network/privateDnsZones/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]",
+ "properties": {
+ "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]",
+ "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]"
+ },
+ "dependsOn": [
+ "privateDnsZone"
+ ]
+ },
+ "privateDnsZone_roleAssignments": {
+ "copy": {
+ "name": "privateDnsZone_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Network/privateDnsZones/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "privateDnsZone"
+ ]
+ },
+ "privateDnsZone_A": {
+ "copy": {
+ "name": "privateDnsZone_A",
+ "count": "[length(coalesce(parameters('a'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2022-09-01",
+ "name": "[format('{0}-PrivateDnsZone-ARecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "privateDnsZoneName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('a'), createArray())[copyIndex()].name]"
+ },
+ "aRecords": {
+ "value": "[tryGet(coalesce(parameters('a'), createArray())[copyIndex()], 'aRecords')]"
+ },
+ "metadata": {
+ "value": "[tryGet(coalesce(parameters('a'), createArray())[copyIndex()], 'metadata')]"
+ },
+ "ttl": {
+ "value": "[coalesce(tryGet(coalesce(parameters('a'), createArray())[copyIndex()], 'ttl'), 3600)]"
+ },
+ "roleAssignments": {
+ "value": "[tryGet(coalesce(parameters('a'), createArray())[copyIndex()], 'roleAssignments')]"
+ },
+ "enableTelemetry": {
+ "value": "[variables('enableReferencedModulesTelemetry')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.37.4.10188",
+ "templateHash": "12608084563401365743"
+ },
+ "name": "Private DNS Zone A record",
+ "description": "This module deploys a Private DNS Zone A record."
+ },
+ "definitions": {
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "privateDnsZoneName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the A record."
+ }
+ },
+ "aRecords": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/A@2024-06-01#properties/properties/properties/aRecords"
+ },
+ "description": "Optional. The list of A records in the record set."
+ },
+ "nullable": true
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/A@2024-06-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. The metadata attached to the record set."
+ },
+ "nullable": true
+ },
+ "ttl": {
+ "type": "int",
+ "defaultValue": 3600,
+ "metadata": {
+ "description": "Optional. The TTL (time-to-live) of the records in the record set."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]",
+ "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.nw-privdnszonea.{0}.{1}', replace('0.1.0', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "privateDnsZone": {
+ "existing": true,
+ "type": "Microsoft.Network/privateDnsZones",
+ "apiVersion": "2020-06-01",
+ "name": "[parameters('privateDnsZoneName')]"
+ },
+ "A": {
+ "type": "Microsoft.Network/privateDnsZones/A",
+ "apiVersion": "2020-06-01",
+ "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]",
+ "properties": {
+ "aRecords": "[parameters('aRecords')]",
+ "metadata": "[parameters('metadata')]",
+ "ttl": "[parameters('ttl')]"
+ }
+ },
+ "A_roleAssignments": {
+ "copy": {
+ "name": "A_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Network/privateDnsZones/{0}/A/{1}', parameters('privateDnsZoneName'), parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/A', parameters('privateDnsZoneName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "A"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed A record."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed A record."
+ },
+ "value": "[resourceId('Microsoft.Network/privateDnsZones/A', parameters('privateDnsZoneName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group of the deployed A record."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "privateDnsZone"
+ ]
+ },
+ "privateDnsZone_AAAA": {
+ "copy": {
+ "name": "privateDnsZone_AAAA",
+ "count": "[length(coalesce(parameters('aaaa'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2022-09-01",
+ "name": "[format('{0}-PrivateDnsZone-AAAARecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "privateDnsZoneName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('aaaa'), createArray())[copyIndex()].name]"
+ },
+ "aaaaRecords": {
+ "value": "[tryGet(coalesce(parameters('aaaa'), createArray())[copyIndex()], 'aaaaRecords')]"
+ },
+ "metadata": {
+ "value": "[tryGet(coalesce(parameters('aaaa'), createArray())[copyIndex()], 'metadata')]"
+ },
+ "ttl": {
+ "value": "[coalesce(tryGet(coalesce(parameters('aaaa'), createArray())[copyIndex()], 'ttl'), 3600)]"
+ },
+ "roleAssignments": {
+ "value": "[tryGet(coalesce(parameters('aaaa'), createArray())[copyIndex()], 'roleAssignments')]"
+ },
+ "enableTelemetry": {
+ "value": "[variables('enableReferencedModulesTelemetry')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.37.4.10188",
+ "templateHash": "4881696097088567452"
+ },
+ "name": "Private DNS Zone AAAA record",
+ "description": "This module deploys a Private DNS Zone AAAA record."
+ },
+ "definitions": {
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "privateDnsZoneName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the AAAA record."
+ }
+ },
+ "aaaaRecords": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/AAAA@2024-06-01#properties/properties/properties/aaaaRecords"
+ },
+ "description": "Optional. The list of AAAA records in the record set."
+ },
+ "nullable": true
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/AAAA@2024-06-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. The metadata attached to the record set."
+ },
+ "nullable": true
+ },
+ "ttl": {
+ "type": "int",
+ "defaultValue": 3600,
+ "metadata": {
+ "description": "Optional. The TTL (time-to-live) of the records in the record set."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]",
+ "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.nw-privdnszoneaaaa.{0}.{1}', replace('0.1.0', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "privateDnsZone": {
+ "existing": true,
+ "type": "Microsoft.Network/privateDnsZones",
+ "apiVersion": "2020-06-01",
+ "name": "[parameters('privateDnsZoneName')]"
+ },
+ "AAAA": {
+ "type": "Microsoft.Network/privateDnsZones/AAAA",
+ "apiVersion": "2020-06-01",
+ "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]",
+ "properties": {
+ "aaaaRecords": "[parameters('aaaaRecords')]",
+ "metadata": "[parameters('metadata')]",
+ "ttl": "[parameters('ttl')]"
+ }
+ },
+ "AAAA_roleAssignments": {
+ "copy": {
+ "name": "AAAA_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Network/privateDnsZones/{0}/AAAA/{1}', parameters('privateDnsZoneName'), parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/AAAA', parameters('privateDnsZoneName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "AAAA"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed AAAA record."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed AAAA record."
+ },
+ "value": "[resourceId('Microsoft.Network/privateDnsZones/AAAA', parameters('privateDnsZoneName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group of the deployed AAAA record."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "privateDnsZone"
+ ]
+ },
+ "privateDnsZone_CNAME": {
+ "copy": {
+ "name": "privateDnsZone_CNAME",
+ "count": "[length(coalesce(parameters('cname'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2022-09-01",
+ "name": "[format('{0}-PrivateDnsZone-CNAMERecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "privateDnsZoneName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('cname'), createArray())[copyIndex()].name]"
+ },
+ "cnameRecord": {
+ "value": "[tryGet(coalesce(parameters('cname'), createArray())[copyIndex()], 'cnameRecord')]"
+ },
+ "metadata": {
+ "value": "[tryGet(coalesce(parameters('cname'), createArray())[copyIndex()], 'metadata')]"
+ },
+ "ttl": {
+ "value": "[coalesce(tryGet(coalesce(parameters('cname'), createArray())[copyIndex()], 'ttl'), 3600)]"
+ },
+ "roleAssignments": {
+ "value": "[tryGet(coalesce(parameters('cname'), createArray())[copyIndex()], 'roleAssignments')]"
+ },
+ "enableTelemetry": {
+ "value": "[variables('enableReferencedModulesTelemetry')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.37.4.10188",
+ "templateHash": "13307906270868460967"
+ },
+ "name": "Private DNS Zone CNAME record",
+ "description": "This module deploys a Private DNS Zone CNAME record."
+ },
+ "definitions": {
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "privateDnsZoneName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the CNAME record."
+ }
+ },
+ "cnameRecord": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/CNAME@2024-06-01#properties/properties/properties/cnameRecord"
+ },
+ "description": "Optional. A CNAME record."
+ },
+ "nullable": true
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/CNAME@2024-06-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. The metadata attached to the record set."
+ },
+ "nullable": true
+ },
+ "ttl": {
+ "type": "int",
+ "defaultValue": 3600,
+ "metadata": {
+ "description": "Optional. The TTL (time-to-live) of the records in the record set."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]",
+ "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.nw-privdnszonecname.{0}.{1}', replace('0.1.0', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "privateDnsZone": {
+ "existing": true,
+ "type": "Microsoft.Network/privateDnsZones",
+ "apiVersion": "2020-06-01",
+ "name": "[parameters('privateDnsZoneName')]"
+ },
+ "CNAME": {
+ "type": "Microsoft.Network/privateDnsZones/CNAME",
+ "apiVersion": "2020-06-01",
+ "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]",
+ "properties": {
+ "cnameRecord": "[parameters('cnameRecord')]",
+ "metadata": "[parameters('metadata')]",
+ "ttl": "[parameters('ttl')]"
+ }
+ },
+ "CNAME_roleAssignments": {
+ "copy": {
+ "name": "CNAME_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Network/privateDnsZones/{0}/CNAME/{1}', parameters('privateDnsZoneName'), parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/CNAME', parameters('privateDnsZoneName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "CNAME"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed CNAME record."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed CNAME record."
+ },
+ "value": "[resourceId('Microsoft.Network/privateDnsZones/CNAME', parameters('privateDnsZoneName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group of the deployed CNAME record."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "privateDnsZone"
+ ]
+ },
+ "privateDnsZone_MX": {
+ "copy": {
+ "name": "privateDnsZone_MX",
+ "count": "[length(coalesce(parameters('mx'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2022-09-01",
+ "name": "[format('{0}-PrivateDnsZone-MXRecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "privateDnsZoneName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('mx'), createArray())[copyIndex()].name]"
+ },
+ "metadata": {
+ "value": "[tryGet(coalesce(parameters('mx'), createArray())[copyIndex()], 'metadata')]"
+ },
+ "mxRecords": {
+ "value": "[tryGet(coalesce(parameters('mx'), createArray())[copyIndex()], 'mxRecords')]"
+ },
+ "ttl": {
+ "value": "[coalesce(tryGet(coalesce(parameters('mx'), createArray())[copyIndex()], 'ttl'), 3600)]"
+ },
+ "roleAssignments": {
+ "value": "[tryGet(coalesce(parameters('mx'), createArray())[copyIndex()], 'roleAssignments')]"
+ },
+ "enableTelemetry": {
+ "value": "[variables('enableReferencedModulesTelemetry')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.37.4.10188",
+ "templateHash": "7946896598573056688"
+ },
+ "name": "Private DNS Zone MX record",
+ "description": "This module deploys a Private DNS Zone MX record."
+ },
+ "definitions": {
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "privateDnsZoneName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the MX record."
+ }
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/MX@2024-06-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. The metadata attached to the record set."
+ },
+ "nullable": true
+ },
+ "mxRecords": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/MX@2024-06-01#properties/properties/properties/mxRecords"
+ },
+ "description": "Optional. The list of MX records in the record set."
+ },
+ "nullable": true
+ },
+ "ttl": {
+ "type": "int",
+ "defaultValue": 3600,
+ "metadata": {
+ "description": "Optional. The TTL (time-to-live) of the records in the record set."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]",
+ "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.nw-privdnszonemx.{0}.{1}', replace('0.1.0', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "privateDnsZone": {
+ "existing": true,
+ "type": "Microsoft.Network/privateDnsZones",
+ "apiVersion": "2020-06-01",
+ "name": "[parameters('privateDnsZoneName')]"
+ },
+ "MX": {
+ "type": "Microsoft.Network/privateDnsZones/MX",
+ "apiVersion": "2020-06-01",
+ "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]",
+ "properties": {
+ "metadata": "[parameters('metadata')]",
+ "mxRecords": "[parameters('mxRecords')]",
+ "ttl": "[parameters('ttl')]"
+ }
+ },
+ "MX_roleAssignments": {
+ "copy": {
+ "name": "MX_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Network/privateDnsZones/{0}/MX/{1}', parameters('privateDnsZoneName'), parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/MX', parameters('privateDnsZoneName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "MX"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed MX record."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed MX record."
+ },
+ "value": "[resourceId('Microsoft.Network/privateDnsZones/MX', parameters('privateDnsZoneName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group of the deployed MX record."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "privateDnsZone"
+ ]
+ },
+ "privateDnsZone_PTR": {
+ "copy": {
+ "name": "privateDnsZone_PTR",
+ "count": "[length(coalesce(parameters('ptr'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2022-09-01",
+ "name": "[format('{0}-PrivateDnsZone-PTRRecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "privateDnsZoneName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('ptr'), createArray())[copyIndex()].name]"
+ },
+ "metadata": {
+ "value": "[tryGet(coalesce(parameters('ptr'), createArray())[copyIndex()], 'metadata')]"
+ },
+ "ptrRecords": {
+ "value": "[tryGet(coalesce(parameters('ptr'), createArray())[copyIndex()], 'ptrRecords')]"
+ },
+ "ttl": {
+ "value": "[coalesce(tryGet(coalesce(parameters('ptr'), createArray())[copyIndex()], 'ttl'), 3600)]"
+ },
+ "roleAssignments": {
+ "value": "[tryGet(coalesce(parameters('ptr'), createArray())[copyIndex()], 'roleAssignments')]"
+ },
+ "enableTelemetry": {
+ "value": "[variables('enableReferencedModulesTelemetry')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.37.4.10188",
+ "templateHash": "7627375510490151870"
+ },
+ "name": "Private DNS Zone PTR record",
+ "description": "This module deploys a Private DNS Zone PTR record."
+ },
+ "definitions": {
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "privateDnsZoneName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the PTR record."
+ }
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/PTR@2024-06-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. The metadata attached to the record set."
+ },
+ "nullable": true
+ },
+ "ptrRecords": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/PTR@2024-06-01#properties/properties/properties/ptrRecords"
+ },
+ "description": "Optional. The list of PTR records in the record set."
+ },
+ "nullable": true
+ },
+ "ttl": {
+ "type": "int",
+ "defaultValue": 3600,
+ "metadata": {
+ "description": "Optional. The TTL (time-to-live) of the records in the record set."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]",
+ "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.nw-privdnszoneptr.{0}.{1}', replace('0.1.0', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "privateDnsZone": {
+ "existing": true,
+ "type": "Microsoft.Network/privateDnsZones",
+ "apiVersion": "2020-06-01",
+ "name": "[parameters('privateDnsZoneName')]"
+ },
+ "PTR": {
+ "type": "Microsoft.Network/privateDnsZones/PTR",
+ "apiVersion": "2020-06-01",
+ "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]",
+ "properties": {
+ "metadata": "[parameters('metadata')]",
+ "ptrRecords": "[parameters('ptrRecords')]",
+ "ttl": "[parameters('ttl')]"
+ }
+ },
+ "PTR_roleAssignments": {
+ "copy": {
+ "name": "PTR_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Network/privateDnsZones/{0}/PTR/{1}', parameters('privateDnsZoneName'), parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/PTR', parameters('privateDnsZoneName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "PTR"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed PTR record."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed PTR record."
+ },
+ "value": "[resourceId('Microsoft.Network/privateDnsZones/PTR', parameters('privateDnsZoneName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group of the deployed PTR record."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "privateDnsZone"
+ ]
+ },
+ "privateDnsZone_SOA": {
+ "copy": {
+ "name": "privateDnsZone_SOA",
+ "count": "[length(coalesce(parameters('soa'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2022-09-01",
+ "name": "[format('{0}-PrivateDnsZone-SOARecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "privateDnsZoneName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('soa'), createArray())[copyIndex()].name]"
+ },
+ "metadata": {
+ "value": "[tryGet(coalesce(parameters('soa'), createArray())[copyIndex()], 'metadata')]"
+ },
+ "soaRecord": {
+ "value": "[tryGet(coalesce(parameters('soa'), createArray())[copyIndex()], 'soaRecord')]"
+ },
+ "ttl": {
+ "value": "[coalesce(tryGet(coalesce(parameters('soa'), createArray())[copyIndex()], 'ttl'), 3600)]"
+ },
+ "roleAssignments": {
+ "value": "[tryGet(coalesce(parameters('soa'), createArray())[copyIndex()], 'roleAssignments')]"
+ },
+ "enableTelemetry": {
+ "value": "[variables('enableReferencedModulesTelemetry')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.37.4.10188",
+ "templateHash": "16709883266329935583"
+ },
+ "name": "Private DNS Zone SOA record",
+ "description": "This module deploys a Private DNS Zone SOA record."
+ },
+ "definitions": {
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "privateDnsZoneName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the SOA record."
+ }
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/SOA@2024-06-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. The metadata attached to the record set."
+ },
+ "nullable": true
+ },
+ "soaRecord": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/SOA@2024-06-01#properties/properties/properties/soaRecord"
+ },
+ "description": "Optional. A SOA record."
+ },
+ "nullable": true
+ },
+ "ttl": {
+ "type": "int",
+ "defaultValue": 3600,
+ "metadata": {
+ "description": "Optional. The TTL (time-to-live) of the records in the record set."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]",
+ "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.nw-privdnszonesoa.{0}.{1}', replace('0.1.0', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "privateDnsZone": {
+ "existing": true,
+ "type": "Microsoft.Network/privateDnsZones",
+ "apiVersion": "2020-06-01",
+ "name": "[parameters('privateDnsZoneName')]"
+ },
+ "SOA": {
+ "type": "Microsoft.Network/privateDnsZones/SOA",
+ "apiVersion": "2020-06-01",
+ "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]",
+ "properties": {
+ "metadata": "[parameters('metadata')]",
+ "soaRecord": "[parameters('soaRecord')]",
+ "ttl": "[parameters('ttl')]"
+ }
+ },
+ "SOA_roleAssignments": {
+ "copy": {
+ "name": "SOA_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Network/privateDnsZones/{0}/SOA/{1}', parameters('privateDnsZoneName'), parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/SOA', parameters('privateDnsZoneName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "SOA"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed SOA record."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed SOA record."
+ },
+ "value": "[resourceId('Microsoft.Network/privateDnsZones/SOA', parameters('privateDnsZoneName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group of the deployed SOA record."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "privateDnsZone"
+ ]
+ },
+ "privateDnsZone_SRV": {
+ "copy": {
+ "name": "privateDnsZone_SRV",
+ "count": "[length(coalesce(parameters('srv'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2022-09-01",
+ "name": "[format('{0}-PrivateDnsZone-SRVRecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "privateDnsZoneName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('srv'), createArray())[copyIndex()].name]"
+ },
+ "metadata": {
+ "value": "[tryGet(coalesce(parameters('srv'), createArray())[copyIndex()], 'metadata')]"
+ },
+ "srvRecords": {
+ "value": "[tryGet(coalesce(parameters('srv'), createArray())[copyIndex()], 'srvRecords')]"
+ },
+ "ttl": {
+ "value": "[coalesce(tryGet(coalesce(parameters('srv'), createArray())[copyIndex()], 'ttl'), 3600)]"
+ },
+ "roleAssignments": {
+ "value": "[tryGet(coalesce(parameters('srv'), createArray())[copyIndex()], 'roleAssignments')]"
+ },
+ "enableTelemetry": {
+ "value": "[variables('enableReferencedModulesTelemetry')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.37.4.10188",
+ "templateHash": "8123422724272920495"
+ },
+ "name": "Private DNS Zone SRV record",
+ "description": "This module deploys a Private DNS Zone SRV record."
+ },
+ "definitions": {
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "privateDnsZoneName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the SRV record."
+ }
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/SRV@2024-06-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. The metadata attached to the record set."
+ },
+ "nullable": true
+ },
+ "srvRecords": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/SRV@2024-06-01#properties/properties/properties/srvRecords"
+ },
+ "description": "Optional. The list of SRV records in the record set."
+ },
+ "nullable": true
+ },
+ "ttl": {
+ "type": "int",
+ "defaultValue": 3600,
+ "metadata": {
+ "description": "Optional. The TTL (time-to-live) of the records in the record set."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]",
+ "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.nw-privdnszonesrv.{0}.{1}', replace('0.1.0', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "privateDnsZone": {
+ "existing": true,
+ "type": "Microsoft.Network/privateDnsZones",
+ "apiVersion": "2020-06-01",
+ "name": "[parameters('privateDnsZoneName')]"
+ },
+ "SRV": {
+ "type": "Microsoft.Network/privateDnsZones/SRV",
+ "apiVersion": "2020-06-01",
+ "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]",
+ "properties": {
+ "metadata": "[parameters('metadata')]",
+ "srvRecords": "[parameters('srvRecords')]",
+ "ttl": "[parameters('ttl')]"
+ }
+ },
+ "SRV_roleAssignments": {
+ "copy": {
+ "name": "SRV_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Network/privateDnsZones/{0}/SRV/{1}', parameters('privateDnsZoneName'), parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/SRV', parameters('privateDnsZoneName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "SRV"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed SRV record."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed SRV record."
+ },
+ "value": "[resourceId('Microsoft.Network/privateDnsZones/SRV', parameters('privateDnsZoneName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group of the deployed SRV record."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "privateDnsZone"
+ ]
+ },
+ "privateDnsZone_TXT": {
+ "copy": {
+ "name": "privateDnsZone_TXT",
+ "count": "[length(coalesce(parameters('txt'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2022-09-01",
+ "name": "[format('{0}-PrivateDnsZone-TXTRecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "privateDnsZoneName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('txt'), createArray())[copyIndex()].name]"
+ },
+ "metadata": {
+ "value": "[tryGet(coalesce(parameters('txt'), createArray())[copyIndex()], 'metadata')]"
+ },
+ "txtRecords": {
+ "value": "[tryGet(coalesce(parameters('txt'), createArray())[copyIndex()], 'txtRecords')]"
+ },
+ "ttl": {
+ "value": "[coalesce(tryGet(coalesce(parameters('txt'), createArray())[copyIndex()], 'ttl'), 3600)]"
+ },
+ "roleAssignments": {
+ "value": "[tryGet(coalesce(parameters('txt'), createArray())[copyIndex()], 'roleAssignments')]"
+ },
+ "enableTelemetry": {
+ "value": "[variables('enableReferencedModulesTelemetry')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.37.4.10188",
+ "templateHash": "17170531000135004092"
+ },
+ "name": "Private DNS Zone TXT record",
+ "description": "This module deploys a Private DNS Zone TXT record."
+ },
+ "definitions": {
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "privateDnsZoneName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the TXT record."
+ }
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/TXT@2024-06-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. The metadata attached to the record set."
+ },
+ "nullable": true
+ },
+ "ttl": {
+ "type": "int",
+ "defaultValue": 3600,
+ "metadata": {
+ "description": "Optional. The TTL (time-to-live) of the records in the record set."
+ }
+ },
+ "txtRecords": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/TXT@2024-06-01#properties/properties/properties/txtRecords"
+ },
+ "description": "Optional. The list of TXT records in the record set."
+ },
+ "nullable": true
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]",
+ "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.nw-privdnszonetxt.{0}.{1}', replace('0.1.0', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "privateDnsZone": {
+ "existing": true,
+ "type": "Microsoft.Network/privateDnsZones",
+ "apiVersion": "2020-06-01",
+ "name": "[parameters('privateDnsZoneName')]"
+ },
+ "TXT": {
+ "type": "Microsoft.Network/privateDnsZones/TXT",
+ "apiVersion": "2020-06-01",
+ "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]",
+ "properties": {
+ "metadata": "[parameters('metadata')]",
+ "ttl": "[parameters('ttl')]",
+ "txtRecords": "[parameters('txtRecords')]"
+ }
+ },
+ "TXT_roleAssignments": {
+ "copy": {
+ "name": "TXT_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Network/privateDnsZones/{0}/TXT/{1}', parameters('privateDnsZoneName'), parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/TXT', parameters('privateDnsZoneName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "TXT"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed TXT record."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed TXT record."
+ },
+ "value": "[resourceId('Microsoft.Network/privateDnsZones/TXT', parameters('privateDnsZoneName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group of the deployed TXT record."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "privateDnsZone"
+ ]
+ },
+ "privateDnsZone_virtualNetworkLinks": {
+ "copy": {
+ "name": "privateDnsZone_virtualNetworkLinks",
+ "count": "[length(coalesce(parameters('virtualNetworkLinks'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2022-09-01",
+ "name": "[format('{0}-PrivateDnsZone-VNetLink-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "privateDnsZoneName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(tryGet(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()], 'name'), format('{0}-vnetlink', last(split(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()].virtualNetworkResourceId, '/'))))]"
+ },
+ "virtualNetworkResourceId": {
+ "value": "[coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()].virtualNetworkResourceId]"
+ },
+ "location": {
+ "value": "[coalesce(tryGet(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()], 'location'), 'global')]"
+ },
+ "registrationEnabled": {
+ "value": "[coalesce(tryGet(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()], 'registrationEnabled'), false())]"
+ },
+ "tags": {
+ "value": "[coalesce(tryGet(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()], 'tags'), parameters('tags'))]"
+ },
+ "resolutionPolicy": {
+ "value": "[tryGet(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()], 'resolutionPolicy')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.37.4.10188",
+ "templateHash": "517173107480898390"
+ },
+ "name": "Private DNS Zone Virtual Network Link",
+ "description": "This module deploys a Private DNS Zone Virtual Network Link."
+ },
+ "parameters": {
+ "privateDnsZoneName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "defaultValue": "[format('{0}-vnetlink', last(split(parameters('virtualNetworkResourceId'), '/')))]",
+ "metadata": {
+ "description": "Optional. The name of the virtual network link."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "global",
+ "metadata": {
+ "description": "Optional. The location of the PrivateDNSZone. Should be global."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01#properties/tags"
+ },
+ "description": "Optional. Tags of the resource."
+ },
+ "nullable": true
+ },
+ "registrationEnabled": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Is auto-registration of virtual machine records in the virtual network in the Private DNS zone enabled?."
+ }
+ },
+ "virtualNetworkResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Link to another virtual network resource ID."
+ }
+ },
+ "resolutionPolicy": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resolution policy on the virtual network link. Only applicable for virtual network links to privatelink zones, and for A,AAAA,CNAME queries. When set to `NxDomainRedirect`, Azure DNS resolver falls back to public resolution if private dns query resolution results in non-existent domain response. `Default` is configured as the default option."
+ }
+ }
+ },
+ "resources": {
+ "privateDnsZone": {
+ "existing": true,
+ "type": "Microsoft.Network/privateDnsZones",
+ "apiVersion": "2020-06-01",
+ "name": "[parameters('privateDnsZoneName')]"
+ },
+ "virtualNetworkLink": {
+ "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks",
+ "apiVersion": "2024-06-01",
+ "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]",
+ "location": "[parameters('location')]",
+ "tags": "[parameters('tags')]",
+ "properties": {
+ "registrationEnabled": "[parameters('registrationEnabled')]",
+ "virtualNetwork": {
+ "id": "[parameters('virtualNetworkResourceId')]"
+ },
+ "resolutionPolicy": "[parameters('resolutionPolicy')]"
+ }
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed virtual network link."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed virtual network link."
+ },
+ "value": "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', parameters('privateDnsZoneName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group of the deployed virtual network link."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "location": {
+ "type": "string",
+ "metadata": {
+ "description": "The location the resource was deployed into."
+ },
+ "value": "[reference('virtualNetworkLink', '2024-06-01', 'full').location]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "privateDnsZone"
+ ]
+ }
+ },
+ "outputs": {
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the private DNS zone was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the private DNS zone."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the private DNS zone."
+ },
+ "value": "[resourceId('Microsoft.Network/privateDnsZones', parameters('name'))]"
+ },
+ "location": {
+ "type": "string",
+ "metadata": {
+ "description": "The location the resource was deployed into."
+ },
+ "value": "[reference('privateDnsZone', '2020-06-01', 'full').location]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "virtualNetwork"
+ ]
+ },
+ "aiFoundryAiServices": {
+ "condition": "[not(variables('useExistingAiFoundryAiProject'))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[take(format('avm.res.cognitive-services.account.{0}', variables('aiFoundryAiServicesResourceName')), 64)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[variables('aiFoundryAiServicesResourceName')]"
+ },
+ "location": {
+ "value": "[variables('aiServiceLocation')]"
+ },
+ "tags": {
+ "value": "[parameters('tags')]"
+ },
+ "sku": {
+ "value": "S0"
+ },
+ "kind": {
+ "value": "AIServices"
+ },
+ "disableLocalAuth": {
+ "value": true
+ },
+ "allowProjectManagement": {
+ "value": true
+ },
+ "customSubDomainName": {
+ "value": "[variables('aiFoundryAiServicesResourceName')]"
+ },
+ "restrictOutboundNetworkAccess": {
+ "value": false
+ },
+ "deployments": {
+ "copy": [
+ {
+ "name": "value",
+ "count": "[length(variables('aiFoundryAiServicesModelDeployment'))]",
+ "input": "[createObject('name', variables('aiFoundryAiServicesModelDeployment')[copyIndex('value')].name, 'model', createObject('format', variables('aiFoundryAiServicesModelDeployment')[copyIndex('value')].format, 'name', variables('aiFoundryAiServicesModelDeployment')[copyIndex('value')].name, 'version', variables('aiFoundryAiServicesModelDeployment')[copyIndex('value')].version), 'raiPolicyName', variables('aiFoundryAiServicesModelDeployment')[copyIndex('value')].raiPolicyName, 'sku', createObject('name', variables('aiFoundryAiServicesModelDeployment')[copyIndex('value')].sku.name, 'capacity', variables('aiFoundryAiServicesModelDeployment')[copyIndex('value')].sku.capacity))]"
+ }
+ ]
+ },
+ "networkAcls": {
+ "value": {
+ "defaultAction": "Allow",
+ "virtualNetworkRules": [],
+ "ipRules": []
+ }
+ },
+ "managedIdentities": {
+ "value": {
+ "userAssignedResourceIds": [
+ "[reference('userAssignedIdentity').outputs.resourceId.value]"
+ ]
+ }
+ },
+ "roleAssignments": {
+ "value": [
+ {
+ "roleDefinitionIdOrName": "53ca6127-db72-4b80-b1b0-d745d6d5456d",
+ "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]",
+ "principalType": "ServicePrincipal"
+ },
+ {
+ "roleDefinitionIdOrName": "64702f94-c441-49e6-a78b-ef80e0188fee",
+ "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]",
+ "principalType": "ServicePrincipal"
+ },
+ {
+ "roleDefinitionIdOrName": "5e0bd9bd-7b93-4f28-af87-19fc36ad61bd",
+ "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]",
+ "principalType": "ServicePrincipal"
+ },
+ {
+ "roleDefinitionIdOrName": "53ca6127-db72-4b80-b1b0-d745d6d5456d",
+ "principalId": "[deployer().objectId]",
+ "principalType": "User"
+ }
+ ]
+ },
+ "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), if(parameters('enableMonitoring'), reference('logAnalyticsWorkspace').outputs.resourceId.value, ''))))), createObject('value', null()))]",
+ "publicNetworkAccess": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Disabled'), createObject('value', 'Enabled'))]"
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.33.27573",
+ "templateHash": "7191594406492701501"
+ },
+ "name": "Cognitive Services",
+ "description": "This module deploys a Cognitive Service."
+ },
+ "definitions": {
+ "privateEndpointOutputType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the private endpoint."
+ }
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the private endpoint."
+ }
+ },
+ "groupId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "The group Id for the private endpoint Group."
+ }
+ },
+ "customDnsConfigs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "fqdn": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "FQDN that resolves to private endpoint IP address."
+ }
+ },
+ "ipAddresses": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "A list of private IP addresses of the private endpoint."
+ }
+ }
+ }
+ },
+ "metadata": {
+ "description": "The custom DNS configurations of the private endpoint."
+ }
+ },
+ "networkInterfaceResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "The IDs of the network interfaces associated with the private endpoint."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for the private endpoint output."
+ }
+ },
+ "deploymentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the name of cognitive service account deployment."
+ }
+ },
+ "model": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of Cognitive Services account deployment model."
+ }
+ },
+ "format": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The format of Cognitive Services account deployment model."
+ }
+ },
+ "version": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The version of Cognitive Services account deployment model."
+ }
+ }
+ },
+ "metadata": {
+ "description": "Required. Properties of Cognitive Services account deployment model."
+ }
+ },
+ "sku": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the resource model definition representing SKU."
+ }
+ },
+ "capacity": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The capacity of the resource model definition representing SKU."
+ }
+ },
+ "tier": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The tier of the resource model definition representing SKU."
+ }
+ },
+ "size": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The size of the resource model definition representing SKU."
+ }
+ },
+ "family": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The family of the resource model definition representing SKU."
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource model definition representing SKU."
+ }
+ },
+ "raiPolicyName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of RAI policy."
+ }
+ },
+ "versionUpgradeOption": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The version upgrade option."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for a cognitive services account deployment."
+ }
+ },
+ "endpointType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Type of the endpoint."
+ }
+ },
+ "endpoint": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "The endpoint URI."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for a cognitive services account endpoint."
+ }
+ },
+ "secretsExportConfigurationType": {
+ "type": "object",
+ "properties": {
+ "keyVaultResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The key vault name where to store the keys and connection strings generated by the modules."
+ }
+ },
+ "accessKey1Name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name for the accessKey1 secret to create."
+ }
+ },
+ "accessKey2Name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name for the accessKey2 secret to create."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of the secrets exported to the provided Key Vault."
+ }
+ },
+ "commitmentPlanType": {
+ "type": "object",
+ "properties": {
+ "autoRenew": {
+ "type": "bool",
+ "metadata": {
+ "description": "Required. Whether the plan should auto-renew at the end of the current commitment period."
+ }
+ },
+ "current": {
+ "type": "object",
+ "properties": {
+ "count": {
+ "type": "int",
+ "metadata": {
+ "description": "Required. The number of committed instances (e.g., number of containers or cores)."
+ }
+ },
+ "tier": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The tier of the commitment plan (e.g., T1, T2)."
+ }
+ }
+ },
+ "metadata": {
+ "description": "Required. The current commitment configuration."
+ }
+ },
+ "hostingModel": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The hosting model for the commitment plan. (e.g., DisconnectedContainer, ConnectedContainer, ProvisionedWeb, Web)."
+ }
+ },
+ "planType": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The plan type indicating which capability the plan applies to (e.g., NTTS, STT, CUSTOMSTT, ADDON)."
+ }
+ },
+ "commitmentPlanGuid": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The unique identifier of an existing commitment plan to update. Set to null to create a new plan."
+ }
+ },
+ "next": {
+ "type": "object",
+ "properties": {
+ "count": {
+ "type": "int",
+ "metadata": {
+ "description": "Required. The number of committed instances for the next period."
+ }
+ },
+ "tier": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The tier for the next commitment period."
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The configuration of the next commitment period, if scheduled."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for a disconnected container commitment plan."
+ }
+ },
+ "networkInjectionType": {
+ "type": "object",
+ "properties": {
+ "scenario": {
+ "type": "string",
+ "allowedValues": [
+ "agent",
+ "none"
+ ],
+ "metadata": {
+ "description": "Required. The scenario for the network injection."
+ }
+ },
+ "subnetResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The Resource ID of the subnet on the Virtual Network on which to inject."
+ }
+ },
+ "useMicrosoftManagedNetwork": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Whether to use Microsoft Managed Network. Defaults to false."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "Type for network configuration in AI Foundry where virtual network injection occurs to secure scenarios like Agents entirely within a private network."
+ }
+ },
+ "_1.secretSetOutputType": {
+ "type": "object",
+ "properties": {
+ "secretResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resourceId of the exported secret."
+ }
+ },
+ "secretUri": {
+ "type": "string",
+ "metadata": {
+ "description": "The secret URI of the exported secret."
+ }
+ },
+ "secretUriWithVersion": {
+ "type": "string",
+ "metadata": {
+ "description": "The secret URI with version of the exported secret."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for the output of the secret set via the secrets export feature.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0"
+ }
+ }
+ },
+ "_2.lockType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the name of lock."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "CanNotDelete",
+ "None",
+ "ReadOnly"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ },
+ "notes": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the notes of the lock."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a lock.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "_2.privateEndpointCustomDnsConfigType": {
+ "type": "object",
+ "properties": {
+ "fqdn": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. FQDN that resolves to private endpoint IP address."
+ }
+ },
+ "ipAddresses": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of private IP addresses of the private endpoint."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "_2.privateEndpointIpConfigurationType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the resource that is unique within a resource group."
+ }
+ },
+ "properties": {
+ "type": "object",
+ "properties": {
+ "groupId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to."
+ }
+ },
+ "memberName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to."
+ }
+ },
+ "privateIPAddress": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. A private IP address obtained from the private endpoint's subnet."
+ }
+ }
+ },
+ "metadata": {
+ "description": "Required. Properties of private endpoint IP configurations."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "_2.privateEndpointPrivateDnsZoneGroupType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the Private DNS Zone Group."
+ }
+ },
+ "privateDnsZoneGroupConfigs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private DNS Zone Group config."
+ }
+ },
+ "privateDnsZoneResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource id of the private DNS zone."
+ }
+ }
+ }
+ },
+ "metadata": {
+ "description": "Required. The private DNS Zone Groups to associate the Private Endpoint. A DNS Zone Group can support up to 5 DNS zones."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "_2.roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "customerManagedKeyType": {
+ "type": "object",
+ "properties": {
+ "keyVaultResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource ID of a key vault to reference a customer managed key for encryption from."
+ }
+ },
+ "keyName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the customer managed key to use for encryption."
+ }
+ },
+ "keyVersion": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The version of the customer managed key to reference for encryption. If not provided, the deployment will use the latest version available at deployment time."
+ }
+ },
+ "userAssignedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. User assigned identity to use when fetching the customer managed key. Required if no system assigned identity is available for use."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a customer-managed key. To be used if the resource type does not support auto-rotation of the customer-managed key.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0"
+ }
+ }
+ },
+ "diagnosticSettingFullType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the diagnostic setting."
+ }
+ },
+ "logCategoriesAndGroups": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here."
+ }
+ },
+ "categoryGroup": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection."
+ }
+ },
+ "metricCategories": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection."
+ }
+ },
+ "logAnalyticsDestinationType": {
+ "type": "string",
+ "allowedValues": [
+ "AzureDiagnostics",
+ "Dedicated"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type."
+ }
+ },
+ "workspaceResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "storageAccountResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "eventHubAuthorizationRuleResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to."
+ }
+ },
+ "eventHubName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "marketplacePartnerResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0"
+ }
+ }
+ },
+ "lockType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the name of lock."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "CanNotDelete",
+ "None",
+ "ReadOnly"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ },
+ "notes": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the notes of the lock."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a lock.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0"
+ }
+ }
+ },
+ "managedIdentityAllType": {
+ "type": "object",
+ "properties": {
+ "systemAssigned": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enables system assigned managed identity on the resource."
+ }
+ },
+ "userAssignedResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID(s) to assign to the resource. Required if a user assigned identity is used for encryption."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0"
+ }
+ }
+ },
+ "privateEndpointSingleServiceType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the Private Endpoint."
+ }
+ },
+ "location": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The location to deploy the Private Endpoint to."
+ }
+ },
+ "privateLinkServiceConnectionName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private link connection to create."
+ }
+ },
+ "service": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The subresource to deploy the Private Endpoint for. For example \"vault\" for a Key Vault Private Endpoint."
+ }
+ },
+ "subnetResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Resource ID of the subnet where the endpoint needs to be created."
+ }
+ },
+ "resourceGroupResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID of the Resource Group the Private Endpoint will be created in. If not specified, the Resource Group of the provided Virtual Network Subnet is used."
+ }
+ },
+ "privateDnsZoneGroup": {
+ "$ref": "#/definitions/_2.privateEndpointPrivateDnsZoneGroupType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The private DNS Zone Group to configure for the Private Endpoint."
+ }
+ },
+ "isManualConnection": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. If Manual Private Link Connection is required."
+ }
+ },
+ "manualConnectionRequestMessage": {
+ "type": "string",
+ "nullable": true,
+ "maxLength": 140,
+ "metadata": {
+ "description": "Optional. A message passed to the owner of the remote resource with the manual connection request."
+ }
+ },
+ "customDnsConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/_2.privateEndpointCustomDnsConfigType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Custom DNS configurations."
+ }
+ },
+ "ipConfigurations": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/_2.privateEndpointIpConfigurationType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A list of IP configurations of the Private Endpoint. This will be used to map to the first-party Service endpoints."
+ }
+ },
+ "applicationSecurityGroupResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Application security groups in which the Private Endpoint IP configuration is included."
+ }
+ },
+ "customNetworkInterfaceName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The custom name of the network interface attached to the Private Endpoint."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/_2.lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/_2.roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "nullable": true,
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-07-01#properties/tags"
+ },
+ "description": "Optional. Tags to be applied on all resources/Resource Groups in this deployment."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a private endpoint. To be used if the private endpoint's default service / groupId can be assumed (i.e., for services that only have one Private Endpoint type like 'vault' for key vault).",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0"
+ }
+ }
+ },
+ "secretsOutputType": {
+ "type": "object",
+ "properties": {},
+ "additionalProperties": {
+ "$ref": "#/definitions/_1.secretSetOutputType",
+ "metadata": {
+ "description": "An exported secret's references."
+ }
+ },
+ "metadata": {
+ "description": "A map of the exported secrets",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of Cognitive Services account."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "AIServices",
+ "AnomalyDetector",
+ "CognitiveServices",
+ "ComputerVision",
+ "ContentModerator",
+ "ContentSafety",
+ "ConversationalLanguageUnderstanding",
+ "CustomVision.Prediction",
+ "CustomVision.Training",
+ "Face",
+ "FormRecognizer",
+ "HealthInsights",
+ "ImmersiveReader",
+ "Internal.AllInOne",
+ "LUIS",
+ "LUIS.Authoring",
+ "LanguageAuthoring",
+ "MetricsAdvisor",
+ "OpenAI",
+ "Personalizer",
+ "QnAMaker.v2",
+ "SpeechServices",
+ "TextAnalytics",
+ "TextTranslation"
+ ],
+ "metadata": {
+ "description": "Required. Kind of the Cognitive Services account. Use 'Get-AzCognitiveServicesAccountSku' to determine a valid combinations of 'kind' and 'SKU' for your Azure region."
+ }
+ },
+ "sku": {
+ "type": "string",
+ "defaultValue": "S0",
+ "allowedValues": [
+ "C2",
+ "C3",
+ "C4",
+ "F0",
+ "F1",
+ "S",
+ "S0",
+ "S1",
+ "S10",
+ "S2",
+ "S3",
+ "S4",
+ "S5",
+ "S6",
+ "S7",
+ "S8",
+ "S9",
+ "DC0"
+ ],
+ "metadata": {
+ "description": "Optional. SKU of the Cognitive Services account. Use 'Get-AzCognitiveServicesAccountSku' to determine a valid combinations of 'kind' and 'SKU' for your Azure region."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Optional. Location for all Resources."
+ }
+ },
+ "diagnosticSettings": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/diagnosticSettingFullType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The diagnostic settings of the service."
+ }
+ },
+ "publicNetworkAccess": {
+ "type": "string",
+ "nullable": true,
+ "allowedValues": [
+ "Enabled",
+ "Disabled"
+ ],
+ "metadata": {
+ "description": "Optional. Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set and networkAcls are not set."
+ }
+ },
+ "customSubDomainName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Conditional. Subdomain name used for token-based authentication. Required if 'networkAcls' or 'privateEndpoints' are set."
+ }
+ },
+ "networkAcls": {
+ "type": "object",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A collection of rules governing the accessibility from specific network locations."
+ }
+ },
+ "networkInjections": {
+ "$ref": "#/definitions/networkInjectionType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specifies in AI Foundry where virtual network injection occurs to secure scenarios like Agents entirely within a private network."
+ }
+ },
+ "privateEndpoints": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateEndpointSingleServiceType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The lock settings of the service."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Tags of the resource."
+ }
+ },
+ "allowedFqdnList": {
+ "type": "array",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. List of allowed FQDN."
+ }
+ },
+ "apiProperties": {
+ "type": "object",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The API properties for special APIs."
+ }
+ },
+ "disableLocalAuth": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Allow only Azure AD authentication. Should be enabled for security reasons."
+ }
+ },
+ "customerManagedKey": {
+ "$ref": "#/definitions/customerManagedKeyType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The customer managed key definition."
+ }
+ },
+ "dynamicThrottlingEnabled": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. The flag to enable dynamic throttling."
+ }
+ },
+ "migrationToken": {
+ "type": "securestring",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource migration token."
+ }
+ },
+ "restore": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Restore a soft-deleted cognitive service at deployment time. Will fail if no such soft-deleted resource exists."
+ }
+ },
+ "restrictOutboundNetworkAccess": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Restrict outbound network access."
+ }
+ },
+ "userOwnedStorage": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.CognitiveServices/accounts@2025-04-01-preview#properties/properties/properties/userOwnedStorage"
+ },
+ "description": "Optional. The storage accounts for this resource."
+ },
+ "nullable": true
+ },
+ "managedIdentities": {
+ "$ref": "#/definitions/managedIdentityAllType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The managed identity definition for this resource."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ },
+ "deployments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/deploymentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of deployments about cognitive service accounts to create."
+ }
+ },
+ "secretsExportConfiguration": {
+ "$ref": "#/definitions/secretsExportConfigurationType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Key vault reference and secret settings for the module's secrets export."
+ }
+ },
+ "allowProjectManagement": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable project management feature for AI Foundry."
+ }
+ },
+ "commitmentPlans": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/commitmentPlanType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Commitment plans to deploy for the cognitive services account."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "enableReferencedModulesTelemetry": false,
+ "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]",
+ "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'SystemAssigned, UserAssigned', 'SystemAssigned'), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', null())), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]",
+ "builtInRoleNames": {
+ "Cognitive Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68')]",
+ "Cognitive Services Custom Vision Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c1ff6cc2-c111-46fe-8896-e0ef812ad9f3')]",
+ "Cognitive Services Custom Vision Deployment": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5c4089e1-6d96-4d2f-b296-c1bc7137275f')]",
+ "Cognitive Services Custom Vision Labeler": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '88424f51-ebe7-446f-bc41-7fa16989e96c')]",
+ "Cognitive Services Custom Vision Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '93586559-c37d-4a6b-ba08-b9f0940c2d73')]",
+ "Cognitive Services Custom Vision Trainer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0a5ae4ab-0d65-4eeb-be61-29fc9b54394b')]",
+ "Cognitive Services Data Reader (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b59867f0-fa02-499b-be73-45a86b5b3e1c')]",
+ "Cognitive Services Face Recognizer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '9894cab4-e18a-44aa-828b-cb588cd6f2d7')]",
+ "Cognitive Services Immersive Reader User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b2de6794-95db-4659-8781-7e080d3f2b9d')]",
+ "Cognitive Services Language Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f07febfe-79bc-46b1-8b37-790e26e6e498')]",
+ "Cognitive Services Language Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7628b7b8-a8b2-4cdc-b46f-e9b35248918e')]",
+ "Cognitive Services Language Writer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f2310ca1-dc64-4889-bb49-c8e0fa3d47a8')]",
+ "Cognitive Services LUIS Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f72c8140-2111-481c-87ff-72b910f6e3f8')]",
+ "Cognitive Services LUIS Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18e81cdc-4e98-4e29-a639-e7d10c5a6226')]",
+ "Cognitive Services LUIS Writer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '6322a993-d5c9-4bed-b113-e49bbea25b27')]",
+ "Cognitive Services Metrics Advisor Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'cb43c632-a144-4ec5-977c-e80c4affc34a')]",
+ "Cognitive Services Metrics Advisor User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '3b20f47b-3825-43cb-8114-4bd2201156a8')]",
+ "Cognitive Services OpenAI Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a001fd3d-188f-4b5d-821b-7da978bf7442')]",
+ "Cognitive Services OpenAI User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')]",
+ "Cognitive Services QnA Maker Editor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f4cc2bf9-21be-47a1-bdf1-5c5804381025')]",
+ "Cognitive Services QnA Maker Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '466ccd10-b268-4a11-b098-b4849f024126')]",
+ "Cognitive Services Speech Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0e75ca1e-0464-4b4d-8b93-68208a576181')]",
+ "Cognitive Services Speech User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f2dc8367-1007-4938-bd23-fe263f013447')]",
+ "Cognitive Services User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a97b65f3-24c7-4388-baec-2e87135dc908')]",
+ "Azure AI Developer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '64702f94-c441-49e6-a78b-ef80e0188fee')]",
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ },
+ "isHSMManagedCMK": "[equals(tryGet(split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), ''), '/'), 7), 'managedHSMs')]"
+ },
+ "resources": {
+ "cMKKeyVault::cMKKey": {
+ "condition": "[and(and(not(empty(parameters('customerManagedKey'))), not(variables('isHSMManagedCMK'))), and(not(empty(parameters('customerManagedKey'))), not(variables('isHSMManagedCMK'))))]",
+ "existing": true,
+ "type": "Microsoft.KeyVault/vaults/keys",
+ "apiVersion": "2025-05-01",
+ "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[2]]",
+ "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[4]]",
+ "name": "[format('{0}/{1}', last(split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')), tryGet(parameters('customerManagedKey'), 'keyName'))]"
+ },
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.cognitiveservices-account.{0}.{1}', replace('0.14.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "cMKKeyVault": {
+ "condition": "[and(not(empty(parameters('customerManagedKey'))), not(variables('isHSMManagedCMK')))]",
+ "existing": true,
+ "type": "Microsoft.KeyVault/vaults",
+ "apiVersion": "2025-05-01",
+ "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[2]]",
+ "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[4]]",
+ "name": "[last(split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/'))]"
+ },
+ "cMKUserAssignedIdentity": {
+ "condition": "[not(empty(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId')))]",
+ "existing": true,
+ "type": "Microsoft.ManagedIdentity/userAssignedIdentities",
+ "apiVersion": "2025-01-31-preview",
+ "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[2]]",
+ "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[4]]",
+ "name": "[last(split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/'))]"
+ },
+ "cognitiveService": {
+ "type": "Microsoft.CognitiveServices/accounts",
+ "apiVersion": "2025-06-01",
+ "name": "[parameters('name')]",
+ "kind": "[parameters('kind')]",
+ "identity": "[variables('identity')]",
+ "location": "[parameters('location')]",
+ "tags": "[parameters('tags')]",
+ "sku": {
+ "name": "[parameters('sku')]"
+ },
+ "properties": {
+ "allowProjectManagement": "[parameters('allowProjectManagement')]",
+ "customSubDomainName": "[parameters('customSubDomainName')]",
+ "networkAcls": "[if(not(empty(coalesce(parameters('networkAcls'), createObject()))), createObject('defaultAction', tryGet(parameters('networkAcls'), 'defaultAction'), 'virtualNetworkRules', coalesce(tryGet(parameters('networkAcls'), 'virtualNetworkRules'), createArray()), 'ipRules', coalesce(tryGet(parameters('networkAcls'), 'ipRules'), createArray())), null())]",
+ "networkInjections": "[if(not(empty(parameters('networkInjections'))), createArray(createObject('scenario', tryGet(parameters('networkInjections'), 'scenario'), 'subnetArmId', tryGet(parameters('networkInjections'), 'subnetResourceId'), 'useMicrosoftManagedNetwork', coalesce(tryGet(parameters('networkInjections'), 'useMicrosoftManagedNetwork'), false()))), null())]",
+ "publicNetworkAccess": "[if(not(equals(parameters('publicNetworkAccess'), null())), parameters('publicNetworkAccess'), if(not(empty(parameters('networkAcls'))), 'Enabled', 'Disabled'))]",
+ "allowedFqdnList": "[parameters('allowedFqdnList')]",
+ "apiProperties": "[parameters('apiProperties')]",
+ "disableLocalAuth": "[parameters('disableLocalAuth')]",
+ "encryption": "[if(not(empty(parameters('customerManagedKey'))), createObject('keySource', 'Microsoft.KeyVault', 'keyVaultProperties', createObject('identityClientId', if(not(empty(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), ''))), reference('cMKUserAssignedIdentity').clientId, null()), 'keyVaultUri', if(not(variables('isHSMManagedCMK')), reference('cMKKeyVault').vaultUri, format('https://{0}.managedhsm.azure.net/', last(split(parameters('customerManagedKey').keyVaultResourceId, '/')))), 'keyName', parameters('customerManagedKey').keyName, 'keyVersion', if(not(empty(tryGet(parameters('customerManagedKey'), 'keyVersion'))), parameters('customerManagedKey').keyVersion, if(not(variables('isHSMManagedCMK')), last(split(reference('cMKKeyVault::cMKKey').keyUriWithVersion, '/')), fail('Managed HSM CMK encryption requires specifying the ''keyVersion''.'))))), null())]",
+ "migrationToken": "[parameters('migrationToken')]",
+ "restore": "[parameters('restore')]",
+ "restrictOutboundNetworkAccess": "[parameters('restrictOutboundNetworkAccess')]",
+ "userOwnedStorage": "[if(not(empty(parameters('userOwnedStorage'))), parameters('userOwnedStorage'), null())]",
+ "dynamicThrottlingEnabled": "[parameters('dynamicThrottlingEnabled')]"
+ },
+ "dependsOn": [
+ "cMKKeyVault",
+ "cMKKeyVault::cMKKey",
+ "cMKUserAssignedIdentity"
+ ]
+ },
+ "cognitiveService_deployments": {
+ "copy": {
+ "name": "cognitiveService_deployments",
+ "count": "[length(coalesce(parameters('deployments'), createArray()))]",
+ "mode": "serial",
+ "batchSize": 1
+ },
+ "type": "Microsoft.CognitiveServices/accounts/deployments",
+ "apiVersion": "2025-06-01",
+ "name": "[format('{0}/{1}', parameters('name'), coalesce(tryGet(coalesce(parameters('deployments'), createArray())[copyIndex()], 'name'), format('{0}-deployments', parameters('name'))))]",
+ "properties": {
+ "model": "[coalesce(parameters('deployments'), createArray())[copyIndex()].model]",
+ "raiPolicyName": "[tryGet(coalesce(parameters('deployments'), createArray())[copyIndex()], 'raiPolicyName')]",
+ "versionUpgradeOption": "[tryGet(coalesce(parameters('deployments'), createArray())[copyIndex()], 'versionUpgradeOption')]"
+ },
+ "sku": "[coalesce(tryGet(coalesce(parameters('deployments'), createArray())[copyIndex()], 'sku'), createObject('name', parameters('sku'), 'capacity', tryGet(parameters('sku'), 'capacity'), 'tier', tryGet(parameters('sku'), 'tier'), 'size', tryGet(parameters('sku'), 'size'), 'family', tryGet(parameters('sku'), 'family')))]",
+ "dependsOn": [
+ "cognitiveService"
+ ]
+ },
+ "cognitiveService_lock": {
+ "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]",
+ "type": "Microsoft.Authorization/locks",
+ "apiVersion": "2020-05-01",
+ "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]",
+ "properties": {
+ "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]",
+ "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]"
+ },
+ "dependsOn": [
+ "cognitiveService"
+ ]
+ },
+ "cognitiveService_commitmentPlans": {
+ "copy": {
+ "name": "cognitiveService_commitmentPlans",
+ "count": "[length(coalesce(parameters('commitmentPlans'), createArray()))]"
+ },
+ "type": "Microsoft.CognitiveServices/accounts/commitmentPlans",
+ "apiVersion": "2025-06-01",
+ "name": "[format('{0}/{1}', parameters('name'), format('{0}-{1}', coalesce(parameters('commitmentPlans'), createArray())[copyIndex()].hostingModel, coalesce(parameters('commitmentPlans'), createArray())[copyIndex()].planType))]",
+ "properties": "[coalesce(parameters('commitmentPlans'), createArray())[copyIndex()]]",
+ "dependsOn": [
+ "cognitiveService"
+ ]
+ },
+ "cognitiveService_diagnosticSettings": {
+ "copy": {
+ "name": "cognitiveService_diagnosticSettings",
+ "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]"
+ },
+ "type": "Microsoft.Insights/diagnosticSettings",
+ "apiVersion": "2021-05-01-preview",
+ "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]",
+ "properties": {
+ "copy": [
+ {
+ "name": "metrics",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]",
+ "input": {
+ "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]",
+ "timeGrain": null
+ }
+ },
+ {
+ "name": "logs",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]",
+ "input": {
+ "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]",
+ "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]"
+ }
+ }
+ ],
+ "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]",
+ "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]",
+ "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]",
+ "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]",
+ "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]",
+ "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]"
+ },
+ "dependsOn": [
+ "cognitiveService"
+ ]
+ },
+ "cognitiveService_roleAssignments": {
+ "copy": {
+ "name": "cognitiveService_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "cognitiveService"
+ ]
+ },
+ "cognitiveService_privateEndpoints": {
+ "copy": {
+ "name": "cognitiveService_privateEndpoints",
+ "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-cognitiveService-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]",
+ "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]",
+ "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account'), copyIndex()))]"
+ },
+ "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account')))))), createObject('value', null()))]",
+ "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account')), 'requestMessage', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualConnectionRequestMessage'), 'Manual approval required.'))))), createObject('value', null()))]",
+ "subnetResourceId": {
+ "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]"
+ },
+ "enableTelemetry": {
+ "value": "[variables('enableReferencedModulesTelemetry')]"
+ },
+ "location": {
+ "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]"
+ },
+ "lock": {
+ "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]"
+ },
+ "privateDnsZoneGroup": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroup')]"
+ },
+ "roleAssignments": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]"
+ },
+ "tags": {
+ "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]"
+ },
+ "customDnsConfigs": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]"
+ },
+ "ipConfigurations": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]"
+ },
+ "applicationSecurityGroupResourceIds": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]"
+ },
+ "customNetworkInterfaceName": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.5.1644",
+ "templateHash": "16604612898799598358"
+ },
+ "name": "Private Endpoints",
+ "description": "This module deploys a Private Endpoint."
+ },
+ "definitions": {
+ "privateDnsZoneGroupType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the Private DNS Zone Group."
+ }
+ },
+ "privateDnsZoneGroupConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateDnsZoneGroupConfigType"
+ },
+ "metadata": {
+ "description": "Required. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of a private dns zone group."
+ }
+ },
+ "lockType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the name of lock."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "CanNotDelete",
+ "None",
+ "ReadOnly"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ },
+ "notes": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the notes of the lock."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a lock.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "privateDnsZoneGroupConfigType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private DNS zone group config."
+ }
+ },
+ "privateDnsZoneResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource id of the private DNS zone."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The type of a private DNS zone group configuration.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "private-dns-zone-group/main.bicep"
+ }
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the private endpoint resource to create."
+ }
+ },
+ "subnetResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Resource ID of the subnet where the endpoint needs to be created."
+ }
+ },
+ "applicationSecurityGroupResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Application security groups in which the private endpoint IP configuration is included."
+ }
+ },
+ "customNetworkInterfaceName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The custom name of the network interface attached to the private endpoint."
+ }
+ },
+ "ipConfigurations": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/ipConfigurations"
+ },
+ "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints."
+ },
+ "nullable": true
+ },
+ "privateDnsZoneGroup": {
+ "$ref": "#/definitions/privateDnsZoneGroupType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The private DNS zone group to configure for the private endpoint."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Optional. Location for all Resources."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The lock settings of the service."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/tags"
+ },
+ "description": "Optional. Tags to be applied on all resources/resource groups in this deployment."
+ },
+ "nullable": true
+ },
+ "customDnsConfigs": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs"
+ },
+ "description": "Optional. Custom DNS configurations."
+ },
+ "nullable": true
+ },
+ "manualPrivateLinkServiceConnections": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/manualPrivateLinkServiceConnections"
+ },
+ "description": "Conditional. A grouping of information about the connection to the remote resource. Used when the network admin does not have access to approve connections to the remote resource. Required if `privateLinkServiceConnections` is empty."
+ },
+ "nullable": true
+ },
+ "privateLinkServiceConnections": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/privateLinkServiceConnections"
+ },
+ "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty."
+ },
+ "nullable": true
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "DNS Resolver Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f2ebee7-ffd4-4fc0-b3b7-664099fdad5d')]",
+ "DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314')]",
+ "Domain Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'eeaeda52-9324-47f6-8069-5d5bade478b2')]",
+ "Domain Services Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '361898ef-9ed1-48c2-849c-a832951106bb')]",
+ "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.11.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "privateEndpoint": {
+ "type": "Microsoft.Network/privateEndpoints",
+ "apiVersion": "2024-10-01",
+ "name": "[parameters('name')]",
+ "location": "[parameters('location')]",
+ "tags": "[parameters('tags')]",
+ "properties": {
+ "copy": [
+ {
+ "name": "applicationSecurityGroups",
+ "count": "[length(coalesce(parameters('applicationSecurityGroupResourceIds'), createArray()))]",
+ "input": {
+ "id": "[coalesce(parameters('applicationSecurityGroupResourceIds'), createArray())[copyIndex('applicationSecurityGroups')]]"
+ }
+ }
+ ],
+ "customDnsConfigs": "[coalesce(parameters('customDnsConfigs'), createArray())]",
+ "customNetworkInterfaceName": "[coalesce(parameters('customNetworkInterfaceName'), '')]",
+ "ipConfigurations": "[coalesce(parameters('ipConfigurations'), createArray())]",
+ "manualPrivateLinkServiceConnections": "[coalesce(parameters('manualPrivateLinkServiceConnections'), createArray())]",
+ "privateLinkServiceConnections": "[coalesce(parameters('privateLinkServiceConnections'), createArray())]",
+ "subnet": {
+ "id": "[parameters('subnetResourceId')]"
+ }
+ }
+ },
+ "privateEndpoint_lock": {
+ "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]",
+ "type": "Microsoft.Authorization/locks",
+ "apiVersion": "2020-05-01",
+ "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]",
+ "properties": {
+ "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]",
+ "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]"
+ },
+ "dependsOn": [
+ "privateEndpoint"
+ ]
+ },
+ "privateEndpoint_roleAssignments": {
+ "copy": {
+ "name": "privateEndpoint_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateEndpoints', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "privateEndpoint"
+ ]
+ },
+ "privateEndpoint_privateDnsZoneGroup": {
+ "condition": "[not(empty(parameters('privateDnsZoneGroup')))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[tryGet(parameters('privateDnsZoneGroup'), 'name')]"
+ },
+ "privateEndpointName": {
+ "value": "[parameters('name')]"
+ },
+ "privateDnsZoneConfigs": {
+ "value": "[parameters('privateDnsZoneGroup').privateDnsZoneGroupConfigs]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.5.1644",
+ "templateHash": "24141742673128945"
+ },
+ "name": "Private Endpoint Private DNS Zone Groups",
+ "description": "This module deploys a Private Endpoint Private DNS Zone Group."
+ },
+ "definitions": {
+ "privateDnsZoneGroupConfigType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private DNS zone group config."
+ }
+ },
+ "privateDnsZoneResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource id of the private DNS zone."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of a private DNS zone group configuration."
+ }
+ }
+ },
+ "parameters": {
+ "privateEndpointName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment."
+ }
+ },
+ "privateDnsZoneConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateDnsZoneGroupConfigType"
+ },
+ "minLength": 1,
+ "maxLength": 5,
+ "metadata": {
+ "description": "Required. Array of private DNS zone configurations of the private DNS zone group. A DNS zone group can support up to 5 DNS zones."
+ }
+ },
+ "name": {
+ "type": "string",
+ "defaultValue": "default",
+ "metadata": {
+ "description": "Optional. The name of the private DNS zone group."
+ }
+ }
+ },
+ "resources": {
+ "privateEndpoint": {
+ "existing": true,
+ "type": "Microsoft.Network/privateEndpoints",
+ "apiVersion": "2024-10-01",
+ "name": "[parameters('privateEndpointName')]"
+ },
+ "privateDnsZoneGroup": {
+ "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups",
+ "apiVersion": "2024-10-01",
+ "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]",
+ "properties": {
+ "copy": [
+ {
+ "name": "privateDnsZoneConfigs",
+ "count": "[length(parameters('privateDnsZoneConfigs'))]",
+ "input": {
+ "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')].privateDnsZoneResourceId, '/')))]",
+ "properties": {
+ "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')].privateDnsZoneResourceId]"
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the private endpoint DNS zone group."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the private endpoint DNS zone group."
+ },
+ "value": "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', parameters('privateEndpointName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the private endpoint DNS zone group was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "privateEndpoint"
+ ]
+ }
+ },
+ "outputs": {
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the private endpoint was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the private endpoint."
+ },
+ "value": "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]"
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the private endpoint."
+ },
+ "value": "[parameters('name')]"
+ },
+ "location": {
+ "type": "string",
+ "metadata": {
+ "description": "The location the resource was deployed into."
+ },
+ "value": "[reference('privateEndpoint', '2024-10-01', 'full').location]"
+ },
+ "customDnsConfigs": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs",
+ "output": true
+ },
+ "description": "The custom DNS configurations of the private endpoint."
+ },
+ "value": "[reference('privateEndpoint').customDnsConfigs]"
+ },
+ "networkInterfaceResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "The resource IDs of the network interfaces associated with the private endpoint."
+ },
+ "value": "[map(reference('privateEndpoint').networkInterfaces, lambda('nic', lambdaVariables('nic').id))]"
+ },
+ "groupId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "The group Id for the private endpoint Group."
+ },
+ "value": "[coalesce(tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'manualPrivateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0), tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'privateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0))]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "cognitiveService"
+ ]
+ },
+ "secretsExport": {
+ "condition": "[not(equals(parameters('secretsExportConfiguration'), null()))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-secrets-kv', uniqueString(deployment().name, parameters('location')))]",
+ "subscriptionId": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[2]]",
+ "resourceGroup": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[4]]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "keyVaultName": {
+ "value": "[last(split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/'))]"
+ },
+ "secretsToSet": {
+ "value": "[union(createArray(), if(contains(parameters('secretsExportConfiguration'), 'accessKey1Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'accessKey1Name'), 'value', listKeys('cognitiveService', '2025-06-01').key1)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'accessKey2Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'accessKey2Name'), 'value', listKeys('cognitiveService', '2025-06-01').key2)), createArray()))]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.33.27573",
+ "templateHash": "1394089926798493893"
+ }
+ },
+ "definitions": {
+ "secretSetOutputType": {
+ "type": "object",
+ "properties": {
+ "secretResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resourceId of the exported secret."
+ }
+ },
+ "secretUri": {
+ "type": "string",
+ "metadata": {
+ "description": "The secret URI of the exported secret."
+ }
+ },
+ "secretUriWithVersion": {
+ "type": "string",
+ "metadata": {
+ "description": "The secret URI with version of the exported secret."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for the output of the secret set via the secrets export feature.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "secretToSetType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the secret to set."
+ }
+ },
+ "value": {
+ "type": "securestring",
+ "metadata": {
+ "description": "Required. The value of the secret to set."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for the secret to set via the secrets export feature.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "keyVaultName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the Key Vault to set the ecrets in."
+ }
+ },
+ "secretsToSet": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/secretToSetType"
+ },
+ "metadata": {
+ "description": "Required. The secrets to set in the Key Vault."
+ }
+ }
+ },
+ "resources": {
+ "keyVault": {
+ "existing": true,
+ "type": "Microsoft.KeyVault/vaults",
+ "apiVersion": "2025-05-01",
+ "name": "[parameters('keyVaultName')]"
+ },
+ "secrets": {
+ "copy": {
+ "name": "secrets",
+ "count": "[length(parameters('secretsToSet'))]"
+ },
+ "type": "Microsoft.KeyVault/vaults/secrets",
+ "apiVersion": "2025-05-01",
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretsToSet')[copyIndex()].name)]",
+ "properties": {
+ "value": "[parameters('secretsToSet')[copyIndex()].value]"
+ }
+ }
+ },
+ "outputs": {
+ "secretsSet": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/secretSetOutputType"
+ },
+ "metadata": {
+ "description": "The references to the secrets exported to the provided Key Vault."
+ },
+ "copy": {
+ "count": "[length(range(0, length(coalesce(parameters('secretsToSet'), createArray()))))]",
+ "input": {
+ "secretResourceId": "[resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), parameters('secretsToSet')[range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()]].name)]",
+ "secretUri": "[reference(format('secrets[{0}]', range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()])).secretUri]",
+ "secretUriWithVersion": "[reference(format('secrets[{0}]', range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()])).secretUriWithVersion]"
+ }
+ }
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "cognitiveService"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the cognitive services account."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the cognitive services account."
+ },
+ "value": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the cognitive services account was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "endpoint": {
+ "type": "string",
+ "metadata": {
+ "description": "The service endpoint of the cognitive services account."
+ },
+ "value": "[reference('cognitiveService').endpoint]"
+ },
+ "endpoints": {
+ "$ref": "#/definitions/endpointType",
+ "metadata": {
+ "description": "All endpoints available for the cognitive services account, types depends on the cognitive service kind."
+ },
+ "value": "[reference('cognitiveService').endpoints]"
+ },
+ "systemAssignedMIPrincipalId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "The principal ID of the system assigned identity."
+ },
+ "value": "[tryGet(tryGet(reference('cognitiveService', '2025-06-01', 'full'), 'identity'), 'principalId')]"
+ },
+ "location": {
+ "type": "string",
+ "metadata": {
+ "description": "The location the resource was deployed into."
+ },
+ "value": "[reference('cognitiveService', '2025-06-01', 'full').location]"
+ },
+ "exportedSecrets": {
+ "$ref": "#/definitions/secretsOutputType",
+ "metadata": {
+ "description": "A hashtable of references to the secrets exported to the provided Key Vault. The key of each reference is each secret's name."
+ },
+ "value": "[if(not(equals(parameters('secretsExportConfiguration'), null())), toObject(reference('secretsExport').outputs.secretsSet.value, lambda('secret', last(split(lambdaVariables('secret').secretResourceId, '/'))), lambda('secret', lambdaVariables('secret'))), createObject())]"
+ },
+ "privateEndpoints": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateEndpointOutputType"
+ },
+ "metadata": {
+ "description": "The private endpoints of the congitive services account."
+ },
+ "copy": {
+ "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]",
+ "input": {
+ "name": "[reference(format('cognitiveService_privateEndpoints[{0}]', copyIndex())).outputs.name.value]",
+ "resourceId": "[reference(format('cognitiveService_privateEndpoints[{0}]', copyIndex())).outputs.resourceId.value]",
+ "groupId": "[tryGet(tryGet(reference(format('cognitiveService_privateEndpoints[{0}]', copyIndex())).outputs, 'groupId'), 'value')]",
+ "customDnsConfigs": "[reference(format('cognitiveService_privateEndpoints[{0}]', copyIndex())).outputs.customDnsConfigs.value]",
+ "networkInterfaceResourceIds": "[reference(format('cognitiveService_privateEndpoints[{0}]', copyIndex())).outputs.networkInterfaceResourceIds.value]"
+ }
+ }
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "logAnalyticsWorkspace",
+ "userAssignedIdentity"
+ ]
+ },
+ "aiServicesPrivateEndpoint": {
+ "condition": "[and(not(variables('useExistingAiFoundryAiProject')), parameters('enablePrivateNetworking'))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[take(format('pep-ai-services-{0}', variables('aiFoundryAiServicesResourceName')), 64)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[format('pep-{0}', variables('aiFoundryAiServicesResourceName'))]"
+ },
+ "location": {
+ "value": "[variables('solutionLocation')]"
+ },
+ "tags": {
+ "value": "[parameters('tags')]"
+ },
+ "subnetResourceId": {
+ "value": "[reference('virtualNetwork').outputs.pepsSubnetResourceId.value]"
+ },
+ "privateLinkServiceConnections": {
+ "value": [
+ {
+ "name": "[format('pep-{0}', variables('aiFoundryAiServicesResourceName'))]",
+ "properties": {
+ "privateLinkServiceId": "[reference('aiFoundryAiServices').outputs.resourceId.value]",
+ "groupIds": [
+ "account"
+ ]
+ }
+ }
+ ]
+ },
+ "privateDnsZoneGroup": {
+ "value": {
+ "privateDnsZoneGroupConfigs": [
+ {
+ "name": "cognitiveservices",
+ "privateDnsZoneResourceId": "[reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)).outputs.resourceId.value]"
+ },
+ {
+ "name": "openai",
+ "privateDnsZoneResourceId": "[reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)).outputs.resourceId.value]"
+ }
+ ]
+ }
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.34.44.8038",
+ "templateHash": "12389807800450456797"
+ },
+ "name": "Private Endpoints",
+ "description": "This module deploys a Private Endpoint."
+ },
+ "definitions": {
+ "privateDnsZoneGroupType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the Private DNS Zone Group."
+ }
+ },
+ "privateDnsZoneGroupConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateDnsZoneGroupConfigType"
+ },
+ "metadata": {
+ "description": "Required. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ },
+ "ipConfigurationType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the resource that is unique within a resource group."
+ }
+ },
+ "properties": {
+ "type": "object",
+ "properties": {
+ "groupId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string."
+ }
+ },
+ "memberName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string."
+ }
+ },
+ "privateIPAddress": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. A private IP address obtained from the private endpoint's subnet."
+ }
+ }
+ },
+ "metadata": {
+ "description": "Required. Properties of private endpoint IP configurations."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ },
+ "privateLinkServiceConnectionType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the private link service connection."
+ }
+ },
+ "properties": {
+ "type": "object",
+ "properties": {
+ "groupIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string array `[]`."
+ }
+ },
+ "privateLinkServiceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource id of private link service."
+ }
+ },
+ "requestMessage": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars."
+ }
+ }
+ },
+ "metadata": {
+ "description": "Required. Properties of private link service connection."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ },
+ "customDnsConfigType": {
+ "type": "object",
+ "properties": {
+ "fqdn": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. FQDN that resolves to private endpoint IP address."
+ }
+ },
+ "ipAddresses": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of private IP addresses of the private endpoint."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ },
+ "lockType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the name of lock."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "CanNotDelete",
+ "None",
+ "ReadOnly"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a lock.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ },
+ "privateDnsZoneGroupConfigType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private DNS zone group config."
+ }
+ },
+ "privateDnsZoneResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource id of the private DNS zone."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "private-dns-zone-group/main.bicep"
+ }
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the private endpoint resource to create."
+ }
+ },
+ "subnetResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Resource ID of the subnet where the endpoint needs to be created."
+ }
+ },
+ "applicationSecurityGroupResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Application security groups in which the private endpoint IP configuration is included."
+ }
+ },
+ "customNetworkInterfaceName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The custom name of the network interface attached to the private endpoint."
+ }
+ },
+ "ipConfigurations": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ipConfigurationType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints."
+ }
+ },
+ "privateDnsZoneGroup": {
+ "$ref": "#/definitions/privateDnsZoneGroupType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The private DNS zone group to configure for the private endpoint."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Optional. Location for all Resources."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The lock settings of the service."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Tags to be applied on all resources/resource groups in this deployment."
+ }
+ },
+ "customDnsConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/customDnsConfigType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Custom DNS configurations."
+ }
+ },
+ "manualPrivateLinkServiceConnections": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateLinkServiceConnectionType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Conditional. A grouping of information about the connection to the remote resource. Used when the network admin does not have access to approve connections to the remote resource. Required if `privateLinkServiceConnections` is empty."
+ }
+ },
+ "privateLinkServiceConnections": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateLinkServiceConnectionType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "DNS Resolver Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f2ebee7-ffd4-4fc0-b3b7-664099fdad5d')]",
+ "DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314')]",
+ "Domain Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'eeaeda52-9324-47f6-8069-5d5bade478b2')]",
+ "Domain Services Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '361898ef-9ed1-48c2-849c-a832951106bb')]",
+ "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.11.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "privateEndpoint": {
+ "type": "Microsoft.Network/privateEndpoints",
+ "apiVersion": "2024-05-01",
+ "name": "[parameters('name')]",
+ "location": "[parameters('location')]",
+ "tags": "[parameters('tags')]",
+ "properties": {
+ "copy": [
+ {
+ "name": "applicationSecurityGroups",
+ "count": "[length(coalesce(parameters('applicationSecurityGroupResourceIds'), createArray()))]",
+ "input": {
+ "id": "[coalesce(parameters('applicationSecurityGroupResourceIds'), createArray())[copyIndex('applicationSecurityGroups')]]"
+ }
+ }
+ ],
+ "customDnsConfigs": "[coalesce(parameters('customDnsConfigs'), createArray())]",
+ "customNetworkInterfaceName": "[coalesce(parameters('customNetworkInterfaceName'), '')]",
+ "ipConfigurations": "[coalesce(parameters('ipConfigurations'), createArray())]",
+ "manualPrivateLinkServiceConnections": "[coalesce(parameters('manualPrivateLinkServiceConnections'), createArray())]",
+ "privateLinkServiceConnections": "[coalesce(parameters('privateLinkServiceConnections'), createArray())]",
+ "subnet": {
+ "id": "[parameters('subnetResourceId')]"
+ }
+ }
+ },
+ "privateEndpoint_lock": {
+ "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]",
+ "type": "Microsoft.Authorization/locks",
+ "apiVersion": "2020-05-01",
+ "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]",
+ "properties": {
+ "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]",
+ "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]"
+ },
+ "dependsOn": [
+ "privateEndpoint"
+ ]
+ },
+ "privateEndpoint_roleAssignments": {
+ "copy": {
+ "name": "privateEndpoint_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateEndpoints', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "privateEndpoint"
+ ]
+ },
+ "privateEndpoint_privateDnsZoneGroup": {
+ "condition": "[not(empty(parameters('privateDnsZoneGroup')))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2022-09-01",
+ "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[tryGet(parameters('privateDnsZoneGroup'), 'name')]"
+ },
+ "privateEndpointName": {
+ "value": "[parameters('name')]"
+ },
+ "privateDnsZoneConfigs": {
+ "value": "[parameters('privateDnsZoneGroup').privateDnsZoneGroupConfigs]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.34.44.8038",
+ "templateHash": "13997305779829540948"
+ },
+ "name": "Private Endpoint Private DNS Zone Groups",
+ "description": "This module deploys a Private Endpoint Private DNS Zone Group."
+ },
+ "definitions": {
+ "privateDnsZoneGroupConfigType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private DNS zone group config."
+ }
+ },
+ "privateDnsZoneResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource id of the private DNS zone."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ }
+ },
+ "parameters": {
+ "privateEndpointName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment."
+ }
+ },
+ "privateDnsZoneConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateDnsZoneGroupConfigType"
+ },
+ "minLength": 1,
+ "maxLength": 5,
+ "metadata": {
+ "description": "Required. Array of private DNS zone configurations of the private DNS zone group. A DNS zone group can support up to 5 DNS zones."
+ }
+ },
+ "name": {
+ "type": "string",
+ "defaultValue": "default",
+ "metadata": {
+ "description": "Optional. The name of the private DNS zone group."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "privateDnsZoneConfigsVar",
+ "count": "[length(parameters('privateDnsZoneConfigs'))]",
+ "input": {
+ "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId, '/')))]",
+ "properties": {
+ "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId]"
+ }
+ }
+ }
+ ]
+ },
+ "resources": {
+ "privateEndpoint": {
+ "existing": true,
+ "type": "Microsoft.Network/privateEndpoints",
+ "apiVersion": "2024-05-01",
+ "name": "[parameters('privateEndpointName')]"
+ },
+ "privateDnsZoneGroup": {
+ "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups",
+ "apiVersion": "2024-05-01",
+ "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]",
+ "properties": {
+ "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigsVar')]"
+ }
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the private endpoint DNS zone group."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the private endpoint DNS zone group."
+ },
+ "value": "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', parameters('privateEndpointName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the private endpoint DNS zone group was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "privateEndpoint"
+ ]
+ }
+ },
+ "outputs": {
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the private endpoint was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the private endpoint."
+ },
+ "value": "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]"
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the private endpoint."
+ },
+ "value": "[parameters('name')]"
+ },
+ "location": {
+ "type": "string",
+ "metadata": {
+ "description": "The location the resource was deployed into."
+ },
+ "value": "[reference('privateEndpoint', '2024-05-01', 'full').location]"
+ },
+ "customDnsConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/customDnsConfigType"
+ },
+ "metadata": {
+ "description": "The custom DNS configurations of the private endpoint."
+ },
+ "value": "[reference('privateEndpoint').customDnsConfigs]"
+ },
+ "networkInterfaceResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "The resource IDs of the network interfaces associated with the private endpoint."
+ },
+ "value": "[map(reference('privateEndpoint').networkInterfaces, lambda('nic', lambdaVariables('nic').id))]"
+ },
+ "groupId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "The group Id for the private endpoint Group."
+ },
+ "value": "[coalesce(tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'manualPrivateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0), tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'privateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0))]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "aiFoundryAiServices",
+ "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]",
+ "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)]",
+ "virtualNetwork"
+ ]
+ },
+ "aiFoundryAiServicesProject": {
+ "condition": "[not(variables('useExistingAiFoundryAiProject'))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[take(format('module.ai-project.{0}', variables('aiFoundryAiProjectResourceName')), 64)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[variables('aiFoundryAiProjectResourceName')]"
+ },
+ "location": {
+ "value": "[variables('aiServiceLocation')]"
+ },
+ "tags": {
+ "value": "[parameters('tags')]"
+ },
+ "desc": {
+ "value": "[variables('aiFoundryAiProjectDescription')]"
+ },
+ "aiServicesName": {
+ "value": "[variables('aiFoundryAiServicesResourceName')]"
+ },
+ "azureExistingAIProjectResourceId": {
+ "value": "[parameters('azureExistingAIProjectResourceId')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "12336056765515184474"
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the AI Services project."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Required. The location of the Project resource."
+ }
+ },
+ "desc": {
+ "type": "string",
+ "defaultValue": "[parameters('name')]",
+ "metadata": {
+ "description": "Optional. The description of the AI Foundry project to create. Defaults to the project name."
+ }
+ },
+ "aiServicesName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the existing Cognitive Services resource to create the AI Foundry project in."
+ }
+ },
+ "azureExistingAIProjectResourceId": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Required. Azure Existing AI Project ResourceID."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "defaultValue": {},
+ "metadata": {
+ "description": "Optional. Tags to be applied to the resources."
+ }
+ }
+ },
+ "variables": {
+ "useExistingAiFoundryAiProject": "[not(empty(parameters('azureExistingAIProjectResourceId')))]",
+ "existingOpenAIEndpoint": "[if(variables('useExistingAiFoundryAiProject'), format('https://{0}.openai.azure.com/', split(parameters('azureExistingAIProjectResourceId'), '/')[8]), '')]"
+ },
+ "resources": [
+ {
+ "type": "Microsoft.CognitiveServices/accounts/projects",
+ "apiVersion": "2025-06-01",
+ "name": "[format('{0}/{1}', parameters('aiServicesName'), parameters('name'))]",
+ "tags": "[parameters('tags')]",
+ "location": "[parameters('location')]",
+ "identity": {
+ "type": "SystemAssigned"
+ },
+ "properties": {
+ "description": "[parameters('desc')]",
+ "displayName": "[parameters('name')]"
+ }
+ }
+ ],
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the AI project."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Resource ID of the AI project."
+ },
+ "value": "[resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiServicesName'), parameters('name'))]"
+ },
+ "apiEndpoint": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. API endpoint for the AI project."
+ },
+ "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiServicesName'), parameters('name')), '2025-06-01').endpoints['AI Foundry API']]"
+ },
+ "aoaiEndpoint": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains AI Endpoint."
+ },
+ "value": "[if(not(empty(variables('existingOpenAIEndpoint'))), variables('existingOpenAIEndpoint'), reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')), '2025-06-01').endpoints['OpenAI Language Model Instance API'])]"
+ },
+ "systemAssignedMIPrincipalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Principal ID of the AI project system-assigned managed identity."
+ },
+ "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiServicesName'), parameters('name')), '2025-06-01', 'full').identity.principalId]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "aiFoundryAiServices"
+ ]
+ },
+ "aiSearch": {
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[take(format('avm.res.search.search-service.{0}', variables('aiSearchName')), 64)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[variables('aiSearchName')]"
+ },
+ "location": {
+ "value": "[variables('solutionLocation')]"
+ },
+ "tags": {
+ "value": "[parameters('tags')]"
+ },
+ "enableTelemetry": {
+ "value": "[parameters('enableTelemetry')]"
+ },
+ "sku": "[if(parameters('enableScalability'), createObject('value', 'standard'), createObject('value', 'basic'))]",
+ "replicaCount": "[if(parameters('enableRedundancy'), createObject('value', 2), createObject('value', 1))]",
+ "partitionCount": {
+ "value": 1
+ },
+ "hostingMode": {
+ "value": "default"
+ },
+ "semanticSearch": {
+ "value": "free"
+ },
+ "authOptions": {
+ "value": {
+ "aadOrApiKey": {
+ "aadAuthFailureMode": "http401WithBearerChallenge"
+ }
+ }
+ },
+ "disableLocalAuth": {
+ "value": false
+ },
+ "roleAssignments": {
+ "value": [
+ {
+ "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]",
+ "roleDefinitionIdOrName": "Search Index Data Contributor",
+ "principalType": "ServicePrincipal"
+ },
+ {
+ "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]",
+ "roleDefinitionIdOrName": "Search Service Contributor",
+ "principalType": "ServicePrincipal"
+ }
+ ]
+ },
+ "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), if(parameters('enableMonitoring'), reference('logAnalyticsWorkspace').outputs.resourceId.value, ''))))), createObject('value', null()))]",
+ "publicNetworkAccess": {
+ "value": "Enabled"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.37.4.10188",
+ "templateHash": "10902281417196168235"
+ },
+ "name": "Search Services",
+ "description": "This module deploys a Search Service."
+ },
+ "definitions": {
+ "privateEndpointOutputType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the private endpoint."
+ }
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the private endpoint."
+ }
+ },
+ "groupId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "The group Id for the private endpoint Group."
+ }
+ },
+ "customDnsConfigs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "fqdn": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "FQDN that resolves to private endpoint IP address."
+ }
+ },
+ "ipAddresses": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "A list of private IP addresses of the private endpoint."
+ }
+ }
+ }
+ },
+ "metadata": {
+ "description": "The custom DNS configurations of the private endpoint."
+ }
+ },
+ "networkInterfaceResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "The IDs of the network interfaces associated with the private endpoint."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ },
+ "secretsExportConfigurationType": {
+ "type": "object",
+ "properties": {
+ "keyVaultResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The key vault name where to store the API Admin keys generated by the modules."
+ }
+ },
+ "primaryAdminKeyName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The primaryAdminKey secret name to create."
+ }
+ },
+ "secondaryAdminKeyName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The secondaryAdminKey secret name to create."
+ }
+ }
+ }
+ },
+ "secretsOutputType": {
+ "type": "object",
+ "properties": {},
+ "additionalProperties": {
+ "$ref": "#/definitions/secretSetType",
+ "metadata": {
+ "description": "An exported secret's references."
+ }
+ }
+ },
+ "authOptionsType": {
+ "type": "object",
+ "properties": {
+ "aadOrApiKey": {
+ "type": "object",
+ "properties": {
+ "aadAuthFailureMode": {
+ "type": "string",
+ "allowedValues": [
+ "http401WithBearerChallenge",
+ "http403"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Describes what response the data plane API of a search service would send for requests that failed authentication."
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Indicates that either the API key or an access token from a Microsoft Entra ID tenant can be used for authentication."
+ }
+ },
+ "apiKeyOnly": {
+ "type": "object",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Indicates that only the API key can be used for authentication."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ },
+ "networkRuleSetType": {
+ "type": "object",
+ "properties": {
+ "bypass": {
+ "type": "string",
+ "allowedValues": [
+ "AzurePortal",
+ "AzureServices",
+ "None"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Network specific rules that determine how the Azure AI Search service may be reached."
+ }
+ },
+ "ipRules": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ipRuleType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A list of IP restriction rules that defines the inbound network(s) with allowing access to the search service endpoint. At the meantime, all other public IP networks are blocked by the firewall. These restriction rules are applied only when the 'publicNetworkAccess' of the search service is 'enabled'; otherwise, traffic over public interface is not allowed even with any public IP rules, and private endpoint connections would be the exclusive access method."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ },
+ "ipRuleType": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Value corresponding to a single IPv4 address (eg., 123.1.2.3) or an IP range in CIDR format (eg., 123.1.2.3/24) to be allowed."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ },
+ "_1.lockType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the name of lock."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "CanNotDelete",
+ "None",
+ "ReadOnly"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ },
+ "notes": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the notes of the lock."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a lock.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "_1.privateEndpointCustomDnsConfigType": {
+ "type": "object",
+ "properties": {
+ "fqdn": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. FQDN that resolves to private endpoint IP address."
+ }
+ },
+ "ipAddresses": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of private IP addresses of the private endpoint."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "_1.privateEndpointIpConfigurationType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the resource that is unique within a resource group."
+ }
+ },
+ "properties": {
+ "type": "object",
+ "properties": {
+ "groupId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to."
+ }
+ },
+ "memberName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to."
+ }
+ },
+ "privateIPAddress": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. A private IP address obtained from the private endpoint's subnet."
+ }
+ }
+ },
+ "metadata": {
+ "description": "Required. Properties of private endpoint IP configurations."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "_1.privateEndpointPrivateDnsZoneGroupType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the Private DNS Zone Group."
+ }
+ },
+ "privateDnsZoneGroupConfigs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private DNS Zone Group config."
+ }
+ },
+ "privateDnsZoneResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource id of the private DNS zone."
+ }
+ }
+ }
+ },
+ "metadata": {
+ "description": "Required. The private DNS Zone Groups to associate the Private Endpoint. A DNS Zone Group can support up to 5 DNS zones."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "_1.roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "diagnosticSettingFullType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the diagnostic setting."
+ }
+ },
+ "logCategoriesAndGroups": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here."
+ }
+ },
+ "categoryGroup": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection."
+ }
+ },
+ "metricCategories": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection."
+ }
+ },
+ "logAnalyticsDestinationType": {
+ "type": "string",
+ "allowedValues": [
+ "AzureDiagnostics",
+ "Dedicated"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type."
+ }
+ },
+ "workspaceResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "storageAccountResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "eventHubAuthorizationRuleResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to."
+ }
+ },
+ "eventHubName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "marketplacePartnerResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ },
+ "lockType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the name of lock."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "CanNotDelete",
+ "None",
+ "ReadOnly"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ },
+ "notes": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the notes of the lock."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a lock.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0"
+ }
+ }
+ },
+ "managedIdentityAllType": {
+ "type": "object",
+ "properties": {
+ "systemAssigned": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enables system assigned managed identity on the resource."
+ }
+ },
+ "userAssignedResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID(s) to assign to the resource. Required if a user assigned identity is used for encryption."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ },
+ "privateEndpointSingleServiceType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the Private Endpoint."
+ }
+ },
+ "location": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The location to deploy the Private Endpoint to."
+ }
+ },
+ "privateLinkServiceConnectionName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private link connection to create."
+ }
+ },
+ "service": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The subresource to deploy the Private Endpoint for. For example \"vault\" for a Key Vault Private Endpoint."
+ }
+ },
+ "subnetResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Resource ID of the subnet where the endpoint needs to be created."
+ }
+ },
+ "resourceGroupResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID of the Resource Group the Private Endpoint will be created in. If not specified, the Resource Group of the provided Virtual Network Subnet is used."
+ }
+ },
+ "privateDnsZoneGroup": {
+ "$ref": "#/definitions/_1.privateEndpointPrivateDnsZoneGroupType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The private DNS Zone Group to configure for the Private Endpoint."
+ }
+ },
+ "isManualConnection": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. If Manual Private Link Connection is required."
+ }
+ },
+ "manualConnectionRequestMessage": {
+ "type": "string",
+ "nullable": true,
+ "maxLength": 140,
+ "metadata": {
+ "description": "Optional. A message passed to the owner of the remote resource with the manual connection request."
+ }
+ },
+ "customDnsConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/_1.privateEndpointCustomDnsConfigType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Custom DNS configurations."
+ }
+ },
+ "ipConfigurations": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/_1.privateEndpointIpConfigurationType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A list of IP configurations of the Private Endpoint. This will be used to map to the first-party Service endpoints."
+ }
+ },
+ "applicationSecurityGroupResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Application security groups in which the Private Endpoint IP configuration is included."
+ }
+ },
+ "customNetworkInterfaceName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The custom name of the network interface attached to the Private Endpoint."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/_1.lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/_1.roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "nullable": true,
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-07-01#properties/tags"
+ },
+ "description": "Optional. Tags to be applied on all resources/Resource Groups in this deployment."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a private endpoint. To be used if the private endpoint's default service / groupId can be assumed (i.e., for services that only have one Private Endpoint type like 'vault' for key vault).",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ },
+ "secretSetType": {
+ "type": "object",
+ "properties": {
+ "secretResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resourceId of the exported secret."
+ }
+ },
+ "secretUri": {
+ "type": "string",
+ "metadata": {
+ "description": "The secret URI of the exported secret."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "modules/keyVaultExport.bicep"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the Azure Cognitive Search service to create or update. Search service names must only contain lowercase letters, digits or dashes, cannot use dash as the first two or last one characters, cannot contain consecutive dashes, and must be between 2 and 60 characters in length. Search service names must be globally unique since they are part of the service URI (https://.search.windows.net). You cannot change the service name after the service is created."
+ }
+ },
+ "authOptions": {
+ "$ref": "#/definitions/authOptionsType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Defines the options for how the data plane API of a Search service authenticates requests. Must remain an empty object {} if 'disableLocalAuth' is set to true."
+ }
+ },
+ "disableLocalAuth": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. When set to true, calls to the search service will not be permitted to utilize API keys for authentication. This cannot be set to true if 'authOptions' are defined."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ },
+ "cmkEnforcement": {
+ "type": "string",
+ "defaultValue": "Unspecified",
+ "allowedValues": [
+ "Disabled",
+ "Enabled",
+ "Unspecified"
+ ],
+ "metadata": {
+ "description": "Optional. Describes a policy that determines how resources within the search service are to be encrypted with Customer Managed Keys."
+ }
+ },
+ "hostingMode": {
+ "type": "string",
+ "defaultValue": "default",
+ "allowedValues": [
+ "default",
+ "highDensity"
+ ],
+ "metadata": {
+ "description": "Optional. Applicable only for the standard3 SKU. You can set this property to enable up to 3 high density partitions that allow up to 1000 indexes, which is much higher than the maximum indexes allowed for any other SKU. For the standard3 SKU, the value is either 'default' or 'highDensity'. For all other SKUs, this value must be 'default'."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Optional. Location for all Resources."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The lock settings for all Resources in the solution."
+ }
+ },
+ "networkRuleSet": {
+ "$ref": "#/definitions/networkRuleSetType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Network specific rules that determine how the Azure Cognitive Search service may be reached."
+ }
+ },
+ "partitionCount": {
+ "type": "int",
+ "defaultValue": 1,
+ "minValue": 1,
+ "maxValue": 12,
+ "metadata": {
+ "description": "Optional. The number of partitions in the search service; if specified, it can be 1, 2, 3, 4, 6, or 12. Values greater than 1 are only valid for standard SKUs. For 'standard3' services with hostingMode set to 'highDensity', the allowed values are between 1 and 3."
+ }
+ },
+ "privateEndpoints": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateEndpointSingleServiceType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible."
+ }
+ },
+ "sharedPrivateLinkResources": {
+ "type": "array",
+ "defaultValue": [],
+ "metadata": {
+ "description": "Optional. The sharedPrivateLinkResources to create as part of the search Service."
+ }
+ },
+ "publicNetworkAccess": {
+ "type": "string",
+ "defaultValue": "Enabled",
+ "allowedValues": [
+ "Enabled",
+ "Disabled"
+ ],
+ "metadata": {
+ "description": "Optional. This value can be set to 'Enabled' to avoid breaking changes on existing customer resources and templates. If set to 'Disabled', traffic over public interface is not allowed, and private endpoint connections would be the exclusive access method."
+ }
+ },
+ "secretsExportConfiguration": {
+ "$ref": "#/definitions/secretsExportConfigurationType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Key vault reference and secret settings for the module's secrets export."
+ }
+ },
+ "replicaCount": {
+ "type": "int",
+ "defaultValue": 3,
+ "minValue": 1,
+ "maxValue": 12,
+ "metadata": {
+ "description": "Optional. The number of replicas in the search service. If specified, it must be a value between 1 and 12 inclusive for standard SKUs or between 1 and 3 inclusive for basic SKU."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "semanticSearch": {
+ "type": "string",
+ "nullable": true,
+ "allowedValues": [
+ "disabled",
+ "free",
+ "standard"
+ ],
+ "metadata": {
+ "description": "Optional. Sets options that control the availability of semantic search. This configuration is only possible for certain search SKUs in certain locations."
+ }
+ },
+ "sku": {
+ "type": "string",
+ "defaultValue": "standard",
+ "allowedValues": [
+ "basic",
+ "free",
+ "standard",
+ "standard2",
+ "standard3",
+ "storage_optimized_l1",
+ "storage_optimized_l2"
+ ],
+ "metadata": {
+ "description": "Optional. Defines the SKU of an Azure Cognitive Search Service, which determines price tier and capacity limits."
+ }
+ },
+ "managedIdentities": {
+ "$ref": "#/definitions/managedIdentityAllType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The managed identity definition for this resource."
+ }
+ },
+ "diagnosticSettings": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/diagnosticSettingFullType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The diagnostic settings of the service."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Search/searchServices@2025-02-01-preview#properties/tags"
+ },
+ "description": "Optional. Tags to help categorize the resource in the Azure portal."
+ },
+ "nullable": true
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "enableReferencedModulesTelemetry": false,
+ "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]",
+ "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'SystemAssigned,UserAssigned', 'SystemAssigned'), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', '')), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]",
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "Search Index Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8ebe5a00-799e-43f5-93ac-243d3dce84a7')]",
+ "Search Index Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '1407120a-92aa-4202-b7e9-c0e197c71c8f')]",
+ "Search Service Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.search-searchservice.{0}.{1}', replace('0.11.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "searchService": {
+ "type": "Microsoft.Search/searchServices",
+ "apiVersion": "2025-02-01-preview",
+ "name": "[parameters('name')]",
+ "location": "[parameters('location')]",
+ "sku": {
+ "name": "[parameters('sku')]"
+ },
+ "tags": "[parameters('tags')]",
+ "identity": "[variables('identity')]",
+ "properties": {
+ "authOptions": "[parameters('authOptions')]",
+ "disableLocalAuth": "[parameters('disableLocalAuth')]",
+ "encryptionWithCmk": {
+ "enforcement": "[parameters('cmkEnforcement')]"
+ },
+ "hostingMode": "[parameters('hostingMode')]",
+ "networkRuleSet": "[parameters('networkRuleSet')]",
+ "partitionCount": "[parameters('partitionCount')]",
+ "replicaCount": "[parameters('replicaCount')]",
+ "publicNetworkAccess": "[toLower(parameters('publicNetworkAccess'))]",
+ "semanticSearch": "[parameters('semanticSearch')]"
+ }
+ },
+ "searchService_diagnosticSettings": {
+ "copy": {
+ "name": "searchService_diagnosticSettings",
+ "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]"
+ },
+ "type": "Microsoft.Insights/diagnosticSettings",
+ "apiVersion": "2021-05-01-preview",
+ "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]",
+ "properties": {
+ "copy": [
+ {
+ "name": "metrics",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]",
+ "input": {
+ "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]",
+ "timeGrain": null
+ }
+ },
+ {
+ "name": "logs",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]",
+ "input": {
+ "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]",
+ "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]"
+ }
+ }
+ ],
+ "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]",
+ "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]",
+ "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]",
+ "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]",
+ "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]",
+ "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]"
+ },
+ "dependsOn": [
+ "searchService"
+ ]
+ },
+ "searchService_lock": {
+ "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]",
+ "type": "Microsoft.Authorization/locks",
+ "apiVersion": "2020-05-01",
+ "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]",
+ "properties": {
+ "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]",
+ "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]"
+ },
+ "dependsOn": [
+ "searchService"
+ ]
+ },
+ "searchService_roleAssignments": {
+ "copy": {
+ "name": "searchService_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Search/searchServices', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "searchService"
+ ]
+ },
+ "searchService_privateEndpoints": {
+ "copy": {
+ "name": "searchService_privateEndpoints",
+ "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2022-09-01",
+ "name": "[format('{0}-searchService-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]",
+ "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]",
+ "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Search/searchServices', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'searchService'), copyIndex()))]"
+ },
+ "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Search/searchServices', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'searchService'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Search/searchServices', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'searchService')))))), createObject('value', null()))]",
+ "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Search/searchServices', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'searchService'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Search/searchServices', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'searchService')), 'requestMessage', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualConnectionRequestMessage'), 'Manual approval required.'))))), createObject('value', null()))]",
+ "subnetResourceId": {
+ "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]"
+ },
+ "enableTelemetry": {
+ "value": "[variables('enableReferencedModulesTelemetry')]"
+ },
+ "location": {
+ "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]"
+ },
+ "lock": {
+ "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]"
+ },
+ "privateDnsZoneGroup": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroup')]"
+ },
+ "roleAssignments": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]"
+ },
+ "tags": {
+ "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]"
+ },
+ "customDnsConfigs": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]"
+ },
+ "ipConfigurations": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]"
+ },
+ "applicationSecurityGroupResourceIds": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]"
+ },
+ "customNetworkInterfaceName": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.34.44.8038",
+ "templateHash": "12389807800450456797"
+ },
+ "name": "Private Endpoints",
+ "description": "This module deploys a Private Endpoint."
+ },
+ "definitions": {
+ "privateDnsZoneGroupType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the Private DNS Zone Group."
+ }
+ },
+ "privateDnsZoneGroupConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateDnsZoneGroupConfigType"
+ },
+ "metadata": {
+ "description": "Required. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ },
+ "ipConfigurationType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the resource that is unique within a resource group."
+ }
+ },
+ "properties": {
+ "type": "object",
+ "properties": {
+ "groupId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string."
+ }
+ },
+ "memberName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string."
+ }
+ },
+ "privateIPAddress": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. A private IP address obtained from the private endpoint's subnet."
+ }
+ }
+ },
+ "metadata": {
+ "description": "Required. Properties of private endpoint IP configurations."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ },
+ "privateLinkServiceConnectionType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the private link service connection."
+ }
+ },
+ "properties": {
+ "type": "object",
+ "properties": {
+ "groupIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string array `[]`."
+ }
+ },
+ "privateLinkServiceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource id of private link service."
+ }
+ },
+ "requestMessage": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars."
+ }
+ }
+ },
+ "metadata": {
+ "description": "Required. Properties of private link service connection."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ },
+ "customDnsConfigType": {
+ "type": "object",
+ "properties": {
+ "fqdn": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. FQDN that resolves to private endpoint IP address."
+ }
+ },
+ "ipAddresses": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of private IP addresses of the private endpoint."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ },
+ "lockType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the name of lock."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "CanNotDelete",
+ "None",
+ "ReadOnly"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a lock.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ },
+ "privateDnsZoneGroupConfigType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private DNS zone group config."
+ }
+ },
+ "privateDnsZoneResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource id of the private DNS zone."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "private-dns-zone-group/main.bicep"
+ }
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the private endpoint resource to create."
+ }
+ },
+ "subnetResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Resource ID of the subnet where the endpoint needs to be created."
+ }
+ },
+ "applicationSecurityGroupResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Application security groups in which the private endpoint IP configuration is included."
+ }
+ },
+ "customNetworkInterfaceName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The custom name of the network interface attached to the private endpoint."
+ }
+ },
+ "ipConfigurations": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ipConfigurationType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints."
+ }
+ },
+ "privateDnsZoneGroup": {
+ "$ref": "#/definitions/privateDnsZoneGroupType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The private DNS zone group to configure for the private endpoint."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Optional. Location for all Resources."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The lock settings of the service."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Tags to be applied on all resources/resource groups in this deployment."
+ }
+ },
+ "customDnsConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/customDnsConfigType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Custom DNS configurations."
+ }
+ },
+ "manualPrivateLinkServiceConnections": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateLinkServiceConnectionType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Conditional. A grouping of information about the connection to the remote resource. Used when the network admin does not have access to approve connections to the remote resource. Required if `privateLinkServiceConnections` is empty."
+ }
+ },
+ "privateLinkServiceConnections": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateLinkServiceConnectionType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "DNS Resolver Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f2ebee7-ffd4-4fc0-b3b7-664099fdad5d')]",
+ "DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314')]",
+ "Domain Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'eeaeda52-9324-47f6-8069-5d5bade478b2')]",
+ "Domain Services Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '361898ef-9ed1-48c2-849c-a832951106bb')]",
+ "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.11.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "privateEndpoint": {
+ "type": "Microsoft.Network/privateEndpoints",
+ "apiVersion": "2024-05-01",
+ "name": "[parameters('name')]",
+ "location": "[parameters('location')]",
+ "tags": "[parameters('tags')]",
+ "properties": {
+ "copy": [
+ {
+ "name": "applicationSecurityGroups",
+ "count": "[length(coalesce(parameters('applicationSecurityGroupResourceIds'), createArray()))]",
+ "input": {
+ "id": "[coalesce(parameters('applicationSecurityGroupResourceIds'), createArray())[copyIndex('applicationSecurityGroups')]]"
+ }
+ }
+ ],
+ "customDnsConfigs": "[coalesce(parameters('customDnsConfigs'), createArray())]",
+ "customNetworkInterfaceName": "[coalesce(parameters('customNetworkInterfaceName'), '')]",
+ "ipConfigurations": "[coalesce(parameters('ipConfigurations'), createArray())]",
+ "manualPrivateLinkServiceConnections": "[coalesce(parameters('manualPrivateLinkServiceConnections'), createArray())]",
+ "privateLinkServiceConnections": "[coalesce(parameters('privateLinkServiceConnections'), createArray())]",
+ "subnet": {
+ "id": "[parameters('subnetResourceId')]"
+ }
+ }
+ },
+ "privateEndpoint_lock": {
+ "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]",
+ "type": "Microsoft.Authorization/locks",
+ "apiVersion": "2020-05-01",
+ "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]",
+ "properties": {
+ "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]",
+ "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]"
+ },
+ "dependsOn": [
+ "privateEndpoint"
+ ]
+ },
+ "privateEndpoint_roleAssignments": {
+ "copy": {
+ "name": "privateEndpoint_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateEndpoints', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "privateEndpoint"
+ ]
+ },
+ "privateEndpoint_privateDnsZoneGroup": {
+ "condition": "[not(empty(parameters('privateDnsZoneGroup')))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2022-09-01",
+ "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[tryGet(parameters('privateDnsZoneGroup'), 'name')]"
+ },
+ "privateEndpointName": {
+ "value": "[parameters('name')]"
+ },
+ "privateDnsZoneConfigs": {
+ "value": "[parameters('privateDnsZoneGroup').privateDnsZoneGroupConfigs]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.34.44.8038",
+ "templateHash": "13997305779829540948"
+ },
+ "name": "Private Endpoint Private DNS Zone Groups",
+ "description": "This module deploys a Private Endpoint Private DNS Zone Group."
+ },
+ "definitions": {
+ "privateDnsZoneGroupConfigType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private DNS zone group config."
+ }
+ },
+ "privateDnsZoneResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource id of the private DNS zone."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ }
+ },
+ "parameters": {
+ "privateEndpointName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment."
+ }
+ },
+ "privateDnsZoneConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateDnsZoneGroupConfigType"
+ },
+ "minLength": 1,
+ "maxLength": 5,
+ "metadata": {
+ "description": "Required. Array of private DNS zone configurations of the private DNS zone group. A DNS zone group can support up to 5 DNS zones."
+ }
+ },
+ "name": {
+ "type": "string",
+ "defaultValue": "default",
+ "metadata": {
+ "description": "Optional. The name of the private DNS zone group."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "privateDnsZoneConfigsVar",
+ "count": "[length(parameters('privateDnsZoneConfigs'))]",
+ "input": {
+ "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId, '/')))]",
+ "properties": {
+ "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId]"
+ }
+ }
+ }
+ ]
+ },
+ "resources": {
+ "privateEndpoint": {
+ "existing": true,
+ "type": "Microsoft.Network/privateEndpoints",
+ "apiVersion": "2024-05-01",
+ "name": "[parameters('privateEndpointName')]"
+ },
+ "privateDnsZoneGroup": {
+ "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups",
+ "apiVersion": "2024-05-01",
+ "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]",
+ "properties": {
+ "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigsVar')]"
+ }
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the private endpoint DNS zone group."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the private endpoint DNS zone group."
+ },
+ "value": "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', parameters('privateEndpointName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the private endpoint DNS zone group was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "privateEndpoint"
+ ]
+ }
+ },
+ "outputs": {
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the private endpoint was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the private endpoint."
+ },
+ "value": "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]"
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the private endpoint."
+ },
+ "value": "[parameters('name')]"
+ },
+ "location": {
+ "type": "string",
+ "metadata": {
+ "description": "The location the resource was deployed into."
+ },
+ "value": "[reference('privateEndpoint', '2024-05-01', 'full').location]"
+ },
+ "customDnsConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/customDnsConfigType"
+ },
+ "metadata": {
+ "description": "The custom DNS configurations of the private endpoint."
+ },
+ "value": "[reference('privateEndpoint').customDnsConfigs]"
+ },
+ "networkInterfaceResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "The resource IDs of the network interfaces associated with the private endpoint."
+ },
+ "value": "[map(reference('privateEndpoint').networkInterfaces, lambda('nic', lambdaVariables('nic').id))]"
+ },
+ "groupId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "The group Id for the private endpoint Group."
+ },
+ "value": "[coalesce(tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'manualPrivateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0), tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'privateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0))]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "searchService"
+ ]
+ },
+ "searchService_sharedPrivateLinkResources": {
+ "copy": {
+ "name": "searchService_sharedPrivateLinkResources",
+ "count": "[length(parameters('sharedPrivateLinkResources'))]",
+ "mode": "serial",
+ "batchSize": 1
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2022-09-01",
+ "name": "[format('{0}-searchService-SharedPrvLink-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[coalesce(tryGet(parameters('sharedPrivateLinkResources')[copyIndex()], 'name'), format('spl-{0}-{1}-{2}', last(split(resourceId('Microsoft.Search/searchServices', parameters('name')), '/')), parameters('sharedPrivateLinkResources')[copyIndex()].groupId, copyIndex()))]"
+ },
+ "searchServiceName": {
+ "value": "[parameters('name')]"
+ },
+ "privateLinkResourceId": {
+ "value": "[parameters('sharedPrivateLinkResources')[copyIndex()].privateLinkResourceId]"
+ },
+ "groupId": {
+ "value": "[parameters('sharedPrivateLinkResources')[copyIndex()].groupId]"
+ },
+ "requestMessage": {
+ "value": "[parameters('sharedPrivateLinkResources')[copyIndex()].requestMessage]"
+ },
+ "resourceRegion": {
+ "value": "[tryGet(parameters('sharedPrivateLinkResources')[copyIndex()], 'resourceRegion')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.37.4.10188",
+ "templateHash": "557730297583881254"
+ },
+ "name": "Search Services Private Link Resources",
+ "description": "This module deploys a Search Service Private Link Resource."
+ },
+ "parameters": {
+ "searchServiceName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent searchServices. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the shared private link resource managed by the Azure Cognitive Search service within the specified resource group."
+ }
+ },
+ "privateLinkResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource ID of the resource the shared private link resource is for."
+ }
+ },
+ "groupId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The group ID from the provider of resource the shared private link resource is for."
+ }
+ },
+ "requestMessage": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The request message for requesting approval of the shared private link resource."
+ }
+ },
+ "resourceRegion": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Can be used to specify the Azure Resource Manager location of the resource to which a shared private link is to be created. This is only required for those resources whose DNS configuration are regional (such as Azure Kubernetes Service)."
+ }
+ }
+ },
+ "resources": {
+ "searchService": {
+ "existing": true,
+ "type": "Microsoft.Search/searchServices",
+ "apiVersion": "2025-02-01-preview",
+ "name": "[parameters('searchServiceName')]"
+ },
+ "sharedPrivateLinkResource": {
+ "type": "Microsoft.Search/searchServices/sharedPrivateLinkResources",
+ "apiVersion": "2025-02-01-preview",
+ "name": "[format('{0}/{1}', parameters('searchServiceName'), parameters('name'))]",
+ "properties": {
+ "privateLinkResourceId": "[parameters('privateLinkResourceId')]",
+ "groupId": "[parameters('groupId')]",
+ "requestMessage": "[parameters('requestMessage')]",
+ "resourceRegion": "[parameters('resourceRegion')]"
+ }
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the shared private link resource."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the shared private link resource."
+ },
+ "value": "[resourceId('Microsoft.Search/searchServices/sharedPrivateLinkResources', parameters('searchServiceName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the shared private link resource was created in."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "searchService"
+ ]
+ },
+ "secretsExport": {
+ "condition": "[not(equals(parameters('secretsExportConfiguration'), null()))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2022-09-01",
+ "name": "[format('{0}-secrets-kv', uniqueString(deployment().name, parameters('location')))]",
+ "subscriptionId": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[2]]",
+ "resourceGroup": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[4]]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "keyVaultName": {
+ "value": "[last(split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/'))]"
+ },
+ "secretsToSet": {
+ "value": "[union(createArray(), if(contains(parameters('secretsExportConfiguration'), 'primaryAdminKeyName'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'primaryAdminKeyName'), 'value', listAdminKeys('searchService', '2025-02-01-preview').primaryKey)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'secondaryAdminKeyName'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'secondaryAdminKeyName'), 'value', listAdminKeys('searchService', '2025-02-01-preview').secondaryKey)), createArray()))]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.37.4.10188",
+ "templateHash": "7634110751636246703"
+ }
+ },
+ "definitions": {
+ "secretSetType": {
+ "type": "object",
+ "properties": {
+ "secretResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resourceId of the exported secret."
+ }
+ },
+ "secretUri": {
+ "type": "string",
+ "metadata": {
+ "description": "The secret URI of the exported secret."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ },
+ "secretToSetType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the secret to set."
+ }
+ },
+ "value": {
+ "type": "securestring",
+ "metadata": {
+ "description": "Required. The value of the secret to set."
+ }
+ }
+ }
+ }
+ },
+ "parameters": {
+ "keyVaultName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the Key Vault to set the ecrets in."
+ }
+ },
+ "secretsToSet": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/secretToSetType"
+ },
+ "metadata": {
+ "description": "Required. The secrets to set in the Key Vault."
+ }
+ }
+ },
+ "resources": {
+ "keyVault": {
+ "existing": true,
+ "type": "Microsoft.KeyVault/vaults",
+ "apiVersion": "2024-11-01",
+ "name": "[parameters('keyVaultName')]"
+ },
+ "secrets": {
+ "copy": {
+ "name": "secrets",
+ "count": "[length(parameters('secretsToSet'))]"
+ },
+ "type": "Microsoft.KeyVault/vaults/secrets",
+ "apiVersion": "2024-11-01",
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretsToSet')[copyIndex()].name)]",
+ "properties": {
+ "value": "[parameters('secretsToSet')[copyIndex()].value]"
+ }
+ }
+ },
+ "outputs": {
+ "secretsSet": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/secretSetType"
+ },
+ "metadata": {
+ "description": "The references to the secrets exported to the provided Key Vault."
+ },
+ "copy": {
+ "count": "[length(range(0, length(coalesce(parameters('secretsToSet'), createArray()))))]",
+ "input": {
+ "secretResourceId": "[resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), parameters('secretsToSet')[range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()]].name)]",
+ "secretUri": "[reference(format('secrets[{0}]', range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()])).secretUri]"
+ }
+ }
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "searchService"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the search service."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the search service."
+ },
+ "value": "[resourceId('Microsoft.Search/searchServices', parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the search service was created in."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "systemAssignedMIPrincipalId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "The principal ID of the system assigned identity."
+ },
+ "value": "[tryGet(tryGet(reference('searchService', '2025-02-01-preview', 'full'), 'identity'), 'principalId')]"
+ },
+ "location": {
+ "type": "string",
+ "metadata": {
+ "description": "The location the resource was deployed into."
+ },
+ "value": "[reference('searchService', '2025-02-01-preview', 'full').location]"
+ },
+ "endpoint": {
+ "type": "string",
+ "metadata": {
+ "description": "The endpoint of the search service."
+ },
+ "value": "[reference('searchService').endpoint]"
+ },
+ "privateEndpoints": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateEndpointOutputType"
+ },
+ "metadata": {
+ "description": "The private endpoints of the search service."
+ },
+ "copy": {
+ "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]",
+ "input": {
+ "name": "[reference(format('searchService_privateEndpoints[{0}]', copyIndex())).outputs.name.value]",
+ "resourceId": "[reference(format('searchService_privateEndpoints[{0}]', copyIndex())).outputs.resourceId.value]",
+ "groupId": "[tryGet(tryGet(reference(format('searchService_privateEndpoints[{0}]', copyIndex())).outputs, 'groupId'), 'value')]",
+ "customDnsConfigs": "[reference(format('searchService_privateEndpoints[{0}]', copyIndex())).outputs.customDnsConfigs.value]",
+ "networkInterfaceResourceIds": "[reference(format('searchService_privateEndpoints[{0}]', copyIndex())).outputs.networkInterfaceResourceIds.value]"
+ }
+ }
+ },
+ "exportedSecrets": {
+ "$ref": "#/definitions/secretsOutputType",
+ "metadata": {
+ "description": "A hashtable of references to the secrets exported to the provided Key Vault. The key of each reference is each secret's name."
+ },
+ "value": "[if(not(equals(parameters('secretsExportConfiguration'), null())), toObject(reference('secretsExport').outputs.secretsSet.value, lambda('secret', last(split(lambdaVariables('secret').secretResourceId, '/'))), lambda('secret', lambdaVariables('secret'))), createObject())]"
+ },
+ "primaryKey": {
+ "type": "securestring",
+ "metadata": {
+ "description": "The primary admin API key of the search service."
+ },
+ "value": "[listAdminKeys('searchService', '2025-02-01-preview').primaryKey]"
+ },
+ "secondaryKey": {
+ "type": "securestring",
+ "metadata": {
+ "description": "The secondaryKey admin API key of the search service."
+ },
+ "value": "[listAdminKeys('searchService', '2025-02-01-preview').secondaryKey]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "logAnalyticsWorkspace",
+ "userAssignedIdentity"
+ ]
+ },
+ "storageAccount": {
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[take(format('avm.res.storage.storage-account.{0}', variables('storageAccountName')), 64)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[variables('storageAccountName')]"
+ },
+ "location": {
+ "value": "[variables('solutionLocation')]"
+ },
+ "skuName": "[if(parameters('enableRedundancy'), createObject('value', 'Standard_ZRS'), createObject('value', 'Standard_LRS'))]",
+ "managedIdentities": {
+ "value": {
+ "systemAssigned": true
+ }
+ },
+ "minimumTlsVersion": {
+ "value": "TLS1_2"
+ },
+ "enableTelemetry": {
+ "value": "[parameters('enableTelemetry')]"
+ },
+ "tags": {
+ "value": "[parameters('tags')]"
+ },
+ "accessTier": {
+ "value": "Hot"
+ },
+ "supportsHttpsTrafficOnly": {
+ "value": true
+ },
+ "blobServices": {
+ "value": {
+ "containerDeleteRetentionPolicyEnabled": true,
+ "containerDeleteRetentionPolicyDays": 7,
+ "deleteRetentionPolicyEnabled": true,
+ "deleteRetentionPolicyDays": 7,
+ "containers": [
+ {
+ "name": "[variables('productImagesContainer')]",
+ "publicAccess": "None"
+ },
+ {
+ "name": "[variables('generatedImagesContainer')]",
+ "publicAccess": "None"
+ },
+ {
+ "name": "[variables('dataContainer')]",
+ "publicAccess": "None"
+ }
+ ]
+ }
+ },
+ "roleAssignments": {
+ "value": [
+ {
+ "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]",
+ "roleDefinitionIdOrName": "Storage Blob Data Contributor",
+ "principalType": "ServicePrincipal"
+ }
+ ]
+ },
+ "networkAcls": {
+ "value": {
+ "bypass": "AzureServices",
+ "defaultAction": "[if(parameters('enablePrivateNetworking'), 'Deny', 'Allow')]"
+ }
+ },
+ "allowBlobPublicAccess": {
+ "value": false
+ },
+ "publicNetworkAccess": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Disabled'), createObject('value', 'Enabled'))]",
+ "privateEndpoints": "[if(parameters('enablePrivateNetworking'), createObject('value', createArray(createObject('service', 'blob', 'subnetResourceId', reference('virtualNetwork').outputs.pepsSubnetResourceId.value, 'privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', createArray(createObject('privateDnsZoneResourceId', reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageBlob)).outputs.resourceId.value)))))), createObject('value', null()))]",
+ "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), if(parameters('enableMonitoring'), reference('logAnalyticsWorkspace').outputs.resourceId.value, ''))))), createObject('value', null()))]"
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "8444048237705693390"
+ },
+ "name": "Storage Accounts",
+ "description": "This module deploys a Storage Account."
+ },
+ "definitions": {
+ "privateEndpointOutputType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the private endpoint."
+ }
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the private endpoint."
+ }
+ },
+ "groupId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "The group Id for the private endpoint Group."
+ }
+ },
+ "customDnsConfigs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "fqdn": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "FQDN that resolves to private endpoint IP address."
+ }
+ },
+ "ipAddresses": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "A list of private IP addresses of the private endpoint."
+ }
+ }
+ }
+ },
+ "metadata": {
+ "description": "The custom DNS configurations of the private endpoint."
+ }
+ },
+ "networkInterfaceResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "The IDs of the network interfaces associated with the private endpoint."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for the private endpoints output."
+ }
+ },
+ "networkAclsType": {
+ "type": "object",
+ "properties": {
+ "resourceAccessRules": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "tenantId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The ID of the tenant in which the resource resides in."
+ }
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource ID of the target service. Can also contain a wildcard, if multiple services e.g. in a resource group should be included."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Sets the resource access rules. Array entries must consist of \"tenantId\" and \"resourceId\" fields only."
+ }
+ },
+ "bypass": {
+ "type": "string",
+ "allowedValues": [
+ "AzureServices",
+ "AzureServices, Logging",
+ "AzureServices, Logging, Metrics",
+ "AzureServices, Metrics",
+ "Logging",
+ "Logging, Metrics",
+ "Metrics",
+ "None"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specifies whether traffic is bypassed for Logging/Metrics/AzureServices. Possible values are any combination of Logging,Metrics,AzureServices (For example, \"Logging, Metrics\"), or None to bypass none of those traffics."
+ }
+ },
+ "virtualNetworkRules": {
+ "type": "array",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Sets the virtual network rules."
+ }
+ },
+ "ipRules": {
+ "type": "array",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Sets the IP ACL rules."
+ }
+ },
+ "defaultAction": {
+ "type": "string",
+ "allowedValues": [
+ "Allow",
+ "Deny"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specifies the default action of allow or deny when no other rules match."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for the network configuration."
+ }
+ },
+ "secretsExportConfigurationType": {
+ "type": "object",
+ "properties": {
+ "keyVaultResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The key vault name where to store the keys and connection strings generated by the modules."
+ }
+ },
+ "accessKey1Name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The accessKey1 secret name to create."
+ }
+ },
+ "connectionString1Name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The connectionString1 secret name to create."
+ }
+ },
+ "accessKey2Name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The accessKey2 secret name to create."
+ }
+ },
+ "connectionString2Name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The connectionString2 secret name to create."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of the exported secrets."
+ }
+ },
+ "localUserType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the local user used for SFTP Authentication."
+ }
+ },
+ "hasSharedKey": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Indicates whether shared key exists. Set it to false to remove existing shared key."
+ }
+ },
+ "hasSshKey": {
+ "type": "bool",
+ "metadata": {
+ "description": "Required. Indicates whether SSH key exists. Set it to false to remove existing SSH key."
+ }
+ },
+ "hasSshPassword": {
+ "type": "bool",
+ "metadata": {
+ "description": "Required. Indicates whether SSH password exists. Set it to false to remove existing SSH password."
+ }
+ },
+ "homeDirectory": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The local user home directory."
+ }
+ },
+ "permissionScopes": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/permissionScopeType"
+ },
+ "metadata": {
+ "description": "Required. The permission scopes of the local user."
+ }
+ },
+ "sshAuthorizedKeys": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/sshAuthorizedKeyType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The local user SSH authorized keys for SFTP."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of a local user."
+ }
+ },
+ "blobServiceType": {
+ "type": "object",
+ "properties": {
+ "automaticSnapshotPolicyEnabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Automatic Snapshot is enabled if set to true."
+ }
+ },
+ "changeFeedEnabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The blob service properties for change feed events. Indicates whether change feed event logging is enabled for the Blob service."
+ }
+ },
+ "changeFeedRetentionInDays": {
+ "type": "int",
+ "nullable": true,
+ "minValue": 1,
+ "maxValue": 146000,
+ "metadata": {
+ "description": "Optional. Indicates whether change feed event logging is enabled for the Blob service. Indicates the duration of changeFeed retention in days. If left blank, it indicates an infinite retention of the change feed."
+ }
+ },
+ "containerDeleteRetentionPolicyEnabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The blob service properties for container soft delete. Indicates whether DeleteRetentionPolicy is enabled."
+ }
+ },
+ "containerDeleteRetentionPolicyDays": {
+ "type": "int",
+ "nullable": true,
+ "minValue": 1,
+ "maxValue": 365,
+ "metadata": {
+ "description": "Optional. Indicates the number of days that the deleted item should be retained."
+ }
+ },
+ "containerDeleteRetentionPolicyAllowPermanentDelete": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. This property when set to true allows deletion of the soft deleted blob versions and snapshots. This property cannot be used with blob restore policy. This property only applies to blob service and does not apply to containers or file share."
+ }
+ },
+ "corsRules": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/blobCorsRuleType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The List of CORS rules. You can include up to five CorsRule elements in the request."
+ }
+ },
+ "defaultServiceVersion": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Indicates the default version to use for requests to the Blob service if an incoming request's version is not specified. Possible values include version 2008-10-27 and all more recent versions."
+ }
+ },
+ "deleteRetentionPolicyEnabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The blob service properties for blob soft delete."
+ }
+ },
+ "deleteRetentionPolicyDays": {
+ "type": "int",
+ "nullable": true,
+ "minValue": 1,
+ "maxValue": 365,
+ "metadata": {
+ "description": "Optional. Indicates the number of days that the deleted blob should be retained."
+ }
+ },
+ "deleteRetentionPolicyAllowPermanentDelete": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. This property when set to true allows deletion of the soft deleted blob versions and snapshots. This property cannot be used with blob restore policy. This property only applies to blob service and does not apply to containers or file share."
+ }
+ },
+ "isVersioningEnabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Use versioning to automatically maintain previous versions of your blobs. Cannot be enabled for ADLS Gen2 storage accounts."
+ }
+ },
+ "versionDeletePolicyDays": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Number of days to keep a version before deleting. If set, a lifecycle management policy will be created to handle deleting previous versions."
+ }
+ },
+ "lastAccessTimeTrackingPolicyEnabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The blob service property to configure last access time based tracking policy. When set to true last access time based tracking is enabled."
+ }
+ },
+ "restorePolicyEnabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The blob service properties for blob restore policy. If point-in-time restore is enabled, then versioning, change feed, and blob soft delete must also be enabled."
+ }
+ },
+ "restorePolicyDays": {
+ "type": "int",
+ "nullable": true,
+ "minValue": 1,
+ "metadata": {
+ "description": "Optional. How long this blob can be restored. It should be less than DeleteRetentionPolicy days."
+ }
+ },
+ "containers": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/containerType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Blob containers to create."
+ }
+ },
+ "diagnosticSettings": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/diagnosticSettingFullType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The diagnostic settings of the service."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of a blob service."
+ }
+ },
+ "fileServiceType": {
+ "type": "object",
+ "properties": {
+ "protocolSettings": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Storage/storageAccounts/fileServices@2024-01-01#properties/properties/properties/protocolSettings"
+ },
+ "description": "Optional. Protocol settings for file service."
+ },
+ "nullable": true
+ },
+ "shareDeleteRetentionPolicy": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Storage/storageAccounts/fileServices@2024-01-01#properties/properties/properties/shareDeleteRetentionPolicy"
+ },
+ "description": "Optional. The service properties for soft delete."
+ },
+ "nullable": true
+ },
+ "shares": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/fileShareType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. File shares to create."
+ }
+ },
+ "corsRules": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/fileCorsRuleType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The List of CORS rules. You can include up to five CorsRule elements in the request."
+ }
+ },
+ "diagnosticSettings": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/diagnosticSettingFullType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The diagnostic settings of the service."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of a file service."
+ }
+ },
+ "queueServiceType": {
+ "type": "object",
+ "properties": {
+ "queues": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/queueType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Queues to create."
+ }
+ },
+ "corsRules": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/queueCorsRuleType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The List of CORS rules. You can include up to five CorsRule elements in the request."
+ }
+ },
+ "diagnosticSettings": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/diagnosticSettingFullType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The diagnostic settings of the service."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of a queue service."
+ }
+ },
+ "tableServiceType": {
+ "type": "object",
+ "properties": {
+ "tables": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/tableType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Tables to create."
+ }
+ },
+ "corsRules": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/tableCorsRuleType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The List of CORS rules. You can include up to five CorsRule elements in the request."
+ }
+ },
+ "diagnosticSettings": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/diagnosticSettingFullType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The diagnostic settings of the service."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of a table service."
+ }
+ },
+ "objectReplicationPolicyType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the object replication policy. If not provided, a GUID will be generated."
+ }
+ },
+ "destinationStorageAccountResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource ID of the destination storage account."
+ }
+ },
+ "enableMetrics": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Indicates whether metrics are enabled for the object replication policy."
+ }
+ },
+ "rules": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/objectReplicationPolicyRuleType"
+ },
+ "metadata": {
+ "description": "Required. The storage account object replication rules."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of an object replication policy."
+ }
+ },
+ "_1.immutabilityPolicyType": {
+ "type": "object",
+ "properties": {
+ "immutabilityPeriodSinceCreationInDays": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The immutability period for the blobs in the container since the policy creation, in days."
+ }
+ },
+ "allowProtectedAppendWrites": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to an append blob while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API."
+ }
+ },
+ "allowProtectedAppendWritesAll": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to both \"Append and Block Blobs\" while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API. The \"allowProtectedAppendWrites\" and \"allowProtectedAppendWritesAll\" properties are mutually exclusive."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The type for an immutability policy.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "blob-service/container/main.bicep"
+ }
+ }
+ },
+ "_2.privateEndpointCustomDnsConfigType": {
+ "type": "object",
+ "properties": {
+ "fqdn": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. FQDN that resolves to private endpoint IP address."
+ }
+ },
+ "ipAddresses": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of private IP addresses of the private endpoint."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "_2.privateEndpointIpConfigurationType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the resource that is unique within a resource group."
+ }
+ },
+ "properties": {
+ "type": "object",
+ "properties": {
+ "groupId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to."
+ }
+ },
+ "memberName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to."
+ }
+ },
+ "privateIPAddress": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. A private IP address obtained from the private endpoint's subnet."
+ }
+ }
+ },
+ "metadata": {
+ "description": "Required. Properties of private endpoint IP configurations."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "_2.privateEndpointPrivateDnsZoneGroupType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the Private DNS Zone Group."
+ }
+ },
+ "privateDnsZoneGroupConfigs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private DNS Zone Group config."
+ }
+ },
+ "privateDnsZoneResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource id of the private DNS zone."
+ }
+ }
+ }
+ },
+ "metadata": {
+ "description": "Required. The private DNS Zone Groups to associate the Private Endpoint. A DNS Zone Group can support up to 5 DNS zones."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "_2.secretSetOutputType": {
+ "type": "object",
+ "properties": {
+ "secretResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resourceId of the exported secret."
+ }
+ },
+ "secretUri": {
+ "type": "string",
+ "metadata": {
+ "description": "The secret URI of the exported secret."
+ }
+ },
+ "secretUriWithVersion": {
+ "type": "string",
+ "metadata": {
+ "description": "The secret URI with version of the exported secret."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for the output of the secret set via the secrets export feature.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "blobCorsRuleType": {
+ "type": "object",
+ "properties": {
+ "allowedHeaders": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of headers allowed to be part of the cross-origin request."
+ }
+ },
+ "allowedMethods": {
+ "type": "array",
+ "allowedValues": [
+ "CONNECT",
+ "DELETE",
+ "GET",
+ "HEAD",
+ "MERGE",
+ "OPTIONS",
+ "PATCH",
+ "POST",
+ "PUT",
+ "TRACE"
+ ],
+ "metadata": {
+ "description": "Required. A list of HTTP methods that are allowed to be executed by the origin."
+ }
+ },
+ "allowedOrigins": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of origin domains that will be allowed via CORS, or \"*\" to allow all domains."
+ }
+ },
+ "exposedHeaders": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of response headers to expose to CORS clients."
+ }
+ },
+ "maxAgeInSeconds": {
+ "type": "int",
+ "metadata": {
+ "description": "Required. The number of seconds that the client/browser should cache a preflight response."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The type for a cors rule.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "blob-service/main.bicep",
+ "originalIdentifier": "corsRuleType"
+ }
+ }
+ },
+ "containerType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the Storage Container to deploy."
+ }
+ },
+ "defaultEncryptionScope": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Default the container to use specified encryption scope for all writes."
+ }
+ },
+ "denyEncryptionScopeOverride": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Block override of encryption scope from the container default."
+ }
+ },
+ "enableNfsV3AllSquash": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable NFSv3 all squash on blob container."
+ }
+ },
+ "enableNfsV3RootSquash": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable NFSv3 root squash on blob container."
+ }
+ },
+ "immutableStorageWithVersioningEnabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. This is an immutable property, when set to true it enables object level immutability at the container level. The property is immutable and can only be set to true at the container creation time. Existing containers must undergo a migration process."
+ }
+ },
+ "immutabilityPolicy": {
+ "$ref": "#/definitions/_1.immutabilityPolicyType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Configure immutability policy."
+ }
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Storage/storageAccounts/blobServices/containers@2024-01-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. A name-value pair to associate with the container as metadata."
+ },
+ "nullable": true
+ },
+ "publicAccess": {
+ "type": "string",
+ "allowedValues": [
+ "Blob",
+ "Container",
+ "None"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specifies whether data in the container may be accessed publicly and the level of access."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The type of a storage container.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "blob-service/main.bicep"
+ }
+ }
+ },
+ "customerManagedKeyWithAutoRotateType": {
+ "type": "object",
+ "properties": {
+ "keyVaultResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource ID of a key vault to reference a customer managed key for encryption from."
+ }
+ },
+ "keyName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the customer managed key to use for encryption."
+ }
+ },
+ "keyVersion": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The version of the customer managed key to reference for encryption. If not provided, using version as per 'autoRotationEnabled' setting."
+ }
+ },
+ "autoRotationEnabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable auto-rotating to the latest key version. Default is `true`. If set to `false`, the latest key version at the time of the deployment is used."
+ }
+ },
+ "userAssignedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. User assigned identity to use when fetching the customer managed key. Required if no system assigned identity is available for use."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a customer-managed key. To be used if the resource type supports auto-rotation of the customer-managed key.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "diagnosticSettingFullType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the diagnostic setting."
+ }
+ },
+ "logCategoriesAndGroups": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here."
+ }
+ },
+ "categoryGroup": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection."
+ }
+ },
+ "metricCategories": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection."
+ }
+ },
+ "logAnalyticsDestinationType": {
+ "type": "string",
+ "allowedValues": [
+ "AzureDiagnostics",
+ "Dedicated"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type."
+ }
+ },
+ "workspaceResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "storageAccountResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "eventHubAuthorizationRuleResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to."
+ }
+ },
+ "eventHubName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "marketplacePartnerResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "diagnosticSettingMetricsOnlyType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of diagnostic setting."
+ }
+ },
+ "metricCategories": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection."
+ }
+ },
+ "logAnalyticsDestinationType": {
+ "type": "string",
+ "allowedValues": [
+ "AzureDiagnostics",
+ "Dedicated"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type."
+ }
+ },
+ "workspaceResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "storageAccountResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "eventHubAuthorizationRuleResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to."
+ }
+ },
+ "eventHubName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "marketplacePartnerResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a diagnostic setting. To be used if only metrics are supported by the resource provider.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "fileCorsRuleType": {
+ "type": "object",
+ "properties": {
+ "allowedHeaders": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of headers allowed to be part of the cross-origin request."
+ }
+ },
+ "allowedMethods": {
+ "type": "array",
+ "allowedValues": [
+ "CONNECT",
+ "DELETE",
+ "GET",
+ "HEAD",
+ "MERGE",
+ "OPTIONS",
+ "PATCH",
+ "POST",
+ "PUT",
+ "TRACE"
+ ],
+ "metadata": {
+ "description": "Required. A list of HTTP methods that are allowed to be executed by the origin."
+ }
+ },
+ "allowedOrigins": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of origin domains that will be allowed via CORS, or \"*\" to allow all domains."
+ }
+ },
+ "exposedHeaders": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of response headers to expose to CORS clients."
+ }
+ },
+ "maxAgeInSeconds": {
+ "type": "int",
+ "metadata": {
+ "description": "Required. The number of seconds that the client/browser should cache a preflight response."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The type for a cors rule.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "file-service/main.bicep",
+ "originalIdentifier": "corsRuleType"
+ }
+ }
+ },
+ "fileShareType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the file share."
+ }
+ },
+ "accessTier": {
+ "type": "string",
+ "allowedValues": [
+ "Cool",
+ "Hot",
+ "Premium",
+ "TransactionOptimized"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Access tier for specific share. Required if the Storage Account kind is set to FileStorage (should be set to \"Premium\"). GpV2 account can choose between TransactionOptimized (default), Hot, and Cool."
+ }
+ },
+ "enabledProtocols": {
+ "type": "string",
+ "allowedValues": [
+ "NFS",
+ "SMB"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The authentication protocol that is used for the file share. Can only be specified when creating a share."
+ }
+ },
+ "rootSquash": {
+ "type": "string",
+ "allowedValues": [
+ "AllSquash",
+ "NoRootSquash",
+ "RootSquash"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Permissions for NFS file shares are enforced by the client OS rather than the Azure Files service. Toggling the root squash behavior reduces the rights of the root user for NFS shares."
+ }
+ },
+ "shareQuota": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The maximum size of the share, in gigabytes. Must be greater than 0, and less than or equal to 5120 (5TB). For Large File Shares, the maximum size is 102400 (100TB)."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The type for a file share.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "file-service/main.bicep"
+ }
+ }
+ },
+ "lockType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the name of lock."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "CanNotDelete",
+ "None",
+ "ReadOnly"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ },
+ "notes": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the notes of the lock."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a lock.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "managedIdentityAllType": {
+ "type": "object",
+ "properties": {
+ "systemAssigned": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enables system assigned managed identity on the resource."
+ }
+ },
+ "userAssignedResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID(s) to assign to the resource. Required if a user assigned identity is used for encryption."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "objectReplicationPolicyRuleType": {
+ "type": "object",
+ "properties": {
+ "ruleId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The ID of the rule. Auto-generated on destination account. Required for source account."
+ }
+ },
+ "containerName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the source container."
+ }
+ },
+ "destinationContainerName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the destination container. If not provided, the same name as the source container will be used."
+ }
+ },
+ "filters": {
+ "type": "object",
+ "properties": {
+ "prefixMatch": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The prefix to match for the replication policy rule."
+ }
+ },
+ "minCreationTime": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The minimum creation time to match for the replication policy rule."
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The filters for the object replication policy rule."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The type of an object replication policy rule.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "object-replication-policy/policy/main.bicep"
+ }
+ }
+ },
+ "permissionScopeType": {
+ "type": "object",
+ "properties": {
+ "permissions": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The permissions for the local user. Possible values include: Read (r), Write (w), Delete (d), List (l), and Create (c)."
+ }
+ },
+ "resourceName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of resource, normally the container name or the file share name, used by the local user."
+ }
+ },
+ "service": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The service used by the local user, e.g. blob, file."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "local-user/main.bicep"
+ }
+ }
+ },
+ "privateEndpointMultiServiceType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private endpoint."
+ }
+ },
+ "location": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The location to deploy the private endpoint to."
+ }
+ },
+ "privateLinkServiceConnectionName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private link connection to create."
+ }
+ },
+ "service": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The subresource to deploy the private endpoint for. For example \"blob\", \"table\", \"queue\" or \"file\" for a Storage Account's Private Endpoints."
+ }
+ },
+ "subnetResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Resource ID of the subnet where the endpoint needs to be created."
+ }
+ },
+ "resourceGroupResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID of the Resource Group the Private Endpoint will be created in. If not specified, the Resource Group of the provided Virtual Network Subnet is used."
+ }
+ },
+ "privateDnsZoneGroup": {
+ "$ref": "#/definitions/_2.privateEndpointPrivateDnsZoneGroupType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The private DNS zone group to configure for the private endpoint."
+ }
+ },
+ "isManualConnection": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. If Manual Private Link Connection is required."
+ }
+ },
+ "manualConnectionRequestMessage": {
+ "type": "string",
+ "nullable": true,
+ "maxLength": 140,
+ "metadata": {
+ "description": "Optional. A message passed to the owner of the remote resource with the manual connection request."
+ }
+ },
+ "customDnsConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/_2.privateEndpointCustomDnsConfigType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Custom DNS configurations."
+ }
+ },
+ "ipConfigurations": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/_2.privateEndpointIpConfigurationType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints."
+ }
+ },
+ "applicationSecurityGroupResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Application security groups in which the private endpoint IP configuration is included."
+ }
+ },
+ "customNetworkInterfaceName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The custom name of the network interface attached to the private endpoint."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "nullable": true,
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-07-01#properties/tags"
+ },
+ "description": "Optional. Tags to be applied on all resources/resource groups in this deployment."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a private endpoint. To be used if the private endpoint's default service / groupId can NOT be assumed (i.e., for services that have more than one subresource, like Storage Account with Blob (blob, table, queue, file, ...).",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "queueCorsRuleType": {
+ "type": "object",
+ "properties": {
+ "allowedHeaders": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of headers allowed to be part of the cross-origin request."
+ }
+ },
+ "allowedMethods": {
+ "type": "array",
+ "allowedValues": [
+ "CONNECT",
+ "DELETE",
+ "GET",
+ "HEAD",
+ "MERGE",
+ "OPTIONS",
+ "PATCH",
+ "POST",
+ "PUT",
+ "TRACE"
+ ],
+ "metadata": {
+ "description": "Required. A list of HTTP methods that are allowed to be executed by the origin."
+ }
+ },
+ "allowedOrigins": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of origin domains that will be allowed via CORS, or \"*\" to allow all domains."
+ }
+ },
+ "exposedHeaders": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of response headers to expose to CORS clients."
+ }
+ },
+ "maxAgeInSeconds": {
+ "type": "int",
+ "metadata": {
+ "description": "Required. The number of seconds that the client/browser should cache a preflight response."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The type for a cors rule.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "queue-service/main.bicep",
+ "originalIdentifier": "corsRuleType"
+ }
+ }
+ },
+ "queueType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the queue."
+ }
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Storage/storageAccounts/queueServices/queues@2024-01-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. Metadata to set on the queue."
+ },
+ "nullable": true
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The type for a queue.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "queue-service/main.bicep"
+ }
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "secretsOutputType": {
+ "type": "object",
+ "properties": {},
+ "additionalProperties": {
+ "$ref": "#/definitions/_2.secretSetOutputType",
+ "metadata": {
+ "description": "An exported secret's references."
+ }
+ },
+ "metadata": {
+ "description": "A map of the exported secrets",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "sshAuthorizedKeyType": {
+ "type": "object",
+ "properties": {
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Description used to store the function/usage of the key."
+ }
+ },
+ "key": {
+ "type": "securestring",
+ "metadata": {
+ "description": "Required. SSH public key base64 encoded. The format should be: '{keyType} {keyData}', e.g. ssh-rsa AAAABBBB."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "local-user/main.bicep"
+ }
+ }
+ },
+ "tableCorsRuleType": {
+ "type": "object",
+ "properties": {
+ "allowedHeaders": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of headers allowed to be part of the cross-origin request."
+ }
+ },
+ "allowedMethods": {
+ "type": "array",
+ "allowedValues": [
+ "CONNECT",
+ "DELETE",
+ "GET",
+ "HEAD",
+ "MERGE",
+ "OPTIONS",
+ "PATCH",
+ "POST",
+ "PUT",
+ "TRACE"
+ ],
+ "metadata": {
+ "description": "Required. A list of HTTP methods that are allowed to be executed by the origin."
+ }
+ },
+ "allowedOrigins": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of origin domains that will be allowed via CORS, or \"*\" to allow all domains."
+ }
+ },
+ "exposedHeaders": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of response headers to expose to CORS clients."
+ }
+ },
+ "maxAgeInSeconds": {
+ "type": "int",
+ "metadata": {
+ "description": "Required. The number of seconds that the client/browser should cache a preflight response."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The type for a cors rule.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "table-service/main.bicep",
+ "originalIdentifier": "corsRuleType"
+ }
+ }
+ },
+ "tableType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the table."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The type for a table.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "table-service/main.bicep"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "maxLength": 24,
+ "metadata": {
+ "description": "Required. Name of the Storage Account. Must be lower-case."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Optional. Location for all resources."
+ }
+ },
+ "extendedLocationZone": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Extended Zone location (ex 'losangeles'). When supplied, the storage account will be created in the specified zone under the parent location. The extended zone must be available in the supplied parent location."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "managedIdentities": {
+ "$ref": "#/definitions/managedIdentityAllType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The managed identity definition for this resource."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "defaultValue": "StorageV2",
+ "allowedValues": [
+ "Storage",
+ "StorageV2",
+ "BlobStorage",
+ "FileStorage",
+ "BlockBlobStorage"
+ ],
+ "metadata": {
+ "description": "Optional. Type of Storage Account to create."
+ }
+ },
+ "skuName": {
+ "type": "string",
+ "defaultValue": "Standard_GRS",
+ "allowedValues": [
+ "Standard_LRS",
+ "Standard_ZRS",
+ "Standard_GRS",
+ "Standard_GZRS",
+ "Standard_RAGRS",
+ "Standard_RAGZRS",
+ "StandardV2_LRS",
+ "StandardV2_ZRS",
+ "StandardV2_GRS",
+ "StandardV2_GZRS",
+ "Premium_LRS",
+ "Premium_ZRS",
+ "PremiumV2_LRS",
+ "PremiumV2_ZRS"
+ ],
+ "metadata": {
+ "description": "Optional. Storage Account Sku Name - note: certain V2 SKUs require the use of: kind = FileStorage."
+ }
+ },
+ "accessTier": {
+ "type": "string",
+ "defaultValue": "Hot",
+ "allowedValues": [
+ "Premium",
+ "Hot",
+ "Cool",
+ "Cold"
+ ],
+ "metadata": {
+ "description": "Conditional. Required if the Storage Account kind is set to BlobStorage. The access tier is used for billing. The \"Premium\" access tier is the default value for premium block blobs storage account type and it cannot be changed for the premium block blobs storage account type."
+ }
+ },
+ "largeFileSharesState": {
+ "type": "string",
+ "defaultValue": "Disabled",
+ "allowedValues": [
+ "Disabled",
+ "Enabled"
+ ],
+ "metadata": {
+ "description": "Optional. Allow large file shares if set to 'Enabled'. It cannot be disabled once it is enabled. Only supported on locally redundant and zone redundant file shares. It cannot be set on FileStorage storage accounts (storage accounts for premium file shares)."
+ }
+ },
+ "azureFilesIdentityBasedAuthentication": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Storage/storageAccounts@2025-01-01#properties/properties/properties/azureFilesIdentityBasedAuthentication"
+ },
+ "description": "Optional. Provides the identity based authentication settings for Azure Files."
+ },
+ "nullable": true
+ },
+ "defaultToOAuthAuthentication": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. A boolean flag which indicates whether the default authentication is OAuth or not."
+ }
+ },
+ "allowSharedKeyAccess": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Indicates whether the storage account permits requests to be authorized with the account access key via Shared Key. If false, then all requests, including shared access signatures, must be authorized with Azure Active Directory (Azure AD). The default value is null, which is equivalent to true."
+ }
+ },
+ "privateEndpoints": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateEndpointMultiServiceType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible."
+ }
+ },
+ "managementPolicyRules": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Storage/storageAccounts/managementPolicies@2025-01-01#properties/properties/properties/policy/properties/rules"
+ },
+ "description": "Optional. The Storage Account ManagementPolicies Rules."
+ },
+ "nullable": true
+ },
+ "networkAcls": {
+ "$ref": "#/definitions/networkAclsType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Networks ACLs, this value contains IPs to whitelist and/or Subnet information. If in use, bypass needs to be supplied. For security reasons, it is recommended to set the DefaultAction Deny."
+ }
+ },
+ "requireInfrastructureEncryption": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. A Boolean indicating whether or not the service applies a secondary layer of encryption with platform managed keys for data at rest. For security reasons, it is recommended to set it to true."
+ }
+ },
+ "allowCrossTenantReplication": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Allow or disallow cross AAD tenant object replication."
+ }
+ },
+ "customDomainName": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. Sets the custom domain name assigned to the storage account. Name is the CNAME source."
+ }
+ },
+ "customDomainUseSubDomainName": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Indicates whether indirect CName validation is enabled. This should only be set on updates."
+ }
+ },
+ "dnsEndpointType": {
+ "type": "string",
+ "nullable": true,
+ "allowedValues": [
+ "AzureDnsZone",
+ "Standard"
+ ],
+ "metadata": {
+ "description": "Optional. Allows you to specify the type of endpoint. Set this to AzureDNSZone to create a large number of accounts in a single subscription, which creates accounts in an Azure DNS Zone and the endpoint URL will have an alphanumeric DNS Zone identifier."
+ }
+ },
+ "blobServices": {
+ "$ref": "#/definitions/blobServiceType",
+ "defaultValue": "[if(not(equals(parameters('kind'), 'FileStorage')), createObject('containerDeleteRetentionPolicyEnabled', true(), 'containerDeleteRetentionPolicyDays', 7, 'deleteRetentionPolicyEnabled', true(), 'deleteRetentionPolicyDays', 6), createObject())]",
+ "metadata": {
+ "description": "Optional. Blob service and containers to deploy."
+ }
+ },
+ "fileServices": {
+ "$ref": "#/definitions/fileServiceType",
+ "defaultValue": {},
+ "metadata": {
+ "description": "Optional. File service and shares to deploy."
+ }
+ },
+ "queueServices": {
+ "$ref": "#/definitions/queueServiceType",
+ "defaultValue": {},
+ "metadata": {
+ "description": "Optional. Queue service and queues to create."
+ }
+ },
+ "tableServices": {
+ "$ref": "#/definitions/tableServiceType",
+ "defaultValue": {},
+ "metadata": {
+ "description": "Optional. Table service and tables to create."
+ }
+ },
+ "allowBlobPublicAccess": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Indicates whether public access is enabled for all blobs or containers in the storage account. For security reasons, it is recommended to set it to false."
+ }
+ },
+ "minimumTlsVersion": {
+ "type": "string",
+ "defaultValue": "TLS1_2",
+ "allowedValues": [
+ "TLS1_2"
+ ],
+ "metadata": {
+ "description": "Optional. Set the minimum TLS version on request to storage. The TLS versions 1.0 and 1.1 are deprecated and not supported anymore."
+ }
+ },
+ "enableHierarchicalNamespace": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Conditional. If true, enables Hierarchical Namespace for the storage account. Required if enableSftp or enableNfsV3 is set to true."
+ }
+ },
+ "enableSftp": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. If true, enables Secure File Transfer Protocol for the storage account. Requires enableHierarchicalNamespace to be true."
+ }
+ },
+ "localUsers": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/localUserType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Local users to deploy for SFTP authentication."
+ }
+ },
+ "isLocalUserEnabled": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Enables local users feature, if set to true."
+ }
+ },
+ "enableNfsV3": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. If true, enables NFS 3.0 support for the storage account. Requires enableHierarchicalNamespace to be true."
+ }
+ },
+ "diagnosticSettings": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/diagnosticSettingMetricsOnlyType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The diagnostic settings of the service."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The lock settings of the service."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Storage/storageAccounts@2025-01-01#properties/tags"
+ },
+ "description": "Optional. Tags of the resource."
+ },
+ "nullable": true
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ },
+ "allowedCopyScope": {
+ "type": "string",
+ "nullable": true,
+ "allowedValues": [
+ "AAD",
+ "PrivateLink"
+ ],
+ "metadata": {
+ "description": "Optional. Restrict copy to and from Storage Accounts within an AAD tenant or with Private Links to the same VNet."
+ }
+ },
+ "publicNetworkAccess": {
+ "type": "string",
+ "nullable": true,
+ "allowedValues": [
+ "Enabled",
+ "Disabled",
+ "SecuredByPerimeter"
+ ],
+ "metadata": {
+ "description": "Optional. Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set and networkAcls are not set."
+ }
+ },
+ "supportsHttpsTrafficOnly": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Allows HTTPS traffic only to storage service if sets to true."
+ }
+ },
+ "customerManagedKey": {
+ "$ref": "#/definitions/customerManagedKeyWithAutoRotateType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The customer managed key definition."
+ }
+ },
+ "sasExpirationPeriod": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. The SAS expiration period. DD.HH:MM:SS."
+ }
+ },
+ "sasExpirationAction": {
+ "type": "string",
+ "defaultValue": "Log",
+ "allowedValues": [
+ "Block",
+ "Log"
+ ],
+ "metadata": {
+ "description": "Optional. The SAS expiration action. Allowed values are Block and Log."
+ }
+ },
+ "keyType": {
+ "type": "string",
+ "nullable": true,
+ "allowedValues": [
+ "Account",
+ "Service"
+ ],
+ "metadata": {
+ "description": "Optional. The keyType to use with Queue & Table services."
+ }
+ },
+ "secretsExportConfiguration": {
+ "$ref": "#/definitions/secretsExportConfigurationType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Key vault reference and secret settings for the module's secrets export."
+ }
+ },
+ "immutableStorageWithVersioning": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Storage/storageAccounts@2025-01-01#properties/properties/properties/immutableStorageWithVersioning"
+ },
+ "description": "Optional. The property is immutable and can only be set to true at the account creation time. When set to true, it enables object level immutability for all the new containers in the account by default. Cannot be enabled for ADLS Gen2 storage accounts."
+ },
+ "nullable": true
+ },
+ "objectReplicationPolicies": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/objectReplicationPolicyType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Object replication policies for the storage account."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "enableReferencedModulesTelemetry": false,
+ "immutabilityValidation": "[if(and(equals(parameters('enableHierarchicalNamespace'), true()), not(empty(parameters('immutableStorageWithVersioning')))), fail('Configuration error: Immutable storage with versioning cannot be enabled when hierarchical namespace is enabled.'), null())]",
+ "supportsBlobService": "[or(or(or(equals(parameters('kind'), 'BlockBlobStorage'), equals(parameters('kind'), 'BlobStorage')), equals(parameters('kind'), 'StorageV2')), equals(parameters('kind'), 'Storage'))]",
+ "supportsFileService": "[or(or(equals(parameters('kind'), 'FileStorage'), equals(parameters('kind'), 'StorageV2')), equals(parameters('kind'), 'Storage'))]",
+ "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]",
+ "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'SystemAssigned,UserAssigned', 'SystemAssigned'), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', 'None')), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]",
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]",
+ "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]",
+ "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]",
+ "Storage Blob Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]",
+ "Storage Blob Data Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b')]",
+ "Storage Blob Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1')]",
+ "Storage Blob Delegator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db58b8e5-c6ad-4a2a-8342-4190687cbf4a')]",
+ "Storage File Data Privileged Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '69566ab7-960f-475b-8e7c-b3118f30c6bd')]",
+ "Storage File Data Privileged Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b8eda974-7b85-4f76-af95-65846b26df6d')]",
+ "Storage File Data SMB Share Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0c867c2a-1d8c-454a-a3db-ab2ea1bdc8bb')]",
+ "Storage File Data SMB Share Elevated Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a7264617-510b-434b-a828-9731dc254ea7')]",
+ "Storage File Data SMB Share Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'aba4ae5f-2193-4029-9191-0cb91df5e314')]",
+ "Storage Queue Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '974c5e8b-45b9-4653-ba55-5f855dd0fb88')]",
+ "Storage Queue Data Message Processor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8a0f0c08-91a1-4084-bc3d-661d67233fed')]",
+ "Storage Queue Data Message Sender": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c6a89b2d-59bc-44d0-9896-0f6e12d7b80a')]",
+ "Storage Queue Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '19e7f393-937e-4f77-808e-94535e297925')]",
+ "Storage Table Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3')]",
+ "Storage Table Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '76199698-9eea-4c19-bc75-cec21354c6b6')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ },
+ "formattedManagementPolicies": "[union(coalesce(parameters('managementPolicyRules'), createArray()), if(and(and(not(empty(parameters('blobServices'))), coalesce(tryGet(parameters('blobServices'), 'isVersioningEnabled'), false())), not(equals(tryGet(parameters('blobServices'), 'versionDeletePolicyDays'), null()))), createArray(createObject('name', 'DeletePreviousVersions (auto-created)', 'enabled', true(), 'type', 'Lifecycle', 'definition', createObject('actions', createObject('version', createObject('delete', createObject('daysAfterCreationGreaterThan', parameters('blobServices').versionDeletePolicyDays))), 'filters', createObject('blobTypes', createArray('blockBlob', 'appendBlob'))))), createArray()))]",
+ "isHSMManagedCMK": "[equals(tryGet(split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), ''), '/'), 7), 'managedHSMs')]"
+ },
+ "resources": {
+ "cMKKeyVault::cMKKey": {
+ "condition": "[and(and(not(variables('isHSMManagedCMK')), not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId')))), and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), not(empty(tryGet(parameters('customerManagedKey'), 'keyName')))))]",
+ "existing": true,
+ "type": "Microsoft.KeyVault/vaults/keys",
+ "apiVersion": "2024-11-01",
+ "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[2]]",
+ "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[4]]",
+ "name": "[format('{0}/{1}', last(split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')), tryGet(parameters('customerManagedKey'), 'keyName'))]"
+ },
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('46d3xbcp.res.storage-storageaccount.{0}.{1}', replace('0.30.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "cMKKeyVault": {
+ "condition": "[and(not(variables('isHSMManagedCMK')), not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))))]",
+ "existing": true,
+ "type": "Microsoft.KeyVault/vaults",
+ "apiVersion": "2025-05-01",
+ "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[2]]",
+ "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[4]]",
+ "name": "[last(split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/'))]"
+ },
+ "cMKUserAssignedIdentity": {
+ "condition": "[not(empty(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId')))]",
+ "existing": true,
+ "type": "Microsoft.ManagedIdentity/userAssignedIdentities",
+ "apiVersion": "2024-11-30",
+ "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[2]]",
+ "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[4]]",
+ "name": "[last(split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/'))]"
+ },
+ "storageAccount": {
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2025-01-01",
+ "name": "[parameters('name')]",
+ "location": "[parameters('location')]",
+ "extendedLocation": "[if(not(empty(parameters('extendedLocationZone'))), createObject('name', parameters('extendedLocationZone'), 'type', 'EdgeZone'), null())]",
+ "kind": "[parameters('kind')]",
+ "sku": {
+ "name": "[parameters('skuName')]"
+ },
+ "identity": "[variables('identity')]",
+ "tags": "[parameters('tags')]",
+ "properties": "[shallowMerge(createArray(createObject('allowSharedKeyAccess', parameters('allowSharedKeyAccess'), 'defaultToOAuthAuthentication', parameters('defaultToOAuthAuthentication'), 'allowCrossTenantReplication', parameters('allowCrossTenantReplication'), 'allowedCopyScope', parameters('allowedCopyScope'), 'customDomain', createObject('name', parameters('customDomainName'), 'useSubDomainName', parameters('customDomainUseSubDomainName')), 'dnsEndpointType', parameters('dnsEndpointType'), 'isLocalUserEnabled', parameters('isLocalUserEnabled'), 'encryption', union(createObject('keySource', if(not(empty(parameters('customerManagedKey'))), 'Microsoft.Keyvault', 'Microsoft.Storage'), 'services', createObject('blob', if(variables('supportsBlobService'), createObject('enabled', true()), null()), 'file', if(variables('supportsFileService'), createObject('enabled', true()), null()), 'table', createObject('enabled', true(), 'keyType', parameters('keyType')), 'queue', createObject('enabled', true(), 'keyType', parameters('keyType'))), 'keyvaultproperties', if(not(empty(parameters('customerManagedKey'))), createObject('keyname', parameters('customerManagedKey').keyName, 'keyvaulturi', if(not(variables('isHSMManagedCMK')), reference('cMKKeyVault').vaultUri, format('https://{0}.managedhsm.azure.net/', last(split(parameters('customerManagedKey').keyVaultResourceId, '/')))), 'keyversion', if(not(empty(tryGet(parameters('customerManagedKey'), 'keyVersion'))), parameters('customerManagedKey').keyVersion, if(coalesce(tryGet(parameters('customerManagedKey'), 'autoRotationEnabled'), true()), null(), if(not(variables('isHSMManagedCMK')), last(split(reference('cMKKeyVault::cMKKey').keyUriWithVersion, '/')), fail('Managed HSM CMK encryption requires either specifying the ''keyVersion'' or omitting the ''autoRotationEnabled'' property. Setting ''autoRotationEnabled'' to false without a ''keyVersion'' is not allowed.'))))), null()), 'identity', createObject('userAssignedIdentity', if(not(empty(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'))), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[2], split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', last(split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/'))), null()))), if(parameters('requireInfrastructureEncryption'), createObject('requireInfrastructureEncryption', if(not(equals(parameters('kind'), 'Storage')), parameters('requireInfrastructureEncryption'), null())), createObject())), 'accessTier', if(and(not(equals(parameters('kind'), 'Storage')), not(equals(parameters('kind'), 'BlockBlobStorage'))), parameters('accessTier'), null()), 'sasPolicy', if(not(empty(parameters('sasExpirationPeriod'))), createObject('expirationAction', parameters('sasExpirationAction'), 'sasExpirationPeriod', parameters('sasExpirationPeriod')), null()), 'supportsHttpsTrafficOnly', parameters('supportsHttpsTrafficOnly'), 'isSftpEnabled', parameters('enableSftp'), 'isNfsV3Enabled', if(parameters('enableNfsV3'), parameters('enableNfsV3'), ''), 'largeFileSharesState', if(or(equals(parameters('skuName'), 'Standard_LRS'), equals(parameters('skuName'), 'Standard_ZRS')), parameters('largeFileSharesState'), null()), 'minimumTlsVersion', parameters('minimumTlsVersion'), 'networkAcls', if(not(empty(parameters('networkAcls'))), union(createObject('resourceAccessRules', tryGet(parameters('networkAcls'), 'resourceAccessRules'), 'defaultAction', coalesce(tryGet(parameters('networkAcls'), 'defaultAction'), 'Deny'), 'virtualNetworkRules', tryGet(parameters('networkAcls'), 'virtualNetworkRules'), 'ipRules', tryGet(parameters('networkAcls'), 'ipRules')), if(contains(parameters('networkAcls'), 'bypass'), createObject('bypass', tryGet(parameters('networkAcls'), 'bypass')), createObject())), createObject('bypass', 'AzureServices', 'defaultAction', 'Deny')), 'allowBlobPublicAccess', parameters('allowBlobPublicAccess'), 'publicNetworkAccess', if(not(empty(parameters('publicNetworkAccess'))), parameters('publicNetworkAccess'), if(and(not(empty(parameters('privateEndpoints'))), empty(parameters('networkAcls'))), 'Disabled', null()))), if(not(empty(parameters('azureFilesIdentityBasedAuthentication'))), createObject('azureFilesIdentityBasedAuthentication', parameters('azureFilesIdentityBasedAuthentication')), createObject()), if(not(equals(parameters('enableHierarchicalNamespace'), null())), createObject('isHnsEnabled', parameters('enableHierarchicalNamespace')), createObject()), createObject('immutableStorageWithVersioning', parameters('immutableStorageWithVersioning'))))]",
+ "dependsOn": [
+ "cMKKeyVault",
+ "cMKKeyVault::cMKKey"
+ ]
+ },
+ "storageAccount_diagnosticSettings": {
+ "copy": {
+ "name": "storageAccount_diagnosticSettings",
+ "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]"
+ },
+ "type": "Microsoft.Insights/diagnosticSettings",
+ "apiVersion": "2021-05-01-preview",
+ "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]",
+ "properties": {
+ "copy": [
+ {
+ "name": "metrics",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]",
+ "input": {
+ "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]",
+ "timeGrain": null
+ }
+ }
+ ],
+ "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]",
+ "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]",
+ "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]",
+ "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]",
+ "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]",
+ "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]"
+ },
+ "dependsOn": [
+ "storageAccount"
+ ]
+ },
+ "storageAccount_lock": {
+ "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]",
+ "type": "Microsoft.Authorization/locks",
+ "apiVersion": "2020-05-01",
+ "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]",
+ "properties": {
+ "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]",
+ "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]"
+ },
+ "dependsOn": [
+ "storageAccount"
+ ]
+ },
+ "storageAccount_roleAssignments": {
+ "copy": {
+ "name": "storageAccount_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "storageAccount"
+ ]
+ },
+ "storageAccount_privateEndpoints": {
+ "copy": {
+ "name": "storageAccount_privateEndpoints",
+ "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-sa-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]",
+ "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]",
+ "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex()))]"
+ },
+ "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Storage/storageAccounts', parameters('name')), 'groupIds', createArray(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service))))), createObject('value', null()))]",
+ "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Storage/storageAccounts', parameters('name')), 'groupIds', createArray(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service), 'requestMessage', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualConnectionRequestMessage'), 'Manual approval required.'))))), createObject('value', null()))]",
+ "subnetResourceId": {
+ "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]"
+ },
+ "enableTelemetry": {
+ "value": "[variables('enableReferencedModulesTelemetry')]"
+ },
+ "location": {
+ "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]"
+ },
+ "lock": {
+ "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]"
+ },
+ "privateDnsZoneGroup": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroup')]"
+ },
+ "roleAssignments": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]"
+ },
+ "tags": {
+ "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]"
+ },
+ "customDnsConfigs": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]"
+ },
+ "ipConfigurations": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]"
+ },
+ "applicationSecurityGroupResourceIds": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]"
+ },
+ "customNetworkInterfaceName": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.5.1644",
+ "templateHash": "16604612898799598358"
+ },
+ "name": "Private Endpoints",
+ "description": "This module deploys a Private Endpoint."
+ },
+ "definitions": {
+ "privateDnsZoneGroupType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the Private DNS Zone Group."
+ }
+ },
+ "privateDnsZoneGroupConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateDnsZoneGroupConfigType"
+ },
+ "metadata": {
+ "description": "Required. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of a private dns zone group."
+ }
+ },
+ "lockType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the name of lock."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "CanNotDelete",
+ "None",
+ "ReadOnly"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ },
+ "notes": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the notes of the lock."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a lock.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "privateDnsZoneGroupConfigType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private DNS zone group config."
+ }
+ },
+ "privateDnsZoneResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource id of the private DNS zone."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The type of a private DNS zone group configuration.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "private-dns-zone-group/main.bicep"
+ }
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the private endpoint resource to create."
+ }
+ },
+ "subnetResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Resource ID of the subnet where the endpoint needs to be created."
+ }
+ },
+ "applicationSecurityGroupResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Application security groups in which the private endpoint IP configuration is included."
+ }
+ },
+ "customNetworkInterfaceName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The custom name of the network interface attached to the private endpoint."
+ }
+ },
+ "ipConfigurations": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/ipConfigurations"
+ },
+ "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints."
+ },
+ "nullable": true
+ },
+ "privateDnsZoneGroup": {
+ "$ref": "#/definitions/privateDnsZoneGroupType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The private DNS zone group to configure for the private endpoint."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Optional. Location for all Resources."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The lock settings of the service."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/tags"
+ },
+ "description": "Optional. Tags to be applied on all resources/resource groups in this deployment."
+ },
+ "nullable": true
+ },
+ "customDnsConfigs": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs"
+ },
+ "description": "Optional. Custom DNS configurations."
+ },
+ "nullable": true
+ },
+ "manualPrivateLinkServiceConnections": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/manualPrivateLinkServiceConnections"
+ },
+ "description": "Conditional. A grouping of information about the connection to the remote resource. Used when the network admin does not have access to approve connections to the remote resource. Required if `privateLinkServiceConnections` is empty."
+ },
+ "nullable": true
+ },
+ "privateLinkServiceConnections": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/privateLinkServiceConnections"
+ },
+ "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty."
+ },
+ "nullable": true
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "DNS Resolver Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f2ebee7-ffd4-4fc0-b3b7-664099fdad5d')]",
+ "DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314')]",
+ "Domain Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'eeaeda52-9324-47f6-8069-5d5bade478b2')]",
+ "Domain Services Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '361898ef-9ed1-48c2-849c-a832951106bb')]",
+ "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.11.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "privateEndpoint": {
+ "type": "Microsoft.Network/privateEndpoints",
+ "apiVersion": "2024-10-01",
+ "name": "[parameters('name')]",
+ "location": "[parameters('location')]",
+ "tags": "[parameters('tags')]",
+ "properties": {
+ "copy": [
+ {
+ "name": "applicationSecurityGroups",
+ "count": "[length(coalesce(parameters('applicationSecurityGroupResourceIds'), createArray()))]",
+ "input": {
+ "id": "[coalesce(parameters('applicationSecurityGroupResourceIds'), createArray())[copyIndex('applicationSecurityGroups')]]"
+ }
+ }
+ ],
+ "customDnsConfigs": "[coalesce(parameters('customDnsConfigs'), createArray())]",
+ "customNetworkInterfaceName": "[coalesce(parameters('customNetworkInterfaceName'), '')]",
+ "ipConfigurations": "[coalesce(parameters('ipConfigurations'), createArray())]",
+ "manualPrivateLinkServiceConnections": "[coalesce(parameters('manualPrivateLinkServiceConnections'), createArray())]",
+ "privateLinkServiceConnections": "[coalesce(parameters('privateLinkServiceConnections'), createArray())]",
+ "subnet": {
+ "id": "[parameters('subnetResourceId')]"
+ }
+ }
+ },
+ "privateEndpoint_lock": {
+ "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]",
+ "type": "Microsoft.Authorization/locks",
+ "apiVersion": "2020-05-01",
+ "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]",
+ "properties": {
+ "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]",
+ "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]"
+ },
+ "dependsOn": [
+ "privateEndpoint"
+ ]
+ },
+ "privateEndpoint_roleAssignments": {
+ "copy": {
+ "name": "privateEndpoint_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateEndpoints', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "privateEndpoint"
+ ]
+ },
+ "privateEndpoint_privateDnsZoneGroup": {
+ "condition": "[not(empty(parameters('privateDnsZoneGroup')))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[tryGet(parameters('privateDnsZoneGroup'), 'name')]"
+ },
+ "privateEndpointName": {
+ "value": "[parameters('name')]"
+ },
+ "privateDnsZoneConfigs": {
+ "value": "[parameters('privateDnsZoneGroup').privateDnsZoneGroupConfigs]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.5.1644",
+ "templateHash": "24141742673128945"
+ },
+ "name": "Private Endpoint Private DNS Zone Groups",
+ "description": "This module deploys a Private Endpoint Private DNS Zone Group."
+ },
+ "definitions": {
+ "privateDnsZoneGroupConfigType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private DNS zone group config."
+ }
+ },
+ "privateDnsZoneResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource id of the private DNS zone."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of a private DNS zone group configuration."
+ }
+ }
+ },
+ "parameters": {
+ "privateEndpointName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment."
+ }
+ },
+ "privateDnsZoneConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateDnsZoneGroupConfigType"
+ },
+ "minLength": 1,
+ "maxLength": 5,
+ "metadata": {
+ "description": "Required. Array of private DNS zone configurations of the private DNS zone group. A DNS zone group can support up to 5 DNS zones."
+ }
+ },
+ "name": {
+ "type": "string",
+ "defaultValue": "default",
+ "metadata": {
+ "description": "Optional. The name of the private DNS zone group."
+ }
+ }
+ },
+ "resources": {
+ "privateEndpoint": {
+ "existing": true,
+ "type": "Microsoft.Network/privateEndpoints",
+ "apiVersion": "2024-10-01",
+ "name": "[parameters('privateEndpointName')]"
+ },
+ "privateDnsZoneGroup": {
+ "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups",
+ "apiVersion": "2024-10-01",
+ "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]",
+ "properties": {
+ "copy": [
+ {
+ "name": "privateDnsZoneConfigs",
+ "count": "[length(parameters('privateDnsZoneConfigs'))]",
+ "input": {
+ "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')].privateDnsZoneResourceId, '/')))]",
+ "properties": {
+ "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')].privateDnsZoneResourceId]"
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the private endpoint DNS zone group."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the private endpoint DNS zone group."
+ },
+ "value": "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', parameters('privateEndpointName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the private endpoint DNS zone group was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "privateEndpoint"
+ ]
+ }
+ },
+ "outputs": {
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the private endpoint was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the private endpoint."
+ },
+ "value": "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]"
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the private endpoint."
+ },
+ "value": "[parameters('name')]"
+ },
+ "location": {
+ "type": "string",
+ "metadata": {
+ "description": "The location the resource was deployed into."
+ },
+ "value": "[reference('privateEndpoint', '2024-10-01', 'full').location]"
+ },
+ "customDnsConfigs": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs",
+ "output": true
+ },
+ "description": "The custom DNS configurations of the private endpoint."
+ },
+ "value": "[reference('privateEndpoint').customDnsConfigs]"
+ },
+ "networkInterfaceResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "The resource IDs of the network interfaces associated with the private endpoint."
+ },
+ "value": "[map(reference('privateEndpoint').networkInterfaces, lambda('nic', lambdaVariables('nic').id))]"
+ },
+ "groupId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "The group Id for the private endpoint Group."
+ },
+ "value": "[coalesce(tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'manualPrivateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0), tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'privateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0))]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "storageAccount"
+ ]
+ },
+ "storageAccount_managementPolicies": {
+ "condition": "[not(empty(coalesce(variables('formattedManagementPolicies'), createArray())))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-Storage-ManagementPolicies', uniqueString(deployment().name, parameters('location')))]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "storageAccountName": {
+ "value": "[parameters('name')]"
+ },
+ "rules": {
+ "value": "[variables('formattedManagementPolicies')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "4538661605890101674"
+ },
+ "name": "Storage Account Management Policies",
+ "description": "This module deploys a Storage Account Management Policy."
+ },
+ "parameters": {
+ "storageAccountName": {
+ "type": "string",
+ "maxLength": 24,
+ "metadata": {
+ "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "rules": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Storage/storageAccounts/managementPolicies@2024-01-01#properties/properties/properties/policy/properties/rules"
+ },
+ "description": "Required. The Storage Account ManagementPolicies Rules."
+ }
+ }
+ },
+ "resources": [
+ {
+ "type": "Microsoft.Storage/storageAccounts/managementPolicies",
+ "apiVersion": "2024-01-01",
+ "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]",
+ "properties": {
+ "policy": {
+ "rules": "[parameters('rules')]"
+ }
+ }
+ }
+ ],
+ "outputs": {
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed management policy."
+ },
+ "value": "default"
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed management policy."
+ },
+ "value": "default"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group of the deployed management policy."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "storageAccount",
+ "storageAccount_blobServices"
+ ]
+ },
+ "storageAccount_localUsers": {
+ "copy": {
+ "name": "storageAccount_localUsers",
+ "count": "[length(coalesce(parameters('localUsers'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-Storage-LocalUsers-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "storageAccountName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('localUsers'), createArray())[copyIndex()].name]"
+ },
+ "hasSshKey": {
+ "value": "[coalesce(parameters('localUsers'), createArray())[copyIndex()].hasSshKey]"
+ },
+ "hasSshPassword": {
+ "value": "[coalesce(parameters('localUsers'), createArray())[copyIndex()].hasSshPassword]"
+ },
+ "permissionScopes": {
+ "value": "[coalesce(parameters('localUsers'), createArray())[copyIndex()].permissionScopes]"
+ },
+ "hasSharedKey": {
+ "value": "[tryGet(coalesce(parameters('localUsers'), createArray())[copyIndex()], 'hasSharedKey')]"
+ },
+ "homeDirectory": {
+ "value": "[tryGet(coalesce(parameters('localUsers'), createArray())[copyIndex()], 'homeDirectory')]"
+ },
+ "sshAuthorizedKeys": {
+ "value": "[tryGet(coalesce(parameters('localUsers'), createArray())[copyIndex()], 'sshAuthorizedKeys')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "17421429164012186211"
+ },
+ "name": "Storage Account Local Users",
+ "description": "This module deploys a Storage Account Local User, which is used for SFTP authentication."
+ },
+ "definitions": {
+ "sshAuthorizedKeyType": {
+ "type": "object",
+ "properties": {
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Description used to store the function/usage of the key."
+ }
+ },
+ "key": {
+ "type": "securestring",
+ "metadata": {
+ "description": "Required. SSH public key base64 encoded. The format should be: '{keyType} {keyData}', e.g. ssh-rsa AAAABBBB."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ },
+ "permissionScopeType": {
+ "type": "object",
+ "properties": {
+ "permissions": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The permissions for the local user. Possible values include: Read (r), Write (w), Delete (d), List (l), and Create (c)."
+ }
+ },
+ "resourceName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of resource, normally the container name or the file share name, used by the local user."
+ }
+ },
+ "service": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The service used by the local user, e.g. blob, file."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ }
+ },
+ "parameters": {
+ "storageAccountName": {
+ "type": "string",
+ "maxLength": 24,
+ "metadata": {
+ "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the local user used for SFTP Authentication."
+ }
+ },
+ "hasSharedKey": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Indicates whether shared key exists. Set it to false to remove existing shared key."
+ }
+ },
+ "hasSshKey": {
+ "type": "bool",
+ "metadata": {
+ "description": "Required. Indicates whether SSH key exists. Set it to false to remove existing SSH key."
+ }
+ },
+ "hasSshPassword": {
+ "type": "bool",
+ "metadata": {
+ "description": "Required. Indicates whether SSH password exists. Set it to false to remove existing SSH password."
+ }
+ },
+ "homeDirectory": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. The local user home directory."
+ }
+ },
+ "permissionScopes": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/permissionScopeType"
+ },
+ "metadata": {
+ "description": "Required. The permission scopes of the local user."
+ }
+ },
+ "sshAuthorizedKeys": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/sshAuthorizedKeyType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The local user SSH authorized keys for SFTP."
+ }
+ }
+ },
+ "resources": {
+ "storageAccount": {
+ "existing": true,
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2024-01-01",
+ "name": "[parameters('storageAccountName')]"
+ },
+ "localUsers": {
+ "type": "Microsoft.Storage/storageAccounts/localUsers",
+ "apiVersion": "2024-01-01",
+ "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('name'))]",
+ "properties": {
+ "hasSharedKey": "[parameters('hasSharedKey')]",
+ "hasSshKey": "[parameters('hasSshKey')]",
+ "hasSshPassword": "[parameters('hasSshPassword')]",
+ "homeDirectory": "[parameters('homeDirectory')]",
+ "permissionScopes": "[parameters('permissionScopes')]",
+ "sshAuthorizedKeys": "[parameters('sshAuthorizedKeys')]"
+ }
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed local user."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group of the deployed local user."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed local user."
+ },
+ "value": "[resourceId('Microsoft.Storage/storageAccounts/localUsers', parameters('storageAccountName'), parameters('name'))]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "storageAccount"
+ ]
+ },
+ "storageAccount_blobServices": {
+ "condition": "[not(empty(parameters('blobServices')))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-Storage-BlobServices', uniqueString(deployment().name, parameters('location')))]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "storageAccountName": {
+ "value": "[parameters('name')]"
+ },
+ "containers": {
+ "value": "[tryGet(parameters('blobServices'), 'containers')]"
+ },
+ "automaticSnapshotPolicyEnabled": {
+ "value": "[tryGet(parameters('blobServices'), 'automaticSnapshotPolicyEnabled')]"
+ },
+ "changeFeedEnabled": {
+ "value": "[tryGet(parameters('blobServices'), 'changeFeedEnabled')]"
+ },
+ "changeFeedRetentionInDays": {
+ "value": "[tryGet(parameters('blobServices'), 'changeFeedRetentionInDays')]"
+ },
+ "containerDeleteRetentionPolicyEnabled": {
+ "value": "[tryGet(parameters('blobServices'), 'containerDeleteRetentionPolicyEnabled')]"
+ },
+ "containerDeleteRetentionPolicyDays": {
+ "value": "[tryGet(parameters('blobServices'), 'containerDeleteRetentionPolicyDays')]"
+ },
+ "containerDeleteRetentionPolicyAllowPermanentDelete": {
+ "value": "[tryGet(parameters('blobServices'), 'containerDeleteRetentionPolicyAllowPermanentDelete')]"
+ },
+ "corsRules": {
+ "value": "[tryGet(parameters('blobServices'), 'corsRules')]"
+ },
+ "defaultServiceVersion": {
+ "value": "[tryGet(parameters('blobServices'), 'defaultServiceVersion')]"
+ },
+ "deleteRetentionPolicyAllowPermanentDelete": {
+ "value": "[tryGet(parameters('blobServices'), 'deleteRetentionPolicyAllowPermanentDelete')]"
+ },
+ "deleteRetentionPolicyEnabled": {
+ "value": "[tryGet(parameters('blobServices'), 'deleteRetentionPolicyEnabled')]"
+ },
+ "deleteRetentionPolicyDays": {
+ "value": "[tryGet(parameters('blobServices'), 'deleteRetentionPolicyDays')]"
+ },
+ "isVersioningEnabled": {
+ "value": "[tryGet(parameters('blobServices'), 'isVersioningEnabled')]"
+ },
+ "lastAccessTimeTrackingPolicyEnabled": {
+ "value": "[tryGet(parameters('blobServices'), 'lastAccessTimeTrackingPolicyEnabled')]"
+ },
+ "restorePolicyEnabled": {
+ "value": "[tryGet(parameters('blobServices'), 'restorePolicyEnabled')]"
+ },
+ "restorePolicyDays": {
+ "value": "[tryGet(parameters('blobServices'), 'restorePolicyDays')]"
+ },
+ "diagnosticSettings": {
+ "value": "[tryGet(parameters('blobServices'), 'diagnosticSettings')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "4804748808287128942"
+ },
+ "name": "Storage Account blob Services",
+ "description": "This module deploys a Storage Account Blob Service."
+ },
+ "definitions": {
+ "corsRuleType": {
+ "type": "object",
+ "properties": {
+ "allowedHeaders": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of headers allowed to be part of the cross-origin request."
+ }
+ },
+ "allowedMethods": {
+ "type": "array",
+ "allowedValues": [
+ "CONNECT",
+ "DELETE",
+ "GET",
+ "HEAD",
+ "MERGE",
+ "OPTIONS",
+ "PATCH",
+ "POST",
+ "PUT",
+ "TRACE"
+ ],
+ "metadata": {
+ "description": "Required. A list of HTTP methods that are allowed to be executed by the origin."
+ }
+ },
+ "allowedOrigins": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of origin domains that will be allowed via CORS, or \"*\" to allow all domains."
+ }
+ },
+ "exposedHeaders": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of response headers to expose to CORS clients."
+ }
+ },
+ "maxAgeInSeconds": {
+ "type": "int",
+ "metadata": {
+ "description": "Required. The number of seconds that the client/browser should cache a preflight response."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for a cors rule."
+ }
+ },
+ "containerType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the Storage Container to deploy."
+ }
+ },
+ "defaultEncryptionScope": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Default the container to use specified encryption scope for all writes."
+ }
+ },
+ "denyEncryptionScopeOverride": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Block override of encryption scope from the container default."
+ }
+ },
+ "enableNfsV3AllSquash": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable NFSv3 all squash on blob container."
+ }
+ },
+ "enableNfsV3RootSquash": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable NFSv3 root squash on blob container."
+ }
+ },
+ "immutableStorageWithVersioningEnabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. This is an immutable property, when set to true it enables object level immutability at the container level. The property is immutable and can only be set to true at the container creation time. Existing containers must undergo a migration process."
+ }
+ },
+ "immutabilityPolicy": {
+ "$ref": "#/definitions/immutabilityPolicyType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Configure immutability policy."
+ }
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Storage/storageAccounts/blobServices/containers@2024-01-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. A name-value pair to associate with the container as metadata."
+ },
+ "nullable": true
+ },
+ "publicAccess": {
+ "type": "string",
+ "allowedValues": [
+ "Blob",
+ "Container",
+ "None"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specifies whether data in the container may be accessed publicly and the level of access."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of a storage container."
+ }
+ },
+ "diagnosticSettingFullType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the diagnostic setting."
+ }
+ },
+ "logCategoriesAndGroups": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here."
+ }
+ },
+ "categoryGroup": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection."
+ }
+ },
+ "metricCategories": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection."
+ }
+ },
+ "logAnalyticsDestinationType": {
+ "type": "string",
+ "allowedValues": [
+ "AzureDiagnostics",
+ "Dedicated"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type."
+ }
+ },
+ "workspaceResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "storageAccountResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "eventHubAuthorizationRuleResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to."
+ }
+ },
+ "eventHubName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "marketplacePartnerResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "immutabilityPolicyType": {
+ "type": "object",
+ "properties": {
+ "immutabilityPeriodSinceCreationInDays": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The immutability period for the blobs in the container since the policy creation, in days."
+ }
+ },
+ "allowProtectedAppendWrites": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to an append blob while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API."
+ }
+ },
+ "allowProtectedAppendWritesAll": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to both \"Append and Block Blobs\" while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API. The \"allowProtectedAppendWrites\" and \"allowProtectedAppendWritesAll\" properties are mutually exclusive."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The type for an immutability policy.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "container/main.bicep"
+ }
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "storageAccountName": {
+ "type": "string",
+ "maxLength": 24,
+ "metadata": {
+ "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "automaticSnapshotPolicyEnabled": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Automatic Snapshot is enabled if set to true."
+ }
+ },
+ "changeFeedEnabled": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. The blob service properties for change feed events. Indicates whether change feed event logging is enabled for the Blob service."
+ }
+ },
+ "changeFeedRetentionInDays": {
+ "type": "int",
+ "nullable": true,
+ "minValue": 1,
+ "maxValue": 146000,
+ "metadata": {
+ "description": "Optional. Indicates whether change feed event logging is enabled for the Blob service. Indicates the duration of changeFeed retention in days. If left blank, it indicates an infinite retention of the change feed."
+ }
+ },
+ "containerDeleteRetentionPolicyEnabled": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. The blob service properties for container soft delete. Indicates whether DeleteRetentionPolicy is enabled."
+ }
+ },
+ "containerDeleteRetentionPolicyDays": {
+ "type": "int",
+ "nullable": true,
+ "minValue": 1,
+ "maxValue": 365,
+ "metadata": {
+ "description": "Optional. Indicates the number of days that the deleted item should be retained."
+ }
+ },
+ "containerDeleteRetentionPolicyAllowPermanentDelete": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. This property when set to true allows deletion of the soft deleted blob versions and snapshots. This property cannot be used with blob restore policy. This property only applies to blob service and does not apply to containers or file share."
+ }
+ },
+ "corsRules": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/corsRuleType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The List of CORS rules. You can include up to five CorsRule elements in the request."
+ }
+ },
+ "defaultServiceVersion": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Indicates the default version to use for requests to the Blob service if an incoming request's version is not specified. Possible values include version 2008-10-27 and all more recent versions."
+ }
+ },
+ "deleteRetentionPolicyEnabled": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. The blob service properties for blob soft delete."
+ }
+ },
+ "deleteRetentionPolicyDays": {
+ "type": "int",
+ "defaultValue": 7,
+ "minValue": 1,
+ "maxValue": 365,
+ "metadata": {
+ "description": "Optional. Indicates the number of days that the deleted blob should be retained."
+ }
+ },
+ "deleteRetentionPolicyAllowPermanentDelete": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. This property when set to true allows deletion of the soft deleted blob versions and snapshots. This property cannot be used with blob restore policy. This property only applies to blob service and does not apply to containers or file share."
+ }
+ },
+ "isVersioningEnabled": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Use versioning to automatically maintain previous versions of your blobs. Cannot be enabled for ADLS Gen2 storage accounts."
+ }
+ },
+ "lastAccessTimeTrackingPolicyEnabled": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. The blob service property to configure last access time based tracking policy. When set to true last access time based tracking is enabled."
+ }
+ },
+ "restorePolicyEnabled": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. The blob service properties for blob restore policy. If point-in-time restore is enabled, then versioning, change feed, and blob soft delete must also be enabled."
+ }
+ },
+ "restorePolicyDays": {
+ "type": "int",
+ "defaultValue": 7,
+ "minValue": 1,
+ "metadata": {
+ "description": "Optional. How long this blob can be restored. It should be less than DeleteRetentionPolicy days."
+ }
+ },
+ "containers": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/containerType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Blob containers to create."
+ }
+ },
+ "diagnosticSettings": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/diagnosticSettingFullType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The diagnostic settings of the service."
+ }
+ }
+ },
+ "variables": {
+ "enableReferencedModulesTelemetry": false,
+ "name": "default"
+ },
+ "resources": {
+ "storageAccount": {
+ "existing": true,
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2025-01-01",
+ "name": "[parameters('storageAccountName')]"
+ },
+ "blobServices": {
+ "type": "Microsoft.Storage/storageAccounts/blobServices",
+ "apiVersion": "2025-01-01",
+ "name": "[format('{0}/{1}', parameters('storageAccountName'), variables('name'))]",
+ "properties": {
+ "automaticSnapshotPolicyEnabled": "[parameters('automaticSnapshotPolicyEnabled')]",
+ "changeFeed": "[if(parameters('changeFeedEnabled'), createObject('enabled', true(), 'retentionInDays', parameters('changeFeedRetentionInDays')), null())]",
+ "containerDeleteRetentionPolicy": {
+ "enabled": "[parameters('containerDeleteRetentionPolicyEnabled')]",
+ "days": "[parameters('containerDeleteRetentionPolicyDays')]",
+ "allowPermanentDelete": "[if(equals(parameters('containerDeleteRetentionPolicyEnabled'), true()), parameters('containerDeleteRetentionPolicyAllowPermanentDelete'), null())]"
+ },
+ "cors": "[if(not(equals(parameters('corsRules'), null())), createObject('corsRules', parameters('corsRules')), null())]",
+ "defaultServiceVersion": "[parameters('defaultServiceVersion')]",
+ "deleteRetentionPolicy": {
+ "enabled": "[parameters('deleteRetentionPolicyEnabled')]",
+ "days": "[parameters('deleteRetentionPolicyDays')]",
+ "allowPermanentDelete": "[if(and(parameters('deleteRetentionPolicyEnabled'), parameters('deleteRetentionPolicyAllowPermanentDelete')), true(), null())]"
+ },
+ "isVersioningEnabled": "[parameters('isVersioningEnabled')]",
+ "lastAccessTimeTrackingPolicy": "[if(and(not(equals(reference('storageAccount', '2025-01-01', 'full').kind, 'Storage')), empty(tryGet(reference('storageAccount', '2025-01-01', 'full'), 'extendedLocation'))), createObject('enable', parameters('lastAccessTimeTrackingPolicyEnabled'), 'name', if(equals(parameters('lastAccessTimeTrackingPolicyEnabled'), true()), 'AccessTimeTracking', null()), 'trackingGranularityInDays', if(equals(parameters('lastAccessTimeTrackingPolicyEnabled'), true()), 1, null())), null())]",
+ "restorePolicy": "[if(parameters('restorePolicyEnabled'), createObject('enabled', true(), 'days', parameters('restorePolicyDays')), null())]"
+ },
+ "dependsOn": [
+ "storageAccount"
+ ]
+ },
+ "blobServices_diagnosticSettings": {
+ "copy": {
+ "name": "blobServices_diagnosticSettings",
+ "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]"
+ },
+ "type": "Microsoft.Insights/diagnosticSettings",
+ "apiVersion": "2021-05-01-preview",
+ "scope": "[format('Microsoft.Storage/storageAccounts/{0}/blobServices/{1}', parameters('storageAccountName'), variables('name'))]",
+ "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', variables('name')))]",
+ "properties": {
+ "copy": [
+ {
+ "name": "metrics",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]",
+ "input": {
+ "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]",
+ "timeGrain": null
+ }
+ },
+ {
+ "name": "logs",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]",
+ "input": {
+ "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]",
+ "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]"
+ }
+ }
+ ],
+ "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]",
+ "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]",
+ "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]",
+ "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]",
+ "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]",
+ "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]"
+ },
+ "dependsOn": [
+ "blobServices"
+ ]
+ },
+ "blobServices_container": {
+ "copy": {
+ "name": "blobServices_container",
+ "count": "[length(coalesce(parameters('containers'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-Container-{1}', deployment().name, copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "storageAccountName": {
+ "value": "[parameters('storageAccountName')]"
+ },
+ "blobServiceName": {
+ "value": "[variables('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('containers'), createArray())[copyIndex()].name]"
+ },
+ "defaultEncryptionScope": {
+ "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'defaultEncryptionScope')]"
+ },
+ "denyEncryptionScopeOverride": {
+ "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'denyEncryptionScopeOverride')]"
+ },
+ "enableNfsV3AllSquash": {
+ "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'enableNfsV3AllSquash')]"
+ },
+ "enableNfsV3RootSquash": {
+ "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'enableNfsV3RootSquash')]"
+ },
+ "immutableStorageWithVersioningEnabled": {
+ "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'immutableStorageWithVersioningEnabled')]"
+ },
+ "metadata": {
+ "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'metadata')]"
+ },
+ "publicAccess": {
+ "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'publicAccess')]"
+ },
+ "roleAssignments": {
+ "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'roleAssignments')]"
+ },
+ "immutabilityPolicy": {
+ "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'immutabilityPolicy')]"
+ },
+ "enableTelemetry": {
+ "value": "[variables('enableReferencedModulesTelemetry')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "4737601268768949442"
+ },
+ "name": "Storage Account Blob Containers",
+ "description": "This module deploys a Storage Account Blob Container."
+ },
+ "definitions": {
+ "immutabilityPolicyType": {
+ "type": "object",
+ "properties": {
+ "immutabilityPeriodSinceCreationInDays": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The immutability period for the blobs in the container since the policy creation, in days."
+ }
+ },
+ "allowProtectedAppendWrites": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to an append blob while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API."
+ }
+ },
+ "allowProtectedAppendWritesAll": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to both \"Append and Block Blobs\" while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API. The \"allowProtectedAppendWrites\" and \"allowProtectedAppendWritesAll\" properties are mutually exclusive."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for an immutability policy."
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "storageAccountName": {
+ "type": "string",
+ "maxLength": 24,
+ "metadata": {
+ "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "blobServiceName": {
+ "type": "string",
+ "defaultValue": "default",
+ "metadata": {
+ "description": "Optional. The name of the parent Blob Service. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the Storage Container to deploy."
+ }
+ },
+ "defaultEncryptionScope": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Default the container to use specified encryption scope for all writes."
+ }
+ },
+ "denyEncryptionScopeOverride": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Block override of encryption scope from the container default."
+ }
+ },
+ "enableNfsV3AllSquash": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Enable NFSv3 all squash on blob container."
+ }
+ },
+ "enableNfsV3RootSquash": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Enable NFSv3 root squash on blob container."
+ }
+ },
+ "immutableStorageWithVersioningEnabled": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. This is an immutable property, when set to true it enables object level immutability at the container level. The property is immutable and can only be set to true at the container creation time. Existing containers must undergo a migration process."
+ }
+ },
+ "immutabilityPolicy": {
+ "$ref": "#/definitions/immutabilityPolicyType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Configure immutability policy."
+ }
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Storage/storageAccounts/blobServices/containers@2024-01-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. A name-value pair to associate with the container as metadata."
+ },
+ "defaultValue": {}
+ },
+ "publicAccess": {
+ "type": "string",
+ "defaultValue": "None",
+ "allowedValues": [
+ "Container",
+ "Blob",
+ "None"
+ ],
+ "metadata": {
+ "description": "Optional. Specifies whether data in the container may be accessed publicly and the level of access."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]",
+ "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]",
+ "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]",
+ "Storage Blob Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]",
+ "Storage Blob Data Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b')]",
+ "Storage Blob Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1')]",
+ "Storage Blob Delegator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db58b8e5-c6ad-4a2a-8342-4190687cbf4a')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ }
+ },
+ "resources": {
+ "storageAccount::blobServices": {
+ "existing": true,
+ "type": "Microsoft.Storage/storageAccounts/blobServices",
+ "apiVersion": "2025-01-01",
+ "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('blobServiceName'))]"
+ },
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.storage-blobcontainer.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "storageAccount": {
+ "existing": true,
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2025-01-01",
+ "name": "[parameters('storageAccountName')]"
+ },
+ "container": {
+ "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
+ "apiVersion": "2025-01-01",
+ "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name'))]",
+ "properties": {
+ "defaultEncryptionScope": "[parameters('defaultEncryptionScope')]",
+ "denyEncryptionScopeOverride": "[parameters('denyEncryptionScopeOverride')]",
+ "enableNfsV3AllSquash": "[if(equals(parameters('enableNfsV3AllSquash'), true()), parameters('enableNfsV3AllSquash'), null())]",
+ "enableNfsV3RootSquash": "[if(equals(parameters('enableNfsV3RootSquash'), true()), parameters('enableNfsV3RootSquash'), null())]",
+ "immutableStorageWithVersioning": "[if(parameters('immutableStorageWithVersioningEnabled'), createObject('enabled', parameters('immutableStorageWithVersioningEnabled')), null())]",
+ "metadata": "[parameters('metadata')]",
+ "publicAccess": "[parameters('publicAccess')]"
+ }
+ },
+ "container_roleAssignments": {
+ "copy": {
+ "name": "container_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Storage/storageAccounts/{0}/blobServices/{1}/containers/{2}', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "container"
+ ]
+ },
+ "container_immutabilityPolicy": {
+ "condition": "[not(empty(coalesce(parameters('immutabilityPolicy'), createObject())))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[take(format('{0}-ImmutPol', deployment().name), 64)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "storageAccountName": {
+ "value": "[parameters('storageAccountName')]"
+ },
+ "containerName": {
+ "value": "[parameters('name')]"
+ },
+ "immutabilityPeriodSinceCreationInDays": {
+ "value": "[tryGet(parameters('immutabilityPolicy'), 'immutabilityPeriodSinceCreationInDays')]"
+ },
+ "allowProtectedAppendWrites": {
+ "value": "[tryGet(parameters('immutabilityPolicy'), 'allowProtectedAppendWrites')]"
+ },
+ "allowProtectedAppendWritesAll": {
+ "value": "[tryGet(parameters('immutabilityPolicy'), 'allowProtectedAppendWritesAll')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "9416359146780945405"
+ },
+ "name": "Storage Account Blob Container Immutability Policies",
+ "description": "This module deploys a Storage Account Blob Container Immutability Policy."
+ },
+ "parameters": {
+ "storageAccountName": {
+ "type": "string",
+ "maxLength": 24,
+ "metadata": {
+ "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "containerName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent container to apply the policy to. Required if the template is used in a standalone deployment."
+ }
+ },
+ "immutabilityPeriodSinceCreationInDays": {
+ "type": "int",
+ "defaultValue": 365,
+ "metadata": {
+ "description": "Optional. The immutability period for the blobs in the container since the policy creation, in days."
+ }
+ },
+ "allowProtectedAppendWrites": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to an append blob while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API. The \"allowProtectedAppendWrites\" and \"allowProtectedAppendWritesAll\" properties are mutually exclusive."
+ }
+ },
+ "allowProtectedAppendWritesAll": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to both \"Append and Block Blobs\" while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API. The \"allowProtectedAppendWrites\" and \"allowProtectedAppendWritesAll\" properties are mutually exclusive."
+ }
+ }
+ },
+ "variables": {
+ "name": "default"
+ },
+ "resources": [
+ {
+ "type": "Microsoft.Storage/storageAccounts/blobServices/containers/immutabilityPolicies",
+ "apiVersion": "2025-01-01",
+ "name": "[format('{0}/{1}/{2}/{3}', parameters('storageAccountName'), 'default', parameters('containerName'), variables('name'))]",
+ "properties": {
+ "immutabilityPeriodSinceCreationInDays": "[parameters('immutabilityPeriodSinceCreationInDays')]",
+ "allowProtectedAppendWrites": "[parameters('allowProtectedAppendWrites')]",
+ "allowProtectedAppendWritesAll": "[parameters('allowProtectedAppendWritesAll')]"
+ }
+ }
+ ],
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed immutability policy."
+ },
+ "value": "[variables('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed immutability policy."
+ },
+ "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices/containers/immutabilityPolicies', parameters('storageAccountName'), 'default', parameters('containerName'), variables('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group of the deployed immutability policy."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "container"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed container."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed container."
+ },
+ "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group of the deployed container."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "blobServices"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed blob service."
+ },
+ "value": "[variables('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed blob service."
+ },
+ "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('storageAccountName'), variables('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed blob service."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "storageAccount"
+ ]
+ },
+ "storageAccount_fileServices": {
+ "condition": "[not(empty(parameters('fileServices')))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-Storage-FileServices', uniqueString(deployment().name, parameters('location')))]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "storageAccountName": {
+ "value": "[parameters('name')]"
+ },
+ "diagnosticSettings": {
+ "value": "[tryGet(parameters('fileServices'), 'diagnosticSettings')]"
+ },
+ "protocolSettings": {
+ "value": "[tryGet(parameters('fileServices'), 'protocolSettings')]"
+ },
+ "shareDeleteRetentionPolicy": {
+ "value": "[tryGet(parameters('fileServices'), 'shareDeleteRetentionPolicy')]"
+ },
+ "shares": {
+ "value": "[tryGet(parameters('fileServices'), 'shares')]"
+ },
+ "corsRules": {
+ "value": "[tryGet(parameters('fileServices'), 'corsRules')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "8847095544204825048"
+ },
+ "name": "Storage Account File Share Services",
+ "description": "This module deploys a Storage Account File Share Service."
+ },
+ "definitions": {
+ "corsRuleType": {
+ "type": "object",
+ "properties": {
+ "allowedHeaders": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of headers allowed to be part of the cross-origin request."
+ }
+ },
+ "allowedMethods": {
+ "type": "array",
+ "allowedValues": [
+ "CONNECT",
+ "DELETE",
+ "GET",
+ "HEAD",
+ "MERGE",
+ "OPTIONS",
+ "PATCH",
+ "POST",
+ "PUT",
+ "TRACE"
+ ],
+ "metadata": {
+ "description": "Required. A list of HTTP methods that are allowed to be executed by the origin."
+ }
+ },
+ "allowedOrigins": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of origin domains that will be allowed via CORS, or \"*\" to allow all domains."
+ }
+ },
+ "exposedHeaders": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of response headers to expose to CORS clients."
+ }
+ },
+ "maxAgeInSeconds": {
+ "type": "int",
+ "metadata": {
+ "description": "Required. The number of seconds that the client/browser should cache a preflight response."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for a cors rule."
+ }
+ },
+ "fileShareType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the file share."
+ }
+ },
+ "accessTier": {
+ "type": "string",
+ "allowedValues": [
+ "Cool",
+ "Hot",
+ "Premium",
+ "TransactionOptimized"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Access tier for specific share. Required if the Storage Account kind is set to FileStorage (should be set to \"Premium\"). GpV2 account can choose between TransactionOptimized (default), Hot, and Cool."
+ }
+ },
+ "enabledProtocols": {
+ "type": "string",
+ "allowedValues": [
+ "NFS",
+ "SMB"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The authentication protocol that is used for the file share. Can only be specified when creating a share."
+ }
+ },
+ "rootSquash": {
+ "type": "string",
+ "allowedValues": [
+ "AllSquash",
+ "NoRootSquash",
+ "RootSquash"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Permissions for NFS file shares are enforced by the client OS rather than the Azure Files service. Toggling the root squash behavior reduces the rights of the root user for NFS shares."
+ }
+ },
+ "shareQuota": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The maximum size of the share, in gigabytes. Must be greater than 0, and less than or equal to 5120 (5TB). For Large File Shares, the maximum size is 102400 (100TB)."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for a file share."
+ }
+ },
+ "diagnosticSettingFullType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the diagnostic setting."
+ }
+ },
+ "logCategoriesAndGroups": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here."
+ }
+ },
+ "categoryGroup": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection."
+ }
+ },
+ "metricCategories": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection."
+ }
+ },
+ "logAnalyticsDestinationType": {
+ "type": "string",
+ "allowedValues": [
+ "AzureDiagnostics",
+ "Dedicated"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type."
+ }
+ },
+ "workspaceResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "storageAccountResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "eventHubAuthorizationRuleResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to."
+ }
+ },
+ "eventHubName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "marketplacePartnerResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0"
+ }
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "storageAccountName": {
+ "type": "string",
+ "maxLength": 24,
+ "metadata": {
+ "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "defaultValue": "default",
+ "metadata": {
+ "description": "Optional. The name of the file service."
+ }
+ },
+ "protocolSettings": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Storage/storageAccounts/fileServices@2024-01-01#properties/properties/properties/protocolSettings"
+ },
+ "description": "Optional. Protocol settings for file service."
+ },
+ "defaultValue": {}
+ },
+ "shareDeleteRetentionPolicy": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Storage/storageAccounts/fileServices@2024-01-01#properties/properties/properties/shareDeleteRetentionPolicy"
+ },
+ "description": "Optional. The service properties for soft delete."
+ },
+ "defaultValue": {
+ "enabled": true,
+ "days": 7
+ }
+ },
+ "corsRules": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/corsRuleType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The List of CORS rules. You can include up to five CorsRule elements in the request."
+ }
+ },
+ "diagnosticSettings": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/diagnosticSettingFullType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The diagnostic settings of the service."
+ }
+ },
+ "shares": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/fileShareType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. File shares to create."
+ }
+ }
+ },
+ "variables": {
+ "enableReferencedModulesTelemetry": false
+ },
+ "resources": {
+ "storageAccount": {
+ "existing": true,
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2024-01-01",
+ "name": "[parameters('storageAccountName')]"
+ },
+ "fileServices": {
+ "type": "Microsoft.Storage/storageAccounts/fileServices",
+ "apiVersion": "2024-01-01",
+ "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('name'))]",
+ "properties": {
+ "cors": "[if(not(equals(parameters('corsRules'), null())), createObject('corsRules', parameters('corsRules')), null())]",
+ "protocolSettings": "[parameters('protocolSettings')]",
+ "shareDeleteRetentionPolicy": "[parameters('shareDeleteRetentionPolicy')]"
+ }
+ },
+ "fileServices_diagnosticSettings": {
+ "copy": {
+ "name": "fileServices_diagnosticSettings",
+ "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]"
+ },
+ "type": "Microsoft.Insights/diagnosticSettings",
+ "apiVersion": "2021-05-01-preview",
+ "scope": "[format('Microsoft.Storage/storageAccounts/{0}/fileServices/{1}', parameters('storageAccountName'), parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]",
+ "properties": {
+ "copy": [
+ {
+ "name": "metrics",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]",
+ "input": {
+ "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]",
+ "timeGrain": null
+ }
+ },
+ {
+ "name": "logs",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]",
+ "input": {
+ "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]",
+ "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]"
+ }
+ }
+ ],
+ "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]",
+ "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]",
+ "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]",
+ "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]",
+ "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]",
+ "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]"
+ },
+ "dependsOn": [
+ "fileServices"
+ ]
+ },
+ "fileServices_shares": {
+ "copy": {
+ "name": "fileServices_shares",
+ "count": "[length(coalesce(parameters('shares'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-FileShare-{1}', deployment().name, copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "storageAccountName": {
+ "value": "[parameters('storageAccountName')]"
+ },
+ "fileServicesName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('shares'), createArray())[copyIndex()].name]"
+ },
+ "accessTier": {
+ "value": "[coalesce(tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'accessTier'), if(equals(reference('storageAccount', '2024-01-01', 'full').kind, 'FileStorage'), 'Premium', 'TransactionOptimized'))]"
+ },
+ "enabledProtocols": {
+ "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'enabledProtocols')]"
+ },
+ "rootSquash": {
+ "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'rootSquash')]"
+ },
+ "shareQuota": {
+ "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'shareQuota')]"
+ },
+ "roleAssignments": {
+ "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'roleAssignments')]"
+ },
+ "enableTelemetry": {
+ "value": "[variables('enableReferencedModulesTelemetry')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "1953115828549574279"
+ },
+ "name": "Storage Account File Shares",
+ "description": "This module deploys a Storage Account File Share."
+ },
+ "definitions": {
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "storageAccountName": {
+ "type": "string",
+ "maxLength": 24,
+ "metadata": {
+ "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "fileServicesName": {
+ "type": "string",
+ "defaultValue": "default",
+ "metadata": {
+ "description": "Conditional. The name of the parent file service. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the file share to create."
+ }
+ },
+ "accessTier": {
+ "type": "string",
+ "defaultValue": "TransactionOptimized",
+ "allowedValues": [
+ "Premium",
+ "Hot",
+ "Cool",
+ "TransactionOptimized"
+ ],
+ "metadata": {
+ "description": "Conditional. Access tier for specific share. Required if the Storage Account kind is set to FileStorage (should be set to \"Premium\"). GpV2 account can choose between TransactionOptimized (default), Hot, and Cool."
+ }
+ },
+ "shareQuota": {
+ "type": "int",
+ "defaultValue": 5120,
+ "metadata": {
+ "description": "Optional. The maximum size of the share, in gigabytes. Must be greater than 0, and less than or equal to 5120 (5TB). For Large File Shares, the maximum size is 102400 (100TB)."
+ }
+ },
+ "enabledProtocols": {
+ "type": "string",
+ "defaultValue": "SMB",
+ "allowedValues": [
+ "NFS",
+ "SMB"
+ ],
+ "metadata": {
+ "description": "Optional. The authentication protocol that is used for the file share. Can only be specified when creating a share."
+ }
+ },
+ "rootSquash": {
+ "type": "string",
+ "defaultValue": "NoRootSquash",
+ "allowedValues": [
+ "AllSquash",
+ "NoRootSquash",
+ "RootSquash"
+ ],
+ "metadata": {
+ "description": "Optional. Permissions for NFS file shares are enforced by the client OS rather than the Azure Files service. Toggling the root squash behavior reduces the rights of the root user for NFS shares."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]",
+ "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]",
+ "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]",
+ "Storage File Data SMB Share Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0c867c2a-1d8c-454a-a3db-ab2ea1bdc8bb')]",
+ "Storage File Data SMB Share Elevated Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a7264617-510b-434b-a828-9731dc254ea7')]",
+ "Storage File Data SMB Share Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'aba4ae5f-2193-4029-9191-0cb91df5e314')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ }
+ },
+ "resources": {
+ "storageAccount::fileService": {
+ "existing": true,
+ "type": "Microsoft.Storage/storageAccounts/fileServices",
+ "apiVersion": "2024-01-01",
+ "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('fileServicesName'))]"
+ },
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.storage-fileshare.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "storageAccount": {
+ "existing": true,
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2024-01-01",
+ "name": "[parameters('storageAccountName')]"
+ },
+ "fileShare": {
+ "type": "Microsoft.Storage/storageAccounts/fileServices/shares",
+ "apiVersion": "2024-01-01",
+ "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), parameters('fileServicesName'), parameters('name'))]",
+ "properties": {
+ "accessTier": "[parameters('accessTier')]",
+ "shareQuota": "[parameters('shareQuota')]",
+ "rootSquash": "[if(equals(parameters('enabledProtocols'), 'NFS'), parameters('rootSquash'), null())]",
+ "enabledProtocols": "[parameters('enabledProtocols')]"
+ }
+ },
+ "fileShare_roleAssignments": {
+ "copy": {
+ "name": "fileShare_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-Share-Rbac-{1}', uniqueString(deployment().name), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "scope": {
+ "value": "[replace(resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', parameters('storageAccountName'), parameters('fileServicesName'), parameters('name')), '/shares/', '/fileshares/')]"
+ },
+ "name": {
+ "value": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', parameters('storageAccountName'), parameters('fileServicesName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]"
+ },
+ "roleDefinitionId": {
+ "value": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]"
+ },
+ "principalId": {
+ "value": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]"
+ },
+ "principalType": {
+ "value": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]"
+ },
+ "condition": {
+ "value": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]"
+ },
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), createObject('value', coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0')), createObject('value', null()))]",
+ "delegatedManagedIdentityResourceId": {
+ "value": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "description": {
+ "value": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "scope": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The scope to deploy the role assignment to."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the role assignment."
+ }
+ },
+ "roleDefinitionId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role definition Id to assign."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User",
+ ""
+ ],
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\""
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "defaultValue": "2.0",
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "resources": [
+ {
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[parameters('scope')]",
+ "name": "[parameters('name')]",
+ "properties": {
+ "roleDefinitionId": "[parameters('roleDefinitionId')]",
+ "principalId": "[parameters('principalId')]",
+ "description": "[parameters('description')]",
+ "principalType": "[if(not(empty(parameters('principalType'))), parameters('principalType'), null())]",
+ "condition": "[if(not(empty(parameters('condition'))), parameters('condition'), null())]",
+ "conditionVersion": "[if(and(not(empty(parameters('conditionVersion'))), not(empty(parameters('condition')))), parameters('conditionVersion'), null())]",
+ "delegatedManagedIdentityResourceId": "[if(not(empty(parameters('delegatedManagedIdentityResourceId'))), parameters('delegatedManagedIdentityResourceId'), null())]"
+ }
+ }
+ ]
+ }
+ },
+ "dependsOn": [
+ "fileShare"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed file share."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed file share."
+ },
+ "value": "[resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', parameters('storageAccountName'), parameters('fileServicesName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group of the deployed file share."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "fileServices",
+ "storageAccount"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed file share service."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed file share service."
+ },
+ "value": "[resourceId('Microsoft.Storage/storageAccounts/fileServices', parameters('storageAccountName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group of the deployed file share service."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "storageAccount"
+ ]
+ },
+ "storageAccount_queueServices": {
+ "condition": "[not(empty(parameters('queueServices')))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-Storage-QueueServices', uniqueString(deployment().name, parameters('location')))]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "storageAccountName": {
+ "value": "[parameters('name')]"
+ },
+ "diagnosticSettings": {
+ "value": "[tryGet(parameters('queueServices'), 'diagnosticSettings')]"
+ },
+ "queues": {
+ "value": "[tryGet(parameters('queueServices'), 'queues')]"
+ },
+ "corsRules": {
+ "value": "[tryGet(parameters('queueServices'), 'corsRules')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "762865197442503763"
+ },
+ "name": "Storage Account Queue Services",
+ "description": "This module deploys a Storage Account Queue Service."
+ },
+ "definitions": {
+ "corsRuleType": {
+ "type": "object",
+ "properties": {
+ "allowedHeaders": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of headers allowed to be part of the cross-origin request."
+ }
+ },
+ "allowedMethods": {
+ "type": "array",
+ "allowedValues": [
+ "CONNECT",
+ "DELETE",
+ "GET",
+ "HEAD",
+ "MERGE",
+ "OPTIONS",
+ "PATCH",
+ "POST",
+ "PUT",
+ "TRACE"
+ ],
+ "metadata": {
+ "description": "Required. A list of HTTP methods that are allowed to be executed by the origin."
+ }
+ },
+ "allowedOrigins": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of origin domains that will be allowed via CORS, or \"*\" to allow all domains."
+ }
+ },
+ "exposedHeaders": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of response headers to expose to CORS clients."
+ }
+ },
+ "maxAgeInSeconds": {
+ "type": "int",
+ "metadata": {
+ "description": "Required. The number of seconds that the client/browser should cache a preflight response."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for a cors rule."
+ }
+ },
+ "queueType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the queue."
+ }
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Storage/storageAccounts/queueServices/queues@2024-01-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. Metadata to set on the queue."
+ },
+ "nullable": true
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for a queue."
+ }
+ },
+ "diagnosticSettingFullType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the diagnostic setting."
+ }
+ },
+ "logCategoriesAndGroups": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here."
+ }
+ },
+ "categoryGroup": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection."
+ }
+ },
+ "metricCategories": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection."
+ }
+ },
+ "logAnalyticsDestinationType": {
+ "type": "string",
+ "allowedValues": [
+ "AzureDiagnostics",
+ "Dedicated"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type."
+ }
+ },
+ "workspaceResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "storageAccountResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "eventHubAuthorizationRuleResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to."
+ }
+ },
+ "eventHubName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "marketplacePartnerResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "storageAccountName": {
+ "type": "string",
+ "maxLength": 24,
+ "metadata": {
+ "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "queues": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/queueType"
+ },
+ "defaultValue": [],
+ "metadata": {
+ "description": "Optional. Queues to create."
+ }
+ },
+ "corsRules": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/corsRuleType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The List of CORS rules. You can include up to five CorsRule elements in the request."
+ }
+ },
+ "diagnosticSettings": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/diagnosticSettingFullType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The diagnostic settings of the service."
+ }
+ }
+ },
+ "variables": {
+ "name": "default"
+ },
+ "resources": {
+ "storageAccount": {
+ "existing": true,
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2024-01-01",
+ "name": "[parameters('storageAccountName')]"
+ },
+ "queueServices": {
+ "type": "Microsoft.Storage/storageAccounts/queueServices",
+ "apiVersion": "2024-01-01",
+ "name": "[format('{0}/{1}', parameters('storageAccountName'), variables('name'))]",
+ "properties": {
+ "cors": "[if(not(equals(parameters('corsRules'), null())), createObject('corsRules', parameters('corsRules')), null())]"
+ }
+ },
+ "queueServices_diagnosticSettings": {
+ "copy": {
+ "name": "queueServices_diagnosticSettings",
+ "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]"
+ },
+ "type": "Microsoft.Insights/diagnosticSettings",
+ "apiVersion": "2021-05-01-preview",
+ "scope": "[format('Microsoft.Storage/storageAccounts/{0}/queueServices/{1}', parameters('storageAccountName'), variables('name'))]",
+ "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', variables('name')))]",
+ "properties": {
+ "copy": [
+ {
+ "name": "metrics",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]",
+ "input": {
+ "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]",
+ "timeGrain": null
+ }
+ },
+ {
+ "name": "logs",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]",
+ "input": {
+ "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]",
+ "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]"
+ }
+ }
+ ],
+ "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]",
+ "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]",
+ "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]",
+ "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]",
+ "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]",
+ "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]"
+ },
+ "dependsOn": [
+ "queueServices"
+ ]
+ },
+ "queueServices_queues": {
+ "copy": {
+ "name": "queueServices_queues",
+ "count": "[length(coalesce(parameters('queues'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-Queue-{1}', deployment().name, copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "storageAccountName": {
+ "value": "[parameters('storageAccountName')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('queues'), createArray())[copyIndex()].name]"
+ },
+ "metadata": {
+ "value": "[tryGet(coalesce(parameters('queues'), createArray())[copyIndex()], 'metadata')]"
+ },
+ "roleAssignments": {
+ "value": "[tryGet(coalesce(parameters('queues'), createArray())[copyIndex()], 'roleAssignments')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "2653192815476217627"
+ },
+ "name": "Storage Account Queues",
+ "description": "This module deploys a Storage Account Queue."
+ },
+ "definitions": {
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "storageAccountName": {
+ "type": "string",
+ "maxLength": 24,
+ "metadata": {
+ "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the storage queue to deploy."
+ }
+ },
+ "metadata": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Storage/storageAccounts/queueServices/queues@2024-01-01#properties/properties/properties/metadata"
+ },
+ "description": "Optional. A name-value pair that represents queue metadata."
+ },
+ "defaultValue": {}
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]",
+ "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]",
+ "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]",
+ "Storage Queue Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '974c5e8b-45b9-4653-ba55-5f855dd0fb88')]",
+ "Storage Queue Data Message Processor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8a0f0c08-91a1-4084-bc3d-661d67233fed')]",
+ "Storage Queue Data Message Sender": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c6a89b2d-59bc-44d0-9896-0f6e12d7b80a')]",
+ "Storage Queue Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '19e7f393-937e-4f77-808e-94535e297925')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ }
+ },
+ "resources": {
+ "storageAccount::queueServices": {
+ "existing": true,
+ "type": "Microsoft.Storage/storageAccounts/queueServices",
+ "apiVersion": "2024-01-01",
+ "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]"
+ },
+ "storageAccount": {
+ "existing": true,
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2024-01-01",
+ "name": "[parameters('storageAccountName')]"
+ },
+ "queue": {
+ "type": "Microsoft.Storage/storageAccounts/queueServices/queues",
+ "apiVersion": "2024-01-01",
+ "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]",
+ "properties": {
+ "metadata": "[parameters('metadata')]"
+ }
+ },
+ "queue_roleAssignments": {
+ "copy": {
+ "name": "queue_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Storage/storageAccounts/{0}/queueServices/{1}/queues/{2}', parameters('storageAccountName'), 'default', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Storage/storageAccounts/queueServices/queues', parameters('storageAccountName'), 'default', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "queue"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed queue."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed queue."
+ },
+ "value": "[resourceId('Microsoft.Storage/storageAccounts/queueServices/queues', parameters('storageAccountName'), 'default', parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group of the deployed queue."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ }
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed queue service."
+ },
+ "value": "[variables('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed queue service."
+ },
+ "value": "[resourceId('Microsoft.Storage/storageAccounts/queueServices', parameters('storageAccountName'), variables('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group of the deployed queue service."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "storageAccount"
+ ]
+ },
+ "storageAccount_tableServices": {
+ "condition": "[not(empty(parameters('tableServices')))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-Storage-TableServices', uniqueString(deployment().name, parameters('location')))]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "storageAccountName": {
+ "value": "[parameters('name')]"
+ },
+ "diagnosticSettings": {
+ "value": "[tryGet(parameters('tableServices'), 'diagnosticSettings')]"
+ },
+ "tables": {
+ "value": "[tryGet(parameters('tableServices'), 'tables')]"
+ },
+ "corsRules": {
+ "value": "[tryGet(parameters('tableServices'), 'corsRules')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "17140438874562378925"
+ },
+ "name": "Storage Account Table Services",
+ "description": "This module deploys a Storage Account Table Service."
+ },
+ "definitions": {
+ "corsRuleType": {
+ "type": "object",
+ "properties": {
+ "allowedHeaders": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of headers allowed to be part of the cross-origin request."
+ }
+ },
+ "allowedMethods": {
+ "type": "array",
+ "allowedValues": [
+ "CONNECT",
+ "DELETE",
+ "GET",
+ "HEAD",
+ "MERGE",
+ "OPTIONS",
+ "PATCH",
+ "POST",
+ "PUT",
+ "TRACE"
+ ],
+ "metadata": {
+ "description": "Required. A list of HTTP methods that are allowed to be executed by the origin."
+ }
+ },
+ "allowedOrigins": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of origin domains that will be allowed via CORS, or \"*\" to allow all domains."
+ }
+ },
+ "exposedHeaders": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of response headers to expose to CORS clients."
+ }
+ },
+ "maxAgeInSeconds": {
+ "type": "int",
+ "metadata": {
+ "description": "Required. The number of seconds that the client/browser should cache a preflight response."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for a cors rule."
+ }
+ },
+ "tableType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the table."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for a table."
+ }
+ },
+ "diagnosticSettingFullType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the diagnostic setting."
+ }
+ },
+ "logCategoriesAndGroups": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here."
+ }
+ },
+ "categoryGroup": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection."
+ }
+ },
+ "metricCategories": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection."
+ }
+ },
+ "logAnalyticsDestinationType": {
+ "type": "string",
+ "allowedValues": [
+ "AzureDiagnostics",
+ "Dedicated"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type."
+ }
+ },
+ "workspaceResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "storageAccountResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "eventHubAuthorizationRuleResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to."
+ }
+ },
+ "eventHubName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "marketplacePartnerResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "storageAccountName": {
+ "type": "string",
+ "maxLength": 24,
+ "metadata": {
+ "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "tables": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/tableType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Tables to create."
+ }
+ },
+ "corsRules": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/corsRuleType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The List of CORS rules. You can include up to five CorsRule elements in the request."
+ }
+ },
+ "diagnosticSettings": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/diagnosticSettingFullType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The diagnostic settings of the service."
+ }
+ }
+ },
+ "variables": {
+ "name": "default"
+ },
+ "resources": {
+ "storageAccount": {
+ "existing": true,
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2024-01-01",
+ "name": "[parameters('storageAccountName')]"
+ },
+ "tableServices": {
+ "type": "Microsoft.Storage/storageAccounts/tableServices",
+ "apiVersion": "2024-01-01",
+ "name": "[format('{0}/{1}', parameters('storageAccountName'), variables('name'))]",
+ "properties": {
+ "cors": "[if(not(equals(parameters('corsRules'), null())), createObject('corsRules', parameters('corsRules')), null())]"
+ }
+ },
+ "tableServices_diagnosticSettings": {
+ "copy": {
+ "name": "tableServices_diagnosticSettings",
+ "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]"
+ },
+ "type": "Microsoft.Insights/diagnosticSettings",
+ "apiVersion": "2021-05-01-preview",
+ "scope": "[format('Microsoft.Storage/storageAccounts/{0}/tableServices/{1}', parameters('storageAccountName'), variables('name'))]",
+ "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', variables('name')))]",
+ "properties": {
+ "copy": [
+ {
+ "name": "metrics",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]",
+ "input": {
+ "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]",
+ "timeGrain": null
+ }
+ },
+ {
+ "name": "logs",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]",
+ "input": {
+ "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]",
+ "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]"
+ }
+ }
+ ],
+ "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]",
+ "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]",
+ "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]",
+ "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]",
+ "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]",
+ "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]"
+ },
+ "dependsOn": [
+ "tableServices"
+ ]
+ },
+ "tableServices_tables": {
+ "copy": {
+ "name": "tableServices_tables",
+ "count": "[length(coalesce(parameters('tables'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-Table-{1}', deployment().name, copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[coalesce(parameters('tables'), createArray())[copyIndex()].name]"
+ },
+ "storageAccountName": {
+ "value": "[parameters('storageAccountName')]"
+ },
+ "roleAssignments": {
+ "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'roleAssignments')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "11466809443516053137"
+ },
+ "name": "Storage Account Table",
+ "description": "This module deploys a Storage Account Table."
+ },
+ "definitions": {
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "storageAccountName": {
+ "type": "string",
+ "maxLength": 24,
+ "metadata": {
+ "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the table."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]",
+ "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]",
+ "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]",
+ "Storage Table Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3')]",
+ "Storage Table Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '76199698-9eea-4c19-bc75-cec21354c6b6')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ }
+ },
+ "resources": {
+ "storageAccount::tableServices": {
+ "existing": true,
+ "type": "Microsoft.Storage/storageAccounts/tableServices",
+ "apiVersion": "2024-01-01",
+ "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]"
+ },
+ "storageAccount": {
+ "existing": true,
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2024-01-01",
+ "name": "[parameters('storageAccountName')]"
+ },
+ "table": {
+ "type": "Microsoft.Storage/storageAccounts/tableServices/tables",
+ "apiVersion": "2024-01-01",
+ "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]"
+ },
+ "table_roleAssignments": {
+ "copy": {
+ "name": "table_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Storage/storageAccounts/{0}/tableServices/{1}/tables/{2}', parameters('storageAccountName'), 'default', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Storage/storageAccounts/tableServices/tables', parameters('storageAccountName'), 'default', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "table"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed table."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed table."
+ },
+ "value": "[resourceId('Microsoft.Storage/storageAccounts/tableServices/tables', parameters('storageAccountName'), 'default', parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group of the deployed table."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ }
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed table service."
+ },
+ "value": "[variables('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed table service."
+ },
+ "value": "[resourceId('Microsoft.Storage/storageAccounts/tableServices', parameters('storageAccountName'), variables('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group of the deployed table service."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "storageAccount"
+ ]
+ },
+ "secretsExport": {
+ "condition": "[not(equals(parameters('secretsExportConfiguration'), null()))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-secrets-kv', uniqueString(deployment().name, parameters('location')))]",
+ "subscriptionId": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[2]]",
+ "resourceGroup": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[4]]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "keyVaultName": {
+ "value": "[last(split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/'))]"
+ },
+ "secretsToSet": {
+ "value": "[union(createArray(), if(contains(parameters('secretsExportConfiguration'), 'accessKey1Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'accessKey1Name'), 'value', listKeys('storageAccount', '2025-01-01').keys[0].value)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'connectionString1Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'connectionString1Name'), 'value', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', parameters('name'), listKeys('storageAccount', '2025-01-01').keys[0].value, environment().suffixes.storage))), createArray()), if(contains(parameters('secretsExportConfiguration'), 'accessKey2Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'accessKey2Name'), 'value', listKeys('storageAccount', '2025-01-01').keys[1].value)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'connectionString2Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'connectionString2Name'), 'value', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', parameters('name'), listKeys('storageAccount', '2025-01-01').keys[1].value, environment().suffixes.storage))), createArray()))]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "13614544361780789643"
+ }
+ },
+ "definitions": {
+ "secretSetOutputType": {
+ "type": "object",
+ "properties": {
+ "secretResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resourceId of the exported secret."
+ }
+ },
+ "secretUri": {
+ "type": "string",
+ "metadata": {
+ "description": "The secret URI of the exported secret."
+ }
+ },
+ "secretUriWithVersion": {
+ "type": "string",
+ "metadata": {
+ "description": "The secret URI with version of the exported secret."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for the output of the secret set via the secrets export feature.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0"
+ }
+ }
+ },
+ "secretToSetType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the secret to set."
+ }
+ },
+ "value": {
+ "type": "securestring",
+ "metadata": {
+ "description": "Required. The value of the secret to set."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for the secret to set via the secrets export feature.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "keyVaultName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the Key Vault to set the ecrets in."
+ }
+ },
+ "secretsToSet": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/secretToSetType"
+ },
+ "metadata": {
+ "description": "Required. The secrets to set in the Key Vault."
+ }
+ }
+ },
+ "resources": {
+ "keyVault": {
+ "existing": true,
+ "type": "Microsoft.KeyVault/vaults",
+ "apiVersion": "2024-11-01",
+ "name": "[parameters('keyVaultName')]"
+ },
+ "secrets": {
+ "copy": {
+ "name": "secrets",
+ "count": "[length(parameters('secretsToSet'))]"
+ },
+ "type": "Microsoft.KeyVault/vaults/secrets",
+ "apiVersion": "2024-11-01",
+ "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretsToSet')[copyIndex()].name)]",
+ "properties": {
+ "value": "[parameters('secretsToSet')[copyIndex()].value]"
+ }
+ }
+ },
+ "outputs": {
+ "secretsSet": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/secretSetOutputType"
+ },
+ "metadata": {
+ "description": "The references to the secrets exported to the provided Key Vault."
+ },
+ "copy": {
+ "count": "[length(range(0, length(coalesce(parameters('secretsToSet'), createArray()))))]",
+ "input": {
+ "secretResourceId": "[resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), parameters('secretsToSet')[range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()]].name)]",
+ "secretUri": "[reference(format('secrets[{0}]', range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()])).secretUri]",
+ "secretUriWithVersion": "[reference(format('secrets[{0}]', range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()])).secretUriWithVersion]"
+ }
+ }
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "storageAccount"
+ ]
+ },
+ "storageAccount_objectReplicationPolicies": {
+ "copy": {
+ "name": "storageAccount_objectReplicationPolicies",
+ "count": "[length(coalesce(parameters('objectReplicationPolicies'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-Storage-ObjRepPolicy-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "storageAccountName": {
+ "value": "[parameters('name')]"
+ },
+ "destinationAccountResourceId": {
+ "value": "[coalesce(parameters('objectReplicationPolicies'), createArray())[copyIndex()].destinationStorageAccountResourceId]"
+ },
+ "enableMetrics": {
+ "value": "[coalesce(tryGet(coalesce(parameters('objectReplicationPolicies'), createArray())[copyIndex()], 'enableMetrics'), false())]"
+ },
+ "rules": {
+ "value": "[tryGet(coalesce(parameters('objectReplicationPolicies'), createArray())[copyIndex()], 'rules')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "3528396711847214833"
+ },
+ "name": "Storage Account Object Replication Policy",
+ "description": "This module deploys a Storage Account Object Replication Policy for both the source account and destination account."
+ },
+ "definitions": {
+ "objectReplicationPolicyRuleType": {
+ "type": "object",
+ "properties": {
+ "ruleId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The ID of the rule. Auto-generated on destination account. Required for source account."
+ }
+ },
+ "containerName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the source container."
+ }
+ },
+ "destinationContainerName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the destination container. If not provided, the same name as the source container will be used."
+ }
+ },
+ "filters": {
+ "type": "object",
+ "properties": {
+ "prefixMatch": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The prefix to match for the replication policy rule."
+ }
+ },
+ "minCreationTime": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The minimum creation time to match for the replication policy rule."
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The filters for the object replication policy rule."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The type of an object replication policy rule.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "policy/main.bicep"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of the policy."
+ }
+ },
+ "storageAccountName": {
+ "type": "string",
+ "maxLength": 24,
+ "metadata": {
+ "description": "Required. The name of the parent Storage Account."
+ }
+ },
+ "destinationAccountResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Resource ID of the destination storage account for replication."
+ }
+ },
+ "enableMetrics": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Whether metrics are enabled for the object replication policy."
+ }
+ },
+ "rules": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/objectReplicationPolicyRuleType"
+ },
+ "metadata": {
+ "description": "Required. Rules for the object replication policy."
+ }
+ }
+ },
+ "variables": {
+ "destAccountResourceIdParts": "[split(parameters('destinationAccountResourceId'), '/')]",
+ "destAccountName": "[if(not(empty(variables('destAccountResourceIdParts'))), last(variables('destAccountResourceIdParts')), parameters('destinationAccountResourceId'))]",
+ "destAccountSubscription": "[if(greater(length(variables('destAccountResourceIdParts')), 2), variables('destAccountResourceIdParts')[2], subscription().subscriptionId)]",
+ "destAccountResourceGroupName": "[if(greater(length(variables('destAccountResourceIdParts')), 4), variables('destAccountResourceIdParts')[4], resourceGroup().name)]"
+ },
+ "resources": {
+ "storageAccount": {
+ "existing": true,
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2025-01-01",
+ "name": "[parameters('storageAccountName')]"
+ },
+ "destinationPolicy": {
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[take(format('{0}-ObjRep-Policy-dest-{1}', deployment().name, variables('destAccountName')), 64)]",
+ "subscriptionId": "[variables('destAccountSubscription')]",
+ "resourceGroup": "[variables('destAccountResourceGroupName')]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[coalesce(parameters('name'), 'default')]"
+ },
+ "storageAccountName": {
+ "value": "[variables('destAccountName')]"
+ },
+ "sourceStorageAccountResourceId": {
+ "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"
+ },
+ "destinationAccountResourceId": {
+ "value": "[parameters('destinationAccountResourceId')]"
+ },
+ "enableMetrics": {
+ "value": "[parameters('enableMetrics')]"
+ },
+ "rules": {
+ "value": "[parameters('rules')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "4325417308313318683"
+ },
+ "name": "Storage Account Object Replication Policy",
+ "description": "This module deploys a Storage Account Object Replication Policy for a provided storage account."
+ },
+ "definitions": {
+ "objectReplicationPolicyRuleType": {
+ "type": "object",
+ "properties": {
+ "ruleId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The ID of the rule. Auto-generated on destination account. Required for source account."
+ }
+ },
+ "containerName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the source container."
+ }
+ },
+ "destinationContainerName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the destination container. If not provided, the same name as the source container will be used."
+ }
+ },
+ "filters": {
+ "type": "object",
+ "properties": {
+ "prefixMatch": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The prefix to match for the replication policy rule."
+ }
+ },
+ "minCreationTime": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The minimum creation time to match for the replication policy rule."
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The filters for the object replication policy rule."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of an object replication policy rule."
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the policy."
+ }
+ },
+ "storageAccountName": {
+ "type": "string",
+ "maxLength": 24,
+ "metadata": {
+ "description": "Required. The name of the Storage Account on which to create the policy."
+ }
+ },
+ "sourceStorageAccountResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Resource ID of the source storage account for replication."
+ }
+ },
+ "destinationAccountResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Resource ID of the destination storage account for replication."
+ }
+ },
+ "enableMetrics": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Whether metrics are enabled for the object replication policy."
+ }
+ },
+ "rules": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/objectReplicationPolicyRuleType"
+ },
+ "metadata": {
+ "description": "Required. Rules for the object replication policy."
+ }
+ }
+ },
+ "resources": {
+ "storageAccount": {
+ "existing": true,
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2025-01-01",
+ "name": "[parameters('storageAccountName')]"
+ },
+ "objectReplicationPolicy": {
+ "type": "Microsoft.Storage/storageAccounts/objectReplicationPolicies",
+ "apiVersion": "2025-01-01",
+ "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('name'))]",
+ "properties": {
+ "copy": [
+ {
+ "name": "rules",
+ "count": "[length(parameters('rules'))]",
+ "input": {
+ "ruleId": "[tryGet(parameters('rules')[copyIndex('rules')], 'ruleId')]",
+ "sourceContainer": "[parameters('rules')[copyIndex('rules')].containerName]",
+ "destinationContainer": "[coalesce(tryGet(parameters('rules')[copyIndex('rules')], 'destinationContainerName'), parameters('rules')[copyIndex('rules')].containerName)]",
+ "filters": "[if(not(equals(tryGet(parameters('rules')[copyIndex('rules')], 'filters'), null())), createObject('prefixMatch', tryGet(tryGet(parameters('rules')[copyIndex('rules')], 'filters'), 'prefixMatch'), 'minCreationTime', tryGet(tryGet(parameters('rules')[copyIndex('rules')], 'filters'), 'minCreationTime')), null())]"
+ }
+ }
+ ],
+ "destinationAccount": "[parameters('destinationAccountResourceId')]",
+ "metrics": {
+ "enabled": "[coalesce(parameters('enableMetrics'), false())]"
+ },
+ "sourceAccount": "[parameters('sourceStorageAccountResourceId')]"
+ }
+ }
+ },
+ "outputs": {
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "Resource group name of the provisioned resources."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "objectReplicationPolicyId": {
+ "type": "string",
+ "metadata": {
+ "description": "Resource ID of the created Object Replication Policy."
+ },
+ "value": "[resourceId('Microsoft.Storage/storageAccounts/objectReplicationPolicies', parameters('storageAccountName'), parameters('name'))]"
+ },
+ "policyId": {
+ "type": "string",
+ "metadata": {
+ "description": "Policy ID of the created Object Replication Policy."
+ },
+ "value": "[reference('objectReplicationPolicy').policyId]"
+ },
+ "rules": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Storage/storageAccounts/objectReplicationPolicies@2025-01-01#properties/properties/properties/rules",
+ "output": true
+ },
+ "description": "Rules created Object Replication Policy."
+ },
+ "value": "[reference('objectReplicationPolicy').rules]"
+ }
+ }
+ }
+ }
+ },
+ "sourcePolicy": {
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[take(format('{0}-ObjRep-Policy-source-{1}', deployment().name, parameters('storageAccountName')), 64)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[reference('destinationPolicy').outputs.policyId.value]"
+ },
+ "storageAccountName": {
+ "value": "[parameters('storageAccountName')]"
+ },
+ "sourceStorageAccountResourceId": {
+ "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"
+ },
+ "destinationAccountResourceId": {
+ "value": "[parameters('destinationAccountResourceId')]"
+ },
+ "enableMetrics": {
+ "value": "[parameters('enableMetrics')]"
+ },
+ "rules": {
+ "copy": [
+ {
+ "name": "value",
+ "count": "[length(parameters('rules'))]",
+ "input": "[union(parameters('rules')[copyIndex('value')], createObject('ruleId', reference('destinationPolicy').outputs.rules.value[copyIndex('value')].ruleId))]"
+ }
+ ]
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "4325417308313318683"
+ },
+ "name": "Storage Account Object Replication Policy",
+ "description": "This module deploys a Storage Account Object Replication Policy for a provided storage account."
+ },
+ "definitions": {
+ "objectReplicationPolicyRuleType": {
+ "type": "object",
+ "properties": {
+ "ruleId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The ID of the rule. Auto-generated on destination account. Required for source account."
+ }
+ },
+ "containerName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the source container."
+ }
+ },
+ "destinationContainerName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the destination container. If not provided, the same name as the source container will be used."
+ }
+ },
+ "filters": {
+ "type": "object",
+ "properties": {
+ "prefixMatch": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The prefix to match for the replication policy rule."
+ }
+ },
+ "minCreationTime": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The minimum creation time to match for the replication policy rule."
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The filters for the object replication policy rule."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of an object replication policy rule."
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the policy."
+ }
+ },
+ "storageAccountName": {
+ "type": "string",
+ "maxLength": 24,
+ "metadata": {
+ "description": "Required. The name of the Storage Account on which to create the policy."
+ }
+ },
+ "sourceStorageAccountResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Resource ID of the source storage account for replication."
+ }
+ },
+ "destinationAccountResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Resource ID of the destination storage account for replication."
+ }
+ },
+ "enableMetrics": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Whether metrics are enabled for the object replication policy."
+ }
+ },
+ "rules": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/objectReplicationPolicyRuleType"
+ },
+ "metadata": {
+ "description": "Required. Rules for the object replication policy."
+ }
+ }
+ },
+ "resources": {
+ "storageAccount": {
+ "existing": true,
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2025-01-01",
+ "name": "[parameters('storageAccountName')]"
+ },
+ "objectReplicationPolicy": {
+ "type": "Microsoft.Storage/storageAccounts/objectReplicationPolicies",
+ "apiVersion": "2025-01-01",
+ "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('name'))]",
+ "properties": {
+ "copy": [
+ {
+ "name": "rules",
+ "count": "[length(parameters('rules'))]",
+ "input": {
+ "ruleId": "[tryGet(parameters('rules')[copyIndex('rules')], 'ruleId')]",
+ "sourceContainer": "[parameters('rules')[copyIndex('rules')].containerName]",
+ "destinationContainer": "[coalesce(tryGet(parameters('rules')[copyIndex('rules')], 'destinationContainerName'), parameters('rules')[copyIndex('rules')].containerName)]",
+ "filters": "[if(not(equals(tryGet(parameters('rules')[copyIndex('rules')], 'filters'), null())), createObject('prefixMatch', tryGet(tryGet(parameters('rules')[copyIndex('rules')], 'filters'), 'prefixMatch'), 'minCreationTime', tryGet(tryGet(parameters('rules')[copyIndex('rules')], 'filters'), 'minCreationTime')), null())]"
+ }
+ }
+ ],
+ "destinationAccount": "[parameters('destinationAccountResourceId')]",
+ "metrics": {
+ "enabled": "[coalesce(parameters('enableMetrics'), false())]"
+ },
+ "sourceAccount": "[parameters('sourceStorageAccountResourceId')]"
+ }
+ }
+ },
+ "outputs": {
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "Resource group name of the provisioned resources."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "objectReplicationPolicyId": {
+ "type": "string",
+ "metadata": {
+ "description": "Resource ID of the created Object Replication Policy."
+ },
+ "value": "[resourceId('Microsoft.Storage/storageAccounts/objectReplicationPolicies', parameters('storageAccountName'), parameters('name'))]"
+ },
+ "policyId": {
+ "type": "string",
+ "metadata": {
+ "description": "Policy ID of the created Object Replication Policy."
+ },
+ "value": "[reference('objectReplicationPolicy').policyId]"
+ },
+ "rules": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Storage/storageAccounts/objectReplicationPolicies@2025-01-01#properties/properties/properties/rules",
+ "output": true
+ },
+ "description": "Rules created Object Replication Policy."
+ },
+ "value": "[reference('objectReplicationPolicy').rules]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "destinationPolicy"
+ ]
+ }
+ },
+ "outputs": {
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "Resource group name of the provisioned resources."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "objectReplicationPolicyId": {
+ "type": "string",
+ "metadata": {
+ "description": "Resource ID of the created Object Replication Policy in the source account."
+ },
+ "value": "[reference('sourcePolicy').outputs.objectReplicationPolicyId.value]"
+ },
+ "policyId": {
+ "type": "string",
+ "metadata": {
+ "description": "Policy ID of the created Object Replication Policy in the source account."
+ },
+ "value": "[reference('sourcePolicy').outputs.policyId.value]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "storageAccount",
+ "storageAccount_blobServices"
+ ]
+ }
+ },
+ "outputs": {
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the deployed storage account."
+ },
+ "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]"
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the deployed storage account."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group of the deployed storage account."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "primaryBlobEndpoint": {
+ "type": "string",
+ "metadata": {
+ "description": "The primary blob endpoint reference if blob services are deployed."
+ },
+ "value": "[if(and(not(empty(parameters('blobServices'))), contains(parameters('blobServices'), 'containers')), reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('name')), '2019-04-01').primaryEndpoints.blob, '')]"
+ },
+ "systemAssignedMIPrincipalId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "The principal ID of the system assigned identity."
+ },
+ "value": "[tryGet(tryGet(reference('storageAccount', '2025-01-01', 'full'), 'identity'), 'principalId')]"
+ },
+ "location": {
+ "type": "string",
+ "metadata": {
+ "description": "The location the resource was deployed into."
+ },
+ "value": "[reference('storageAccount', '2025-01-01', 'full').location]"
+ },
+ "serviceEndpoints": {
+ "type": "object",
+ "metadata": {
+ "description": "All service endpoints of the deployed storage account, Note Standard_LRS and Standard_ZRS accounts only have a blob service endpoint."
+ },
+ "value": "[reference('storageAccount').primaryEndpoints]"
+ },
+ "privateEndpoints": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateEndpointOutputType"
+ },
+ "metadata": {
+ "description": "The private endpoints of the Storage Account."
+ },
+ "copy": {
+ "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]",
+ "input": {
+ "name": "[reference(format('storageAccount_privateEndpoints[{0}]', copyIndex())).outputs.name.value]",
+ "resourceId": "[reference(format('storageAccount_privateEndpoints[{0}]', copyIndex())).outputs.resourceId.value]",
+ "groupId": "[tryGet(tryGet(reference(format('storageAccount_privateEndpoints[{0}]', copyIndex())).outputs, 'groupId'), 'value')]",
+ "customDnsConfigs": "[reference(format('storageAccount_privateEndpoints[{0}]', copyIndex())).outputs.customDnsConfigs.value]",
+ "networkInterfaceResourceIds": "[reference(format('storageAccount_privateEndpoints[{0}]', copyIndex())).outputs.networkInterfaceResourceIds.value]"
+ }
+ }
+ },
+ "exportedSecrets": {
+ "$ref": "#/definitions/secretsOutputType",
+ "metadata": {
+ "description": "A hashtable of references to the secrets exported to the provided Key Vault. The key of each reference is each secret's name."
+ },
+ "value": "[if(not(equals(parameters('secretsExportConfiguration'), null())), toObject(reference('secretsExport').outputs.secretsSet.value, lambda('secret', last(split(lambdaVariables('secret').secretResourceId, '/'))), lambda('secret', lambdaVariables('secret'))), createObject())]"
+ },
+ "primaryAccessKey": {
+ "type": "securestring",
+ "metadata": {
+ "description": "The primary access key of the storage account."
+ },
+ "value": "[listKeys('storageAccount', '2025-01-01').keys[0].value]"
+ },
+ "secondaryAccessKey": {
+ "type": "securestring",
+ "metadata": {
+ "description": "The secondary access key of the storage account."
+ },
+ "value": "[listKeys('storageAccount', '2025-01-01').keys[1].value]"
+ },
+ "primaryConnectionString": {
+ "type": "securestring",
+ "metadata": {
+ "description": "The primary connection string of the storage account."
+ },
+ "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', parameters('name'), listKeys('storageAccount', '2025-01-01').keys[0].value, environment().suffixes.storage)]"
+ },
+ "secondaryConnectionString": {
+ "type": "securestring",
+ "metadata": {
+ "description": "The secondary connection string of the storage account."
+ },
+ "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', parameters('name'), listKeys('storageAccount', '2025-01-01').keys[1].value, environment().suffixes.storage)]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageBlob)]",
+ "logAnalyticsWorkspace",
+ "userAssignedIdentity",
+ "virtualNetwork"
+ ]
+ },
+ "cosmosDB": {
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[take(format('avm.res.document-db.database-account.{0}', variables('cosmosDBResourceName')), 64)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[format('cosmos-{0}', variables('solutionSuffix'))]"
+ },
+ "location": {
+ "value": "[parameters('secondaryLocation')]"
+ },
+ "tags": {
+ "value": "[parameters('tags')]"
+ },
+ "enableTelemetry": {
+ "value": "[parameters('enableTelemetry')]"
+ },
+ "sqlDatabases": {
+ "value": [
+ {
+ "name": "[variables('cosmosDBDatabaseName')]",
+ "containers": [
+ {
+ "name": "[variables('cosmosDBConversationsContainer')]",
+ "paths": [
+ "/userId"
+ ]
+ },
+ {
+ "name": "[variables('cosmosDBProductsContainer')]",
+ "paths": [
+ "/category"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "sqlRoleDefinitions": {
+ "value": [
+ {
+ "roleName": "contentgen-data-contributor",
+ "dataActions": [
+ "Microsoft.DocumentDB/databaseAccounts/readMetadata",
+ "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*",
+ "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*"
+ ]
+ }
+ ]
+ },
+ "sqlRoleAssignments": {
+ "value": [
+ {
+ "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]",
+ "roleDefinitionId": "00000000-0000-0000-0000-000000000002"
+ },
+ {
+ "principalId": "[deployer().objectId]",
+ "roleDefinitionId": "00000000-0000-0000-0000-000000000002"
+ }
+ ]
+ },
+ "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), if(parameters('enableMonitoring'), reference('logAnalyticsWorkspace').outputs.resourceId.value, ''))))), createObject('value', null()))]",
+ "networkRestrictions": {
+ "value": {
+ "networkAclBypass": "AzureServices",
+ "publicNetworkAccess": "[if(parameters('enablePrivateNetworking'), 'Disabled', 'Enabled')]"
+ }
+ },
+ "zoneRedundant": {
+ "value": "[parameters('enableRedundancy')]"
+ },
+ "capabilitiesToAdd": "[if(parameters('enableRedundancy'), createObject('value', null()), createObject('value', createArray('EnableServerless')))]",
+ "enableAutomaticFailover": {
+ "value": "[parameters('enableRedundancy')]"
+ },
+ "failoverLocations": "[if(parameters('enableRedundancy'), createObject('value', createArray(createObject('failoverPriority', 0, 'isZoneRedundant', true(), 'locationName', parameters('secondaryLocation')), createObject('failoverPriority', 1, 'isZoneRedundant', true(), 'locationName', variables('cosmosDbHaLocation')))), createObject('value', createArray(createObject('locationName', parameters('secondaryLocation'), 'failoverPriority', 0, 'isZoneRedundant', false()))))]",
+ "privateEndpoints": "[if(parameters('enablePrivateNetworking'), createObject('value', createArray(createObject('service', 'Sql', 'subnetResourceId', reference('virtualNetwork').outputs.pepsSubnetResourceId.value, 'privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', createArray(createObject('privateDnsZoneResourceId', reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cosmosDB)).outputs.resourceId.value)))))), createObject('value', null()))]"
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.33.27573",
+ "templateHash": "11889744396543212232"
+ },
+ "name": "Azure Cosmos DB account",
+ "description": "This module deploys an Azure Cosmos DB account. The API used for the account is determined by the child resources that are deployed."
+ },
+ "definitions": {
+ "privateEndpointOutputType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the private endpoint."
+ }
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the private endpoint."
+ }
+ },
+ "groupId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "The group ID for the private endpoint group."
+ }
+ },
+ "customDnsConfigs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "fqdn": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "fully-qualified domain name (FQDN) that resolves to private endpoint IP address."
+ }
+ },
+ "ipAddresses": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "A list of private IP addresses for the private endpoint."
+ }
+ }
+ }
+ },
+ "metadata": {
+ "description": "The custom DNS configurations of the private endpoint."
+ }
+ },
+ "networkInterfaceResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "The IDs of the network interfaces associated with the private endpoint."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for the private endpoint output."
+ }
+ },
+ "failoverLocationType": {
+ "type": "object",
+ "properties": {
+ "failoverPriority": {
+ "type": "int",
+ "metadata": {
+ "description": "Required. The failover priority of the region. A failover priority of 0 indicates a write region. The maximum value for a failover priority = (total number of regions - 1). Failover priority values must be unique for each of the regions in which the database account exists."
+ }
+ },
+ "isZoneRedundant": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Flag to indicate whether or not this region is an AvailabilityZone region. Defaults to true."
+ }
+ },
+ "locationName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the region."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for the failover location."
+ }
+ },
+ "sqlRoleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The unique name of the role assignment."
+ }
+ },
+ "roleDefinitionId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The unique identifier of the Azure Cosmos DB for NoSQL native role-based access control definition."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The unique identifier for the associated Microsoft Entra ID principal to which access is being granted through this role-based access control assignment. The tenant ID for the principal is inferred using the tenant associated with the subscription."
+ }
+ },
+ "scope": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The data plane resource id for which access is being granted through this Role Assignment. Defaults to the root of the database account, but can also be scoped to e.g., the container and database level."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for an Azure Cosmos DB for NoSQL native role-based access control assignment."
+ }
+ },
+ "sqlRoleDefinitionType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The unique identifier of the role-based access control definition."
+ }
+ },
+ "roleName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. A user-friendly name for the role-based access control definition. This must be unique within the database account."
+ }
+ },
+ "dataActions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minLength": 1,
+ "metadata": {
+ "description": "Required. An array of data actions that are allowed."
+ }
+ },
+ "assignableScopes": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A set of fully-qualified scopes at or below which role-based access control assignments may be created using this definition. This setting allows application of this definition on the entire account or any underlying resource. This setting must have at least one element. Scopes higher than the account level are not enforceable as assignable scopes. Resources referenced in assignable scopes do not need to exist at creation. Defaults to the current account scope."
+ }
+ },
+ "assignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/nestedSqlRoleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. An array of role-based access control assignments to be created for the definition."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for an Azure Cosmos DB for NoSQL or Table native role-based access control definition."
+ }
+ },
+ "networkRestrictionType": {
+ "type": "object",
+ "properties": {
+ "ipRules": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A single IPv4 address or a single IPv4 address range in Classless Inter-Domain Routing (CIDR) format. Provided IPs must be well-formatted and cannot be contained in one of the following ranges: `10.0.0.0/8`, `100.64.0.0/10`, `172.16.0.0/12`, `192.168.0.0/16`, since these are not enforceable by the IP address filter. Example of valid inputs: `23.40.210.245` or `23.40.210.0/8`."
+ }
+ },
+ "networkAclBypass": {
+ "type": "string",
+ "allowedValues": [
+ "AzureServices",
+ "None"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specifies the network ACL bypass for Azure services. Default to \"None\"."
+ }
+ },
+ "publicNetworkAccess": {
+ "type": "string",
+ "allowedValues": [
+ "Disabled",
+ "Enabled"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Whether requests from the public network are allowed. Default to \"Disabled\"."
+ }
+ },
+ "virtualNetworkRules": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "subnetResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Resource ID of a subnet."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. List of virtual network access control list (ACL) rules configured for the account."
+ }
+ },
+ "networkAclBypassResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. An array that contains the Resource Ids for Network Acl Bypass for the Cosmos DB account."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for the network restriction."
+ }
+ },
+ "gremlinDatabaseType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the Gremlin database."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases@2024-11-15#properties/tags"
+ },
+ "description": "Optional. Tags of the Gremlin database resource."
+ },
+ "nullable": true
+ },
+ "graphs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/graphType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of graphs to deploy in the Gremlin database."
+ }
+ },
+ "maxThroughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Represents maximum throughput, the resource can scale up to. Cannot be set together with `throughput`. If `throughput` is set to something else than -1, this autoscale setting is ignored. Setting throughput at the database level is only recommended for development/test or when workload across all graphs in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the graph level and not at the database level."
+ }
+ },
+ "throughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Request Units per second (for example 10000). Cannot be set together with `maxThroughput`. Setting throughput at the database level is only recommended for development/test or when workload across all graphs in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the graph level and not at the database level."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for a gremlin databae."
+ }
+ },
+ "mongoDbType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the mongodb database."
+ }
+ },
+ "throughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Request Units per second. Setting throughput at the database level is only recommended for development/test or when workload across all collections in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the collection level and not at the database level."
+ }
+ },
+ "collections": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/collectionType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Collections in the mongodb database."
+ }
+ },
+ "autoscaleSettings": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases@2025-04-15#properties/properties/properties/options/properties/autoscaleSettings"
+ },
+ "description": "Optional. Specifies the Autoscale settings. Note: Either throughput or autoscaleSettings is required, but not both."
+ },
+ "nullable": true
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases@2025-04-15#properties/tags"
+ },
+ "description": "Optional. Tags of the resource."
+ },
+ "nullable": true
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for a mongo databae."
+ }
+ },
+ "sqlDatabaseType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the SQL database ."
+ }
+ },
+ "containers": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/containerType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of containers to deploy in the SQL database."
+ }
+ },
+ "throughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Request units per second. Will be ignored if autoscaleSettingsMaxThroughput is used. Setting throughput at the database level is only recommended for development/test or when workload across all containers in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level."
+ }
+ },
+ "autoscaleSettingsMaxThroughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specifies the Autoscale settings and represents maximum throughput, the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If value is set to null, then autoscale will be disabled. Setting throughput at the database level is only recommended for development/test or when workload across all containers in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2025-04-15#properties/tags"
+ },
+ "description": "Optional. Tags of the SQL database resource."
+ },
+ "nullable": true
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for a sql database."
+ }
+ },
+ "tableType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the table."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/tables@2025-04-15#properties/tags"
+ },
+ "description": "Optional. Tags for the table."
+ },
+ "nullable": true
+ },
+ "maxThroughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Represents maximum throughput, the resource can scale up to. Cannot be set together with `throughput`. If `throughput` is set to something else than -1, this autoscale setting is ignored."
+ }
+ },
+ "throughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Request Units per second (for example 10000). Cannot be set together with `maxThroughput`."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for a table."
+ }
+ },
+ "cassandraStandaloneRoleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The unique name of the role assignment."
+ }
+ },
+ "roleDefinitionId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The unique identifier of the Azure Cosmos DB for Apache Cassandra native role-based access control definition."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The unique identifier for the associated Microsoft Entra ID principal to which access is being granted through this role-based access control assignment. The tenant ID for the principal is inferred using the tenant associated with the subscription."
+ }
+ },
+ "scope": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The data plane resource path for which access is being granted through this role-based access control assignment. Defaults to the current account."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for an Azure Cosmos DB for Apache Cassandra native role-based access control assignment."
+ }
+ },
+ "cassandraRoleDefinitionType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The unique identifier of the role-based access control definition."
+ }
+ },
+ "roleName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. A user-friendly name for the role-based access control definition. Must be unique for the database account."
+ }
+ },
+ "dataActions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. An array of data actions that are allowed. Note: Valid data action strings are currently undocumented (API version 2025-05-01-preview). Expected to follow format similar to SQL RBAC once documented by Microsoft."
+ }
+ },
+ "notDataActions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. An array of data actions that are denied. Note: Unlike SQL RBAC, Cassandra supports deny rules for granular access control. Valid data action strings are currently undocumented (API version 2025-05-01-preview)."
+ }
+ },
+ "assignableScopes": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A set of fully qualified Scopes at or below which Role Assignments may be created using this Role Definition."
+ }
+ },
+ "assignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/cassandraRoleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. An array of role-based access control assignments to be created for the definition."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for an Azure Cosmos DB for Apache Cassandra native role-based access control definition."
+ }
+ },
+ "cassandraKeyspaceType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the Cassandra keyspace."
+ }
+ },
+ "tables": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/cassandraTableType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of Cassandra tables to deploy in the keyspace."
+ }
+ },
+ "views": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/cassandraViewType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of Cassandra views (materialized views) to deploy in the keyspace."
+ }
+ },
+ "autoscaleSettingsMaxThroughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Represents maximum throughput, the resource can scale up to. Cannot be set together with `throughput`. If `throughput` is set to something else than -1, this autoscale setting is ignored. Setting throughput at the keyspace level is only recommended for development/test or when workload across all tables in the shared throughput keyspace is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the table level and not at the keyspace level."
+ }
+ },
+ "throughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Request Units per second (for example 10000). Cannot be set together with `autoscaleSettingsMaxThroughput`. Setting throughput at the keyspace level is only recommended for development/test or when workload across all tables in the shared throughput keyspace is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the table level and not at the keyspace level."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces@2024-11-15#properties/tags"
+ },
+ "description": "Optional. Tags of the Cassandra keyspace resource."
+ },
+ "nullable": true
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for an Azure Cosmos DB Cassandra keyspace."
+ }
+ },
+ "defaultIdentityType": {
+ "type": "object",
+ "discriminator": {
+ "propertyName": "name",
+ "mapping": {
+ "FirstPartyIdentity": {
+ "$ref": "#/definitions/defaultIdentityFirstPartyType"
+ },
+ "SystemAssignedIdentity": {
+ "$ref": "#/definitions/defaultIdentitySystemAssignedType"
+ },
+ "UserAssignedIdentity": {
+ "$ref": "#/definitions/defaultIdentityUserAssignedType"
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for the default identity."
+ }
+ },
+ "defaultIdentityFirstPartyType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "allowedValues": [
+ "FirstPartyIdentity"
+ ],
+ "metadata": {
+ "description": "Required. The type of default identity to use."
+ }
+ }
+ }
+ },
+ "defaultIdentitySystemAssignedType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "allowedValues": [
+ "SystemAssignedIdentity"
+ ],
+ "metadata": {
+ "description": "Required. The type of default identity to use."
+ }
+ }
+ }
+ },
+ "defaultIdentityUserAssignedType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "allowedValues": [
+ "UserAssignedIdentity"
+ ],
+ "metadata": {
+ "description": "Required. The type of default identity to use."
+ }
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource ID of the user assigned identity to use as the default identity."
+ }
+ }
+ }
+ },
+ "_1.privateEndpointCustomDnsConfigType": {
+ "type": "object",
+ "properties": {
+ "fqdn": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. FQDN that resolves to private endpoint IP address."
+ }
+ },
+ "ipAddresses": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of private IP addresses of the private endpoint."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "_1.privateEndpointIpConfigurationType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the resource that is unique within a resource group."
+ }
+ },
+ "properties": {
+ "type": "object",
+ "properties": {
+ "groupId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to."
+ }
+ },
+ "memberName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to."
+ }
+ },
+ "privateIPAddress": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. A private IP address obtained from the private endpoint's subnet."
+ }
+ }
+ },
+ "metadata": {
+ "description": "Required. Properties of private endpoint IP configurations."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "_1.privateEndpointPrivateDnsZoneGroupType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the Private DNS Zone Group."
+ }
+ },
+ "privateDnsZoneGroupConfigs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private DNS Zone Group config."
+ }
+ },
+ "privateDnsZoneResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource id of the private DNS zone."
+ }
+ }
+ }
+ },
+ "metadata": {
+ "description": "Required. The private DNS Zone Groups to associate the Private Endpoint. A DNS Zone Group can support up to 5 DNS zones."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "cassandraRoleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The unique identifier of the role assignment."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The unique identifier for the associated AAD principal."
+ }
+ },
+ "scope": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The data plane resource path for which access is being granted. Defaults to the current account."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "cassandra-role-definition/main.bicep"
+ }
+ }
+ },
+ "cassandraTableType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the table."
+ }
+ },
+ "schema": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables@2024-11-15#properties/properties/properties/resource/properties/schema"
+ },
+ "description": "Required. Schema definition for the table."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables@2024-11-15#properties/tags"
+ },
+ "description": "Optional. Tags for the table."
+ },
+ "nullable": true
+ },
+ "defaultTtl": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Default TTL (Time To Live) in seconds for data in the table."
+ }
+ },
+ "analyticalStorageTtl": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Analytical TTL for the table."
+ }
+ },
+ "throughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Request units per second. Cannot be used with autoscaleSettingsMaxThroughput."
+ }
+ },
+ "autoscaleSettingsMaxThroughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Maximum autoscale throughput for the table. Cannot be used with throughput."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The type of a Cassandra table.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "cassandra-keyspace/main.bicep",
+ "originalIdentifier": "tableType"
+ }
+ }
+ },
+ "cassandraViewType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the view."
+ }
+ },
+ "viewDefinition": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. View definition (CQL statement)."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/views@2025-05-01-preview#properties/tags"
+ },
+ "description": "Optional. Tags for the view."
+ },
+ "nullable": true
+ },
+ "throughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Request units per second. Cannot be used with autoscaleSettingsMaxThroughput."
+ }
+ },
+ "autoscaleSettingsMaxThroughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Maximum autoscale throughput for the view. Cannot be used with throughput."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The type of a Cassandra view (materialized view).",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "cassandra-keyspace/main.bicep",
+ "originalIdentifier": "viewType"
+ }
+ }
+ },
+ "collectionType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the collection."
+ }
+ },
+ "throughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Request Units per second. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the collection level and not at the database level."
+ }
+ },
+ "indexes": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections@2025-04-15#properties/properties/properties/resource/properties/indexes"
+ },
+ "description": "Required. Indexes for the collection."
+ }
+ },
+ "shardKey": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections@2025-04-15#properties/properties/properties/resource/properties/shardKey"
+ },
+ "description": "Required. ShardKey for the collection."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The type of a collection.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "mongodb-database/main.bicep"
+ }
+ }
+ },
+ "containerType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the container."
+ }
+ },
+ "analyticalStorageTtl": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Default to 0. Indicates how long data should be retained in the analytical store, for a container. Analytical store is enabled when ATTL is set with a value other than 0. If the value is set to -1, the analytical store retains all historical data, irrespective of the retention of the data in the transactional store."
+ }
+ },
+ "conflictResolutionPolicy": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/properties/properties/resource/properties/conflictResolutionPolicy"
+ },
+ "description": "Optional. The conflict resolution policy for the container. Conflicts and conflict resolution policies are applicable if the Azure Cosmos DB account is configured with multiple write regions."
+ },
+ "nullable": true
+ },
+ "defaultTtl": {
+ "type": "int",
+ "nullable": true,
+ "minValue": -1,
+ "maxValue": 2147483647,
+ "metadata": {
+ "description": "Optional. Default to -1. Default time to live (in seconds). With Time to Live or TTL, Azure Cosmos DB provides the ability to delete items automatically from a container after a certain time period. If the value is set to \"-1\", it is equal to infinity, and items don't expire by default."
+ }
+ },
+ "throughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Default to 400. Request Units per second. Will be ignored if autoscaleSettingsMaxThroughput is used. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level."
+ }
+ },
+ "autoscaleSettingsMaxThroughput": {
+ "type": "int",
+ "nullable": true,
+ "maxValue": 1000000,
+ "metadata": {
+ "description": "Optional. Specifies the Autoscale settings and represents maximum throughput, the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If value is set to null, then autoscale will be disabled. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/tags"
+ },
+ "description": "Optional. Tags of the SQL Database resource."
+ },
+ "nullable": true
+ },
+ "paths": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minLength": 1,
+ "maxLength": 3,
+ "metadata": {
+ "description": "Required. List of paths using which data within the container can be partitioned. For kind=MultiHash it can be up to 3. For anything else it needs to be exactly 1."
+ }
+ },
+ "indexingPolicy": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/properties/properties/resource/properties/indexingPolicy"
+ },
+ "description": "Optional. Indexing policy of the container."
+ },
+ "nullable": true
+ },
+ "uniqueKeyPolicyKeys": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/properties/properties/resource/properties/uniqueKeyPolicy/properties/uniqueKeys"
+ },
+ "description": "Optional. The unique key policy configuration containing a list of unique keys that enforces uniqueness constraint on documents in the collection in the Azure Cosmos DB service."
+ },
+ "nullable": true
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "Hash",
+ "MultiHash"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Default to Hash. Indicates the kind of algorithm used for partitioning."
+ }
+ },
+ "version": {
+ "type": "int",
+ "allowedValues": [
+ 1,
+ 2
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Default to 1 for Hash and 2 for MultiHash - 1 is not allowed for MultiHash. Version of the partition key definition."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The type of a container.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "sql-database/main.bicep"
+ }
+ }
+ },
+ "diagnosticSettingFullType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the diagnostic setting."
+ }
+ },
+ "logCategoriesAndGroups": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here."
+ }
+ },
+ "categoryGroup": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection."
+ }
+ },
+ "metricCategories": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection."
+ }
+ },
+ "logAnalyticsDestinationType": {
+ "type": "string",
+ "allowedValues": [
+ "AzureDiagnostics",
+ "Dedicated"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type."
+ }
+ },
+ "workspaceResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "storageAccountResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "eventHubAuthorizationRuleResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to."
+ }
+ },
+ "eventHubName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "marketplacePartnerResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "graphType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the graph."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs@2025-04-15#properties/tags"
+ },
+ "description": "Optional. Tags of the Gremlin graph resource."
+ },
+ "nullable": true
+ },
+ "indexingPolicy": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs@2025-04-15#properties/properties/properties/resource/properties/indexingPolicy"
+ },
+ "description": "Optional. Indexing policy of the graph."
+ },
+ "nullable": true
+ },
+ "partitionKeyPaths": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs@2025-04-15#properties/properties/properties/resource/properties/partitionKey/properties/paths"
+ },
+ "description": "Optional. List of paths using which data within the container can be partitioned."
+ },
+ "nullable": true
+ }
+ },
+ "metadata": {
+ "description": "The type of a graph.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "gremlin-database/main.bicep"
+ }
+ }
+ },
+ "lockType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the name of lock."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "CanNotDelete",
+ "None",
+ "ReadOnly"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ },
+ "notes": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the notes of the lock."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a lock.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "managedIdentityAllType": {
+ "type": "object",
+ "properties": {
+ "systemAssigned": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enables system assigned managed identity on the resource."
+ }
+ },
+ "userAssignedResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID(s) to assign to the resource. Required if a user assigned identity is used for encryption."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "nestedSqlRoleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name unique identifier of the SQL Role Assignment."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The unique identifier for the associated AAD principal in the AAD graph to which access is being granted through this Role Assignment. Tenant ID for the principal is inferred using the tenant associated with the subscription."
+ }
+ },
+ "scope": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The data plane resource id for which access is being granted through this Role Assignment. Defaults to the root of the database account, but can also be scoped to e.g., the container and database level."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The type for the SQL Role Assignments.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "sql-role-definition/main.bicep",
+ "originalIdentifier": "sqlRoleAssignmentType"
+ }
+ }
+ },
+ "privateEndpointMultiServiceType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private endpoint."
+ }
+ },
+ "location": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The location to deploy the private endpoint to."
+ }
+ },
+ "privateLinkServiceConnectionName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private link connection to create."
+ }
+ },
+ "service": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The subresource to deploy the private endpoint for. For example \"blob\", \"table\", \"queue\" or \"file\" for a Storage Account's Private Endpoints."
+ }
+ },
+ "subnetResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Resource ID of the subnet where the endpoint needs to be created."
+ }
+ },
+ "resourceGroupResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID of the Resource Group the Private Endpoint will be created in. If not specified, the Resource Group of the provided Virtual Network Subnet is used."
+ }
+ },
+ "privateDnsZoneGroup": {
+ "$ref": "#/definitions/_1.privateEndpointPrivateDnsZoneGroupType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The private DNS zone group to configure for the private endpoint."
+ }
+ },
+ "isManualConnection": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. If Manual Private Link Connection is required."
+ }
+ },
+ "manualConnectionRequestMessage": {
+ "type": "string",
+ "nullable": true,
+ "maxLength": 140,
+ "metadata": {
+ "description": "Optional. A message passed to the owner of the remote resource with the manual connection request."
+ }
+ },
+ "customDnsConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/_1.privateEndpointCustomDnsConfigType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Custom DNS configurations."
+ }
+ },
+ "ipConfigurations": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/_1.privateEndpointIpConfigurationType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints."
+ }
+ },
+ "applicationSecurityGroupResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Application security groups in which the private endpoint IP configuration is included."
+ }
+ },
+ "customNetworkInterfaceName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The custom name of the network interface attached to the private endpoint."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "nullable": true,
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-07-01#properties/tags"
+ },
+ "description": "Optional. Tags to be applied on all resources/resource groups in this deployment."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a private endpoint. To be used if the private endpoint's default service / groupId can NOT be assumed (i.e., for services that have more than one subresource, like Storage Account with Blob (blob, table, queue, file, ...).",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the account."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Optional. Defaults to the current resource group scope location. Location for all resources."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts@2024-11-15#properties/tags"
+ },
+ "description": "Optional. Tags for the resource."
+ },
+ "nullable": true
+ },
+ "managedIdentities": {
+ "$ref": "#/definitions/managedIdentityAllType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The managed identity definition for this resource."
+ }
+ },
+ "databaseAccountOfferType": {
+ "type": "string",
+ "defaultValue": "Standard",
+ "allowedValues": [
+ "Standard"
+ ],
+ "metadata": {
+ "description": "Optional. The offer type for the account. Defaults to \"Standard\"."
+ }
+ },
+ "failoverLocations": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/failoverLocationType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The set of locations enabled for the account. Defaults to the location where the account is deployed."
+ }
+ },
+ "zoneRedundant": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Indicates whether the single-region account is zone redundant. Defaults to true. This property is ignored for multi-region accounts."
+ }
+ },
+ "defaultConsistencyLevel": {
+ "type": "string",
+ "defaultValue": "Session",
+ "allowedValues": [
+ "Eventual",
+ "ConsistentPrefix",
+ "Session",
+ "BoundedStaleness",
+ "Strong"
+ ],
+ "metadata": {
+ "description": "Optional. The default consistency level of the account. Defaults to \"Session\"."
+ }
+ },
+ "disableLocalAuthentication": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Opt-out of local authentication and ensure that only Microsoft Entra can be used exclusively for authentication. Defaults to true."
+ }
+ },
+ "enableAnalyticalStorage": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Flag to indicate whether to enable storage analytics. Defaults to false."
+ }
+ },
+ "enableAutomaticFailover": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable automatic failover for regions. Defaults to true."
+ }
+ },
+ "enableFreeTier": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Flag to indicate whether \"Free Tier\" is enabled. Defaults to false."
+ }
+ },
+ "enableMultipleWriteLocations": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Enables the account to write in multiple locations. Periodic backup must be used if enabled. Defaults to false."
+ }
+ },
+ "disableKeyBasedMetadataWriteAccess": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Disable write operations on metadata resources (databases, containers, throughput) via account keys. Defaults to true."
+ }
+ },
+ "maxStalenessPrefix": {
+ "type": "int",
+ "defaultValue": 100000,
+ "minValue": 1,
+ "maxValue": 2147483647,
+ "metadata": {
+ "description": "Optional. The maximum stale requests. Required for \"BoundedStaleness\" consistency level. Valid ranges, Single Region: 10 to 1000000. Multi Region: 100000 to 1000000. Defaults to 100000."
+ }
+ },
+ "maxIntervalInSeconds": {
+ "type": "int",
+ "defaultValue": 300,
+ "minValue": 5,
+ "maxValue": 86400,
+ "metadata": {
+ "description": "Optional. The maximum lag time in minutes. Required for \"BoundedStaleness\" consistency level. Valid ranges, Single Region: 5 to 84600. Multi Region: 300 to 86400. Defaults to 300."
+ }
+ },
+ "serverVersion": {
+ "type": "string",
+ "defaultValue": "4.2",
+ "allowedValues": [
+ "3.2",
+ "3.6",
+ "4.0",
+ "4.2",
+ "5.0",
+ "6.0",
+ "7.0"
+ ],
+ "metadata": {
+ "description": "Optional. Specifies the MongoDB server version to use if using Azure Cosmos DB for MongoDB RU. Defaults to \"4.2\"."
+ }
+ },
+ "sqlDatabases": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/sqlDatabaseType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Configuration for databases when using Azure Cosmos DB for NoSQL."
+ }
+ },
+ "mongodbDatabases": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/mongoDbType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Configuration for databases when using Azure Cosmos DB for MongoDB RU."
+ }
+ },
+ "gremlinDatabases": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/gremlinDatabaseType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Configuration for databases when using Azure Cosmos DB for Apache Gremlin."
+ }
+ },
+ "tables": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/tableType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Configuration for databases when using Azure Cosmos DB for Table."
+ }
+ },
+ "cassandraKeyspaces": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/cassandraKeyspaceType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Configuration for keyspaces when using Azure Cosmos DB for Apache Cassandra."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ },
+ "totalThroughputLimit": {
+ "type": "int",
+ "defaultValue": -1,
+ "metadata": {
+ "description": "Optional. The total throughput limit imposed on this account in request units per second (RU/s). Default to unlimited throughput."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The lock settings of the service."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. An array of control plane Azure role-based access control assignments."
+ }
+ },
+ "sqlRoleDefinitions": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/sqlRoleDefinitionType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Configurations for Azure Cosmos DB for NoSQL native role-based access control definitions. Allows the creations of custom role definitions."
+ }
+ },
+ "sqlRoleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/sqlRoleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Configurations for Azure Cosmos DB for NoSQL native role-based access control assignments."
+ }
+ },
+ "cassandraRoleDefinitions": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/cassandraRoleDefinitionType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Configurations for Azure Cosmos DB for Apache Cassandra native role-based access control definitions. Allows the creations of custom role definitions."
+ }
+ },
+ "cassandraRoleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/cassandraStandaloneRoleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Azure Cosmos DB for Apache Cassandra native data plane role-based access control assignments. Each assignment references a role definition unique identifier and a principal identifier."
+ }
+ },
+ "diagnosticSettings": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/diagnosticSettingFullType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The diagnostic settings for the service."
+ }
+ },
+ "capabilitiesToAdd": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "allowedValues": [
+ "EnableCassandra",
+ "EnableTable",
+ "EnableGremlin",
+ "EnableMongo",
+ "DisableRateLimitingResponses",
+ "EnableServerless",
+ "EnableNoSQLVectorSearch",
+ "EnableNoSQLFullTextSearch",
+ "EnableMaterializedViews",
+ "DeleteAllItemsByPartitionKey"
+ ],
+ "metadata": {
+ "description": "Optional. A list of Azure Cosmos DB specific capabilities for the account."
+ }
+ },
+ "backupPolicyType": {
+ "type": "string",
+ "defaultValue": "Continuous",
+ "allowedValues": [
+ "Periodic",
+ "Continuous"
+ ],
+ "metadata": {
+ "description": "Optional. Configures the backup mode. Periodic backup must be used if multiple write locations are used. Defaults to \"Continuous\"."
+ }
+ },
+ "backupPolicyContinuousTier": {
+ "type": "string",
+ "defaultValue": "Continuous30Days",
+ "allowedValues": [
+ "Continuous30Days",
+ "Continuous7Days"
+ ],
+ "metadata": {
+ "description": "Optional. Configuration values to specify the retention period for continuous mode backup. Default to \"Continuous30Days\"."
+ }
+ },
+ "backupIntervalInMinutes": {
+ "type": "int",
+ "defaultValue": 240,
+ "minValue": 60,
+ "maxValue": 1440,
+ "metadata": {
+ "description": "Optional. An integer representing the interval in minutes between two backups. This setting only applies to the periodic backup type. Defaults to 240."
+ }
+ },
+ "backupRetentionIntervalInHours": {
+ "type": "int",
+ "defaultValue": 8,
+ "minValue": 2,
+ "maxValue": 720,
+ "metadata": {
+ "description": "Optional. An integer representing the time (in hours) that each backup is retained. This setting only applies to the periodic backup type. Defaults to 8."
+ }
+ },
+ "backupStorageRedundancy": {
+ "type": "string",
+ "defaultValue": "Local",
+ "allowedValues": [
+ "Geo",
+ "Local",
+ "Zone"
+ ],
+ "metadata": {
+ "description": "Optional. Setting that indicates the type of backup residency. This setting only applies to the periodic backup type. Defaults to \"Local\"."
+ }
+ },
+ "privateEndpoints": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateEndpointMultiServiceType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Configuration details for private endpoints. For security reasons, it is advised to use private endpoints whenever possible."
+ }
+ },
+ "networkRestrictions": {
+ "$ref": "#/definitions/networkRestrictionType",
+ "defaultValue": {
+ "ipRules": [],
+ "virtualNetworkRules": [],
+ "publicNetworkAccess": "Disabled"
+ },
+ "metadata": {
+ "description": "Optional. The network configuration of this module. Defaults to `{ ipRules: [], virtualNetworkRules: [], publicNetworkAccess: 'Disabled' }`."
+ }
+ },
+ "minimumTlsVersion": {
+ "type": "string",
+ "defaultValue": "Tls12",
+ "allowedValues": [
+ "Tls12"
+ ],
+ "metadata": {
+ "description": "Optional. Setting that indicates the minimum allowed TLS version. Azure Cosmos DB for MongoDB RU and Apache Cassandra only work with TLS 1.2 or later. Defaults to \"Tls12\" (TLS 1.2)."
+ }
+ },
+ "enableBurstCapacity": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Flag to indicate enabling/disabling of Burst Capacity feature on the account. Cannot be enabled for serverless accounts."
+ }
+ },
+ "enableCassandraConnector": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Enables the cassandra connector on the Cosmos DB C* account."
+ }
+ },
+ "enablePartitionMerge": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Flag to enable/disable the 'Partition Merge' feature on the account."
+ }
+ },
+ "enablePerRegionPerPartitionAutoscale": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Flag to enable/disable the 'PerRegionPerPartitionAutoscale' feature on the account."
+ }
+ },
+ "analyticalStorageConfiguration": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts@2025-04-15#properties/properties/properties/analyticalStorageConfiguration"
+ },
+ "description": "Optional. Analytical storage specific properties."
+ },
+ "nullable": true
+ },
+ "cors": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts@2025-04-15#properties/properties/properties/cors"
+ },
+ "description": "Optional. The CORS policy for the Cosmos DB database account."
+ },
+ "nullable": true
+ },
+ "defaultIdentity": {
+ "$ref": "#/definitions/defaultIdentityType",
+ "defaultValue": {
+ "name": "FirstPartyIdentity"
+ },
+ "metadata": {
+ "description": "Optional. The default identity for accessing key vault used in features like customer managed keys. Use `FirstPartyIdentity` to use the tenant-level CosmosDB enterprise application. The default identity needs to be explicitly set by the users."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInControlPlaneRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "enableReferencedModulesTelemetry": false,
+ "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]",
+ "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'SystemAssigned,UserAssigned', 'SystemAssigned'), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', null())), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]",
+ "builtInControlPlaneRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Cosmos DB Account Reader Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'fbdf93bf-df7d-467e-a4d2-9458aa1360c8')]",
+ "Cosmos DB Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '230815da-be43-4aae-9cb4-875f7bd000aa')]",
+ "CosmosBackupOperator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db7b14f2-5adf-42da-9f96-f2ee17bab5cb')]",
+ "CosmosRestoreOperator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5432c526-bc82-444a-b7ba-57c5b0b5b34f')]",
+ "DocumentDB Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5bd9cd88-fe45-4216-938b-f97437e15450')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-07-01",
+ "name": "[format('46d3xbcp.res.documentdb-databaseaccount.{0}.{1}', replace('0.18.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "databaseAccount": {
+ "type": "Microsoft.DocumentDB/databaseAccounts",
+ "apiVersion": "2025-04-15",
+ "name": "[parameters('name')]",
+ "location": "[parameters('location')]",
+ "tags": "[parameters('tags')]",
+ "identity": "[variables('identity')]",
+ "kind": "[if(not(empty(parameters('mongodbDatabases'))), 'MongoDB', 'GlobalDocumentDB')]",
+ "properties": "[shallowMerge(createArray(createObject('enableBurstCapacity', if(not(contains(coalesce(parameters('capabilitiesToAdd'), createArray()), 'EnableServerless')), parameters('enableBurstCapacity'), false()), 'analyticalStorageConfiguration', parameters('analyticalStorageConfiguration'), 'defaultIdentity', if(and(not(empty(parameters('defaultIdentity'))), not(equals(tryGet(parameters('defaultIdentity'), 'name'), 'UserAssignedIdentity'))), parameters('defaultIdentity').name, format('UserAssignedIdentity={0}', tryGet(parameters('defaultIdentity'), 'resourceId'))), 'enablePartitionMerge', parameters('enablePartitionMerge'), 'enablePerRegionPerPartitionAutoscale', parameters('enablePerRegionPerPartitionAutoscale'), 'databaseAccountOfferType', parameters('databaseAccountOfferType'), 'backupPolicy', shallowMerge(createArray(createObject('type', parameters('backupPolicyType')), if(equals(parameters('backupPolicyType'), 'Continuous'), createObject('continuousModeProperties', createObject('tier', parameters('backupPolicyContinuousTier'))), createObject()), if(equals(parameters('backupPolicyType'), 'Periodic'), createObject('periodicModeProperties', createObject('backupIntervalInMinutes', parameters('backupIntervalInMinutes'), 'backupRetentionIntervalInHours', parameters('backupRetentionIntervalInHours'), 'backupStorageRedundancy', parameters('backupStorageRedundancy'))), createObject()))), 'capabilities', map(coalesce(parameters('capabilitiesToAdd'), createArray()), lambda('capability', createObject('name', lambdaVariables('capability'))))), if(not(empty(parameters('cors'))), createObject('cors', parameters('cors')), createObject()), if(contains(coalesce(parameters('capabilitiesToAdd'), createArray()), 'EnableCassandra'), createObject('connectorOffer', if(parameters('enableCassandraConnector'), 'Small', null()), 'enableCassandraConnector', parameters('enableCassandraConnector')), createObject()), createObject('minimalTlsVersion', parameters('minimumTlsVersion'), 'capacity', createObject('totalThroughputLimit', parameters('totalThroughputLimit')), 'publicNetworkAccess', coalesce(tryGet(parameters('networkRestrictions'), 'publicNetworkAccess'), 'Disabled')), if(or(or(or(or(not(empty(parameters('sqlDatabases'))), not(empty(parameters('mongodbDatabases')))), not(empty(parameters('gremlinDatabases')))), not(empty(parameters('tables')))), not(empty(parameters('cassandraKeyspaces')))), createObject('consistencyPolicy', shallowMerge(createArray(createObject('defaultConsistencyLevel', parameters('defaultConsistencyLevel')), if(equals(parameters('defaultConsistencyLevel'), 'BoundedStaleness'), createObject('maxStalenessPrefix', parameters('maxStalenessPrefix'), 'maxIntervalInSeconds', parameters('maxIntervalInSeconds')), createObject()))), 'enableMultipleWriteLocations', parameters('enableMultipleWriteLocations'), 'locations', if(not(empty(parameters('failoverLocations'))), map(parameters('failoverLocations'), lambda('failoverLocation', createObject('failoverPriority', lambdaVariables('failoverLocation').failoverPriority, 'locationName', lambdaVariables('failoverLocation').locationName, 'isZoneRedundant', coalesce(tryGet(lambdaVariables('failoverLocation'), 'isZoneRedundant'), true())))), createArray(createObject('failoverPriority', 0, 'locationName', parameters('location'), 'isZoneRedundant', parameters('zoneRedundant')))), 'ipRules', map(coalesce(tryGet(parameters('networkRestrictions'), 'ipRules'), createArray()), lambda('ipRule', createObject('ipAddressOrRange', lambdaVariables('ipRule')))), 'virtualNetworkRules', map(coalesce(tryGet(parameters('networkRestrictions'), 'virtualNetworkRules'), createArray()), lambda('rule', createObject('id', lambdaVariables('rule').subnetResourceId, 'ignoreMissingVNetServiceEndpoint', false()))), 'networkAclBypass', coalesce(tryGet(parameters('networkRestrictions'), 'networkAclBypass'), 'None'), 'networkAclBypassResourceIds', tryGet(parameters('networkRestrictions'), 'networkAclBypassResourceIds'), 'isVirtualNetworkFilterEnabled', or(not(empty(tryGet(parameters('networkRestrictions'), 'ipRules'))), not(empty(tryGet(parameters('networkRestrictions'), 'virtualNetworkRules')))), 'enableFreeTier', parameters('enableFreeTier'), 'enableAutomaticFailover', parameters('enableAutomaticFailover'), 'enableAnalyticalStorage', parameters('enableAnalyticalStorage')), createObject()), if(or(or(not(empty(parameters('mongodbDatabases'))), not(empty(parameters('gremlinDatabases')))), not(empty(parameters('cassandraKeyspaces')))), createObject('disableLocalAuth', false(), 'disableKeyBasedMetadataWriteAccess', false()), createObject('disableLocalAuth', parameters('disableLocalAuthentication'), 'disableKeyBasedMetadataWriteAccess', parameters('disableKeyBasedMetadataWriteAccess'))), if(not(empty(parameters('mongodbDatabases'))), createObject('apiProperties', createObject('serverVersion', parameters('serverVersion'))), createObject())))]"
+ },
+ "databaseAccount_lock": {
+ "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]",
+ "type": "Microsoft.Authorization/locks",
+ "apiVersion": "2020-05-01",
+ "scope": "[format('Microsoft.DocumentDB/databaseAccounts/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]",
+ "properties": {
+ "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]",
+ "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]"
+ },
+ "dependsOn": [
+ "databaseAccount"
+ ]
+ },
+ "databaseAccount_diagnosticSettings": {
+ "copy": {
+ "name": "databaseAccount_diagnosticSettings",
+ "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]"
+ },
+ "type": "Microsoft.Insights/diagnosticSettings",
+ "apiVersion": "2021-05-01-preview",
+ "scope": "[format('Microsoft.DocumentDB/databaseAccounts/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]",
+ "properties": {
+ "copy": [
+ {
+ "name": "metrics",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]",
+ "input": {
+ "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]",
+ "timeGrain": null
+ }
+ },
+ {
+ "name": "logs",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]",
+ "input": {
+ "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]",
+ "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]"
+ }
+ }
+ ],
+ "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]",
+ "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]",
+ "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]",
+ "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]",
+ "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]",
+ "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]"
+ },
+ "dependsOn": [
+ "databaseAccount"
+ ]
+ },
+ "databaseAccount_roleAssignments": {
+ "copy": {
+ "name": "databaseAccount_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.DocumentDB/databaseAccounts/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "databaseAccount"
+ ]
+ },
+ "databaseAccount_sqlDatabases": {
+ "copy": {
+ "name": "databaseAccount_sqlDatabases",
+ "count": "[length(coalesce(parameters('sqlDatabases'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-sqldb-{1}', uniqueString(deployment().name, parameters('location')), coalesce(parameters('sqlDatabases'), createArray())[copyIndex()].name)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[coalesce(parameters('sqlDatabases'), createArray())[copyIndex()].name]"
+ },
+ "containers": {
+ "value": "[tryGet(coalesce(parameters('sqlDatabases'), createArray())[copyIndex()], 'containers')]"
+ },
+ "throughput": {
+ "value": "[tryGet(coalesce(parameters('sqlDatabases'), createArray())[copyIndex()], 'throughput')]"
+ },
+ "databaseAccountName": {
+ "value": "[parameters('name')]"
+ },
+ "autoscaleSettingsMaxThroughput": {
+ "value": "[tryGet(coalesce(parameters('sqlDatabases'), createArray())[copyIndex()], 'autoscaleSettingsMaxThroughput')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.33.27573",
+ "templateHash": "1549250134356326406"
+ },
+ "name": "DocumentDB Database Account SQL Databases",
+ "description": "This module deploys a SQL Database in a CosmosDB Account."
+ },
+ "definitions": {
+ "containerType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the container."
+ }
+ },
+ "analyticalStorageTtl": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Default to 0. Indicates how long data should be retained in the analytical store, for a container. Analytical store is enabled when ATTL is set with a value other than 0. If the value is set to -1, the analytical store retains all historical data, irrespective of the retention of the data in the transactional store."
+ }
+ },
+ "conflictResolutionPolicy": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/properties/properties/resource/properties/conflictResolutionPolicy"
+ },
+ "description": "Optional. The conflict resolution policy for the container. Conflicts and conflict resolution policies are applicable if the Azure Cosmos DB account is configured with multiple write regions."
+ },
+ "nullable": true
+ },
+ "defaultTtl": {
+ "type": "int",
+ "nullable": true,
+ "minValue": -1,
+ "maxValue": 2147483647,
+ "metadata": {
+ "description": "Optional. Default to -1. Default time to live (in seconds). With Time to Live or TTL, Azure Cosmos DB provides the ability to delete items automatically from a container after a certain time period. If the value is set to \"-1\", it is equal to infinity, and items don't expire by default."
+ }
+ },
+ "throughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Default to 400. Request Units per second. Will be ignored if autoscaleSettingsMaxThroughput is used. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level."
+ }
+ },
+ "autoscaleSettingsMaxThroughput": {
+ "type": "int",
+ "nullable": true,
+ "maxValue": 1000000,
+ "metadata": {
+ "description": "Optional. Specifies the Autoscale settings and represents maximum throughput, the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If value is set to null, then autoscale will be disabled. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/tags"
+ },
+ "description": "Optional. Tags of the SQL Database resource."
+ },
+ "nullable": true
+ },
+ "paths": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minLength": 1,
+ "maxLength": 3,
+ "metadata": {
+ "description": "Required. List of paths using which data within the container can be partitioned. For kind=MultiHash it can be up to 3. For anything else it needs to be exactly 1."
+ }
+ },
+ "indexingPolicy": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/properties/properties/resource/properties/indexingPolicy"
+ },
+ "description": "Optional. Indexing policy of the container."
+ },
+ "nullable": true
+ },
+ "uniqueKeyPolicyKeys": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/properties/properties/resource/properties/uniqueKeyPolicy/properties/uniqueKeys"
+ },
+ "description": "Optional. The unique key policy configuration containing a list of unique keys that enforces uniqueness constraint on documents in the collection in the Azure Cosmos DB service."
+ },
+ "nullable": true
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "Hash",
+ "MultiHash"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Default to Hash. Indicates the kind of algorithm used for partitioning."
+ }
+ },
+ "version": {
+ "type": "int",
+ "allowedValues": [
+ 1,
+ 2
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Default to 1 for Hash and 2 for MultiHash - 1 is not allowed for MultiHash. Version of the partition key definition."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of a container."
+ }
+ }
+ },
+ "parameters": {
+ "databaseAccountName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the SQL database ."
+ }
+ },
+ "containers": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/containerType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of containers to deploy in the SQL database."
+ }
+ },
+ "throughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Request units per second. Will be ignored if autoscaleSettingsMaxThroughput is used. Setting throughput at the database level is only recommended for development/test or when workload across all containers in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level."
+ }
+ },
+ "autoscaleSettingsMaxThroughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specifies the Autoscale settings and represents maximum throughput, the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If value is set to null, then autoscale will be disabled. Setting throughput at the database level is only recommended for development/test or when workload across all containers in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2025-04-15#properties/tags"
+ },
+ "description": "Optional. Tags of the SQL database resource."
+ },
+ "nullable": true
+ }
+ },
+ "resources": {
+ "databaseAccount": {
+ "existing": true,
+ "type": "Microsoft.DocumentDB/databaseAccounts",
+ "apiVersion": "2025-04-15",
+ "name": "[parameters('databaseAccountName')]"
+ },
+ "sqlDatabase": {
+ "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases",
+ "apiVersion": "2025-04-15",
+ "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('name'))]",
+ "tags": "[parameters('tags')]",
+ "properties": {
+ "resource": {
+ "id": "[parameters('name')]"
+ },
+ "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), null(), createObject('throughput', if(equals(parameters('autoscaleSettingsMaxThroughput'), null()), parameters('throughput'), null()), 'autoscaleSettings', if(not(equals(parameters('autoscaleSettingsMaxThroughput'), null())), createObject('maxThroughput', parameters('autoscaleSettingsMaxThroughput')), null())))]"
+ },
+ "dependsOn": [
+ "databaseAccount"
+ ]
+ },
+ "container": {
+ "copy": {
+ "name": "container",
+ "count": "[length(coalesce(parameters('containers'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-sqldb-{1}', uniqueString(deployment().name, parameters('name')), coalesce(parameters('containers'), createArray())[copyIndex()].name)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "databaseAccountName": {
+ "value": "[parameters('databaseAccountName')]"
+ },
+ "sqlDatabaseName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('containers'), createArray())[copyIndex()].name]"
+ },
+ "analyticalStorageTtl": {
+ "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'analyticalStorageTtl')]"
+ },
+ "autoscaleSettingsMaxThroughput": {
+ "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'autoscaleSettingsMaxThroughput')]"
+ },
+ "conflictResolutionPolicy": {
+ "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'conflictResolutionPolicy')]"
+ },
+ "defaultTtl": {
+ "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'defaultTtl')]"
+ },
+ "indexingPolicy": {
+ "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'indexingPolicy')]"
+ },
+ "kind": {
+ "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'kind')]"
+ },
+ "version": {
+ "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'version')]"
+ },
+ "paths": {
+ "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'paths')]"
+ },
+ "throughput": "[if(and(or(not(equals(parameters('throughput'), null())), not(equals(parameters('autoscaleSettingsMaxThroughput'), null()))), equals(tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'throughput'), null())), createObject('value', -1), createObject('value', tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'throughput')))]",
+ "uniqueKeyPolicyKeys": {
+ "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'uniqueKeyPolicyKeys')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.33.27573",
+ "templateHash": "1005439058963058082"
+ },
+ "name": "DocumentDB Database Account SQL Database Containers",
+ "description": "This module deploys a SQL Database Container in a CosmosDB Account."
+ },
+ "parameters": {
+ "databaseAccountName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "sqlDatabaseName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent SQL Database. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the container."
+ }
+ },
+ "analyticalStorageTtl": {
+ "type": "int",
+ "defaultValue": 0,
+ "metadata": {
+ "description": "Optional. Default to 0. Indicates how long data should be retained in the analytical store, for a container. Analytical store is enabled when ATTL is set with a value other than 0. If the value is set to -1, the analytical store retains all historical data, irrespective of the retention of the data in the transactional store."
+ }
+ },
+ "conflictResolutionPolicy": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/properties/properties/resource/properties/conflictResolutionPolicy"
+ },
+ "description": "Optional. The conflict resolution policy for the container. Conflicts and conflict resolution policies are applicable if the Azure Cosmos DB account is configured with multiple write regions."
+ },
+ "nullable": true
+ },
+ "defaultTtl": {
+ "type": "int",
+ "nullable": true,
+ "minValue": -1,
+ "maxValue": 2147483647,
+ "metadata": {
+ "description": "Optional. Default to -1. Default time to live (in seconds). With Time to Live or TTL, Azure Cosmos DB provides the ability to delete items automatically from a container after a certain time period. If the value is set to \"-1\", it is equal to infinity, and items don't expire by default."
+ }
+ },
+ "throughput": {
+ "type": "int",
+ "defaultValue": 400,
+ "metadata": {
+ "description": "Optional. Default to 400. Request Units per second. Will be ignored if autoscaleSettingsMaxThroughput is used. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level."
+ }
+ },
+ "autoscaleSettingsMaxThroughput": {
+ "type": "int",
+ "nullable": true,
+ "maxValue": 1000000,
+ "metadata": {
+ "description": "Optional. Specifies the Autoscale settings and represents maximum throughput, the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If value is set to null, then autoscale will be disabled. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/tags"
+ },
+ "description": "Optional. Tags of the SQL Database resource."
+ },
+ "nullable": true
+ },
+ "paths": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minLength": 1,
+ "maxLength": 3,
+ "metadata": {
+ "description": "Required. List of paths using which data within the container can be partitioned. For kind=MultiHash it can be up to 3. For anything else it needs to be exactly 1."
+ }
+ },
+ "indexingPolicy": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/properties/properties/resource/properties/indexingPolicy"
+ },
+ "description": "Optional. Indexing policy of the container."
+ },
+ "nullable": true
+ },
+ "uniqueKeyPolicyKeys": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/properties/properties/resource/properties/uniqueKeyPolicy/properties/uniqueKeys"
+ },
+ "description": "Optional. The unique key policy configuration containing a list of unique keys that enforces uniqueness constraint on documents in the collection in the Azure Cosmos DB service."
+ },
+ "nullable": true
+ },
+ "kind": {
+ "type": "string",
+ "defaultValue": "Hash",
+ "allowedValues": [
+ "Hash",
+ "MultiHash"
+ ],
+ "metadata": {
+ "description": "Optional. Default to Hash. Indicates the kind of algorithm used for partitioning."
+ }
+ },
+ "version": {
+ "type": "int",
+ "defaultValue": 1,
+ "allowedValues": [
+ 1,
+ 2
+ ],
+ "metadata": {
+ "description": "Optional. Default to 1 for Hash and 2 for MultiHash - 1 is not allowed for MultiHash. Version of the partition key definition."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "partitionKeyPaths",
+ "count": "[length(parameters('paths'))]",
+ "input": "[if(startsWith(parameters('paths')[copyIndex('partitionKeyPaths')], '/'), parameters('paths')[copyIndex('partitionKeyPaths')], format('/{0}', parameters('paths')[copyIndex('partitionKeyPaths')]))]"
+ }
+ ]
+ },
+ "resources": {
+ "databaseAccount::sqlDatabase": {
+ "existing": true,
+ "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases",
+ "apiVersion": "2025-04-15",
+ "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('sqlDatabaseName'))]"
+ },
+ "databaseAccount": {
+ "existing": true,
+ "type": "Microsoft.DocumentDB/databaseAccounts",
+ "apiVersion": "2025-04-15",
+ "name": "[parameters('databaseAccountName')]"
+ },
+ "container": {
+ "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers",
+ "apiVersion": "2025-04-15",
+ "name": "[format('{0}/{1}/{2}', parameters('databaseAccountName'), parameters('sqlDatabaseName'), parameters('name'))]",
+ "tags": "[parameters('tags')]",
+ "properties": {
+ "resource": "[shallowMerge(createArray(createObject('conflictResolutionPolicy', parameters('conflictResolutionPolicy'), 'id', parameters('name'), 'indexingPolicy', parameters('indexingPolicy'), 'partitionKey', createObject('paths', variables('partitionKeyPaths'), 'kind', parameters('kind'), 'version', if(equals(parameters('kind'), 'MultiHash'), 2, parameters('version'))), 'uniqueKeyPolicy', if(not(empty(parameters('uniqueKeyPolicyKeys'))), createObject('uniqueKeys', parameters('uniqueKeyPolicyKeys')), null())), if(not(equals(parameters('analyticalStorageTtl'), 0)), createObject('analyticalStorageTtl', parameters('analyticalStorageTtl')), createObject()), if(not(equals(parameters('defaultTtl'), null())), createObject('defaultTtl', parameters('defaultTtl')), createObject())))]",
+ "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), null(), createObject('throughput', if(and(equals(parameters('autoscaleSettingsMaxThroughput'), null()), not(equals(parameters('throughput'), -1))), parameters('throughput'), null()), 'autoscaleSettings', if(not(equals(parameters('autoscaleSettingsMaxThroughput'), null())), createObject('maxThroughput', parameters('autoscaleSettingsMaxThroughput')), null())))]"
+ },
+ "dependsOn": [
+ "databaseAccount"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the container."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the container."
+ },
+ "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers', parameters('databaseAccountName'), parameters('sqlDatabaseName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the container was created in."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "sqlDatabase"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the SQL database."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the SQL database."
+ },
+ "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the SQL database was created in."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "databaseAccount"
+ ]
+ },
+ "databaseAccount_sqlRoleDefinitions": {
+ "copy": {
+ "name": "databaseAccount_sqlRoleDefinitions",
+ "count": "[length(coalesce(parameters('sqlRoleDefinitions'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-sqlrd-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "databaseAccountName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[tryGet(coalesce(parameters('sqlRoleDefinitions'), createArray())[copyIndex()], 'name')]"
+ },
+ "dataActions": {
+ "value": "[coalesce(parameters('sqlRoleDefinitions'), createArray())[copyIndex()].dataActions]"
+ },
+ "roleName": {
+ "value": "[coalesce(parameters('sqlRoleDefinitions'), createArray())[copyIndex()].roleName]"
+ },
+ "assignableScopes": {
+ "value": "[tryGet(coalesce(parameters('sqlRoleDefinitions'), createArray())[copyIndex()], 'assignableScopes')]"
+ },
+ "sqlRoleAssignments": {
+ "value": "[tryGet(coalesce(parameters('sqlRoleDefinitions'), createArray())[copyIndex()], 'assignments')]"
+ },
+ "enableTelemetry": {
+ "value": "[variables('enableReferencedModulesTelemetry')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.33.27573",
+ "templateHash": "8600771348637416058"
+ },
+ "name": "DocumentDB Database Account SQL Role Definitions.",
+ "description": "This module deploys a SQL Role Definision in a CosmosDB Account."
+ },
+ "definitions": {
+ "sqlRoleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name unique identifier of the SQL Role Assignment."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The unique identifier for the associated AAD principal in the AAD graph to which access is being granted through this Role Assignment. Tenant ID for the principal is inferred using the tenant associated with the subscription."
+ }
+ },
+ "scope": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The data plane resource id for which access is being granted through this Role Assignment. Defaults to the root of the database account, but can also be scoped to e.g., the container and database level."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type for the SQL Role Assignments."
+ }
+ }
+ },
+ "parameters": {
+ "databaseAccountName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The unique identifier of the Role Definition."
+ }
+ },
+ "roleName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. A user-friendly name for the Role Definition. Must be unique for the database account."
+ }
+ },
+ "dataActions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minLength": 1,
+ "metadata": {
+ "description": "Required. An array of data actions that are allowed."
+ }
+ },
+ "assignableScopes": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A set of fully qualified Scopes at or below which Role Assignments may be created using this Role Definition. This will allow application of this Role Definition on the entire database account or any underlying Database / Collection. Must have at least one element. Scopes higher than Database account are not enforceable as assignable Scopes. Note that resources referenced in assignable Scopes need not exist. Defaults to the current account."
+ }
+ },
+ "sqlRoleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/sqlRoleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. An array of SQL Role Assignments to be created for the SQL Role Definition."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ }
+ },
+ "variables": {
+ "enableReferencedModulesTelemetry": false
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.doctdb-dbacct-sqlroledefinition.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "databaseAccount": {
+ "existing": true,
+ "type": "Microsoft.DocumentDB/databaseAccounts",
+ "apiVersion": "2024-11-15",
+ "name": "[parameters('databaseAccountName')]"
+ },
+ "sqlRoleDefinition": {
+ "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions",
+ "apiVersion": "2024-11-15",
+ "name": "[format('{0}/{1}', parameters('databaseAccountName'), coalesce(parameters('name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), parameters('roleName'))))]",
+ "properties": {
+ "assignableScopes": "[coalesce(parameters('assignableScopes'), createArray(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))))]",
+ "permissions": [
+ {
+ "dataActions": "[parameters('dataActions')]"
+ }
+ ],
+ "roleName": "[parameters('roleName')]",
+ "type": "CustomRole"
+ }
+ },
+ "databaseAccount_sqlRoleAssignments": {
+ "copy": {
+ "name": "databaseAccount_sqlRoleAssignments",
+ "count": "[length(coalesce(parameters('sqlRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-sqlra-{1}', uniqueString(deployment().name), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "databaseAccountName": {
+ "value": "[parameters('databaseAccountName')]"
+ },
+ "roleDefinitionIdOrName": {
+ "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', parameters('databaseAccountName'), coalesce(parameters('name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), parameters('roleName'))))]"
+ },
+ "principalId": {
+ "value": "[coalesce(parameters('sqlRoleAssignments'), createArray())[copyIndex()].principalId]"
+ },
+ "name": {
+ "value": "[tryGet(coalesce(parameters('sqlRoleAssignments'), createArray())[copyIndex()], 'name')]"
+ },
+ "enableTelemetry": {
+ "value": "[variables('enableReferencedModulesTelemetry')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.33.27573",
+ "templateHash": "17007224102611744259"
+ },
+ "name": "DocumentDB Database Account SQL Role Assignments.",
+ "description": "This module deploys a SQL Role Assignment in a CosmosDB Account."
+ },
+ "parameters": {
+ "databaseAccountName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name unique identifier of the SQL Role Assignment."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The unique identifier for the associated AAD principal in the AAD graph to which access is being granted through this Role Assignment. Tenant ID for the principal is inferred using the tenant associated with the subscription."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The unique identifier of the associated SQL Role Definition."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ },
+ "scope": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The data plane resource id for which access is being granted through this Role Assignment. Defaults to the root of the database account, but can also be scoped to e.g., the container and database level."
+ }
+ }
+ },
+ "variables": {
+ "builtInDataPlaneRoleNames": {
+ "Cosmos DB Built-in Data Reader": "[format('{0}/sqlRoleDefinitions/00000000-0000-0000-0000-000000000001', resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))]",
+ "Cosmos DB Built-in Data Contributor": "[format('{0}/sqlRoleDefinitions/00000000-0000-0000-0000-000000000002', resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))]"
+ },
+ "formattedRoleDefinition": "[coalesce(tryGet(variables('builtInDataPlaneRoleNames'), parameters('roleDefinitionIdOrName')), if(contains(parameters('roleDefinitionIdOrName'), '/sqlRoleDefinitions/'), parameters('roleDefinitionIdOrName'), format('{0}/sqlRoleDefinitions/{1}', resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('roleDefinitionIdOrName'))))]",
+ "formattedScope": "[replace(replace(coalesce(parameters('scope'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))), '/sqlDatabases/', '/dbs/'), '/containers/', '/colls/')]"
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.doctdb-dbacct-sqlroleassignment.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "databaseAccount": {
+ "existing": true,
+ "type": "Microsoft.DocumentDB/databaseAccounts",
+ "apiVersion": "2024-11-15",
+ "name": "[parameters('databaseAccountName')]"
+ },
+ "sqlRoleAssignment": {
+ "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments",
+ "apiVersion": "2024-11-15",
+ "name": "[format('{0}/{1}', parameters('databaseAccountName'), coalesce(parameters('name'), guid(variables('formattedRoleDefinition'), parameters('principalId'), variables('formattedScope'))))]",
+ "properties": {
+ "principalId": "[parameters('principalId')]",
+ "roleDefinitionId": "[variables('formattedRoleDefinition')]",
+ "scope": "[variables('formattedScope')]"
+ }
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the SQL Role Assignment."
+ },
+ "value": "[coalesce(parameters('name'), guid(variables('formattedRoleDefinition'), parameters('principalId'), variables('formattedScope')))]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the SQL Role Assignment."
+ },
+ "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments', parameters('databaseAccountName'), coalesce(parameters('name'), guid(variables('formattedRoleDefinition'), parameters('principalId'), variables('formattedScope'))))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the SQL Role Definition was created in."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "sqlRoleDefinition"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the SQL Role Definition."
+ },
+ "value": "[coalesce(parameters('name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), parameters('roleName')))]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the SQL Role Definition."
+ },
+ "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', parameters('databaseAccountName'), coalesce(parameters('name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), parameters('roleName'))))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the SQL Role Definition was created in."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "roleName": {
+ "type": "string",
+ "metadata": {
+ "description": "The role name of the SQL Role Definition."
+ },
+ "value": "[reference('sqlRoleDefinition').roleName]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "databaseAccount"
+ ]
+ },
+ "databaseAccount_sqlRoleAssignments": {
+ "copy": {
+ "name": "databaseAccount_sqlRoleAssignments",
+ "count": "[length(coalesce(parameters('sqlRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-sqlra-{1}', uniqueString(deployment().name), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "databaseAccountName": {
+ "value": "[parameters('name')]"
+ },
+ "roleDefinitionIdOrName": {
+ "value": "[coalesce(parameters('sqlRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]"
+ },
+ "principalId": {
+ "value": "[coalesce(parameters('sqlRoleAssignments'), createArray())[copyIndex()].principalId]"
+ },
+ "name": {
+ "value": "[tryGet(coalesce(parameters('sqlRoleAssignments'), createArray())[copyIndex()], 'name')]"
+ },
+ "scope": {
+ "value": "[tryGet(coalesce(parameters('sqlRoleAssignments'), createArray())[copyIndex()], 'scope')]"
+ },
+ "enableTelemetry": {
+ "value": "[variables('enableReferencedModulesTelemetry')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.33.27573",
+ "templateHash": "17007224102611744259"
+ },
+ "name": "DocumentDB Database Account SQL Role Assignments.",
+ "description": "This module deploys a SQL Role Assignment in a CosmosDB Account."
+ },
+ "parameters": {
+ "databaseAccountName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name unique identifier of the SQL Role Assignment."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The unique identifier for the associated AAD principal in the AAD graph to which access is being granted through this Role Assignment. Tenant ID for the principal is inferred using the tenant associated with the subscription."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The unique identifier of the associated SQL Role Definition."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ },
+ "scope": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The data plane resource id for which access is being granted through this Role Assignment. Defaults to the root of the database account, but can also be scoped to e.g., the container and database level."
+ }
+ }
+ },
+ "variables": {
+ "builtInDataPlaneRoleNames": {
+ "Cosmos DB Built-in Data Reader": "[format('{0}/sqlRoleDefinitions/00000000-0000-0000-0000-000000000001', resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))]",
+ "Cosmos DB Built-in Data Contributor": "[format('{0}/sqlRoleDefinitions/00000000-0000-0000-0000-000000000002', resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))]"
+ },
+ "formattedRoleDefinition": "[coalesce(tryGet(variables('builtInDataPlaneRoleNames'), parameters('roleDefinitionIdOrName')), if(contains(parameters('roleDefinitionIdOrName'), '/sqlRoleDefinitions/'), parameters('roleDefinitionIdOrName'), format('{0}/sqlRoleDefinitions/{1}', resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('roleDefinitionIdOrName'))))]",
+ "formattedScope": "[replace(replace(coalesce(parameters('scope'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))), '/sqlDatabases/', '/dbs/'), '/containers/', '/colls/')]"
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.doctdb-dbacct-sqlroleassignment.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "databaseAccount": {
+ "existing": true,
+ "type": "Microsoft.DocumentDB/databaseAccounts",
+ "apiVersion": "2024-11-15",
+ "name": "[parameters('databaseAccountName')]"
+ },
+ "sqlRoleAssignment": {
+ "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments",
+ "apiVersion": "2024-11-15",
+ "name": "[format('{0}/{1}', parameters('databaseAccountName'), coalesce(parameters('name'), guid(variables('formattedRoleDefinition'), parameters('principalId'), variables('formattedScope'))))]",
+ "properties": {
+ "principalId": "[parameters('principalId')]",
+ "roleDefinitionId": "[variables('formattedRoleDefinition')]",
+ "scope": "[variables('formattedScope')]"
+ }
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the SQL Role Assignment."
+ },
+ "value": "[coalesce(parameters('name'), guid(variables('formattedRoleDefinition'), parameters('principalId'), variables('formattedScope')))]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the SQL Role Assignment."
+ },
+ "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments', parameters('databaseAccountName'), coalesce(parameters('name'), guid(variables('formattedRoleDefinition'), parameters('principalId'), variables('formattedScope'))))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the SQL Role Definition was created in."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "databaseAccount",
+ "databaseAccount_sqlDatabases",
+ "databaseAccount_sqlRoleDefinitions"
+ ]
+ },
+ "databaseAccount_cassandraRoleDefinitions": {
+ "copy": {
+ "name": "databaseAccount_cassandraRoleDefinitions",
+ "count": "[length(coalesce(parameters('cassandraRoleDefinitions'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-cassandra-rd-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "databaseAccountName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[tryGet(coalesce(parameters('cassandraRoleDefinitions'), createArray())[copyIndex()], 'name')]"
+ },
+ "roleName": {
+ "value": "[coalesce(parameters('cassandraRoleDefinitions'), createArray())[copyIndex()].roleName]"
+ },
+ "dataActions": {
+ "value": "[tryGet(coalesce(parameters('cassandraRoleDefinitions'), createArray())[copyIndex()], 'dataActions')]"
+ },
+ "notDataActions": {
+ "value": "[tryGet(coalesce(parameters('cassandraRoleDefinitions'), createArray())[copyIndex()], 'notDataActions')]"
+ },
+ "assignableScopes": {
+ "value": "[tryGet(coalesce(parameters('cassandraRoleDefinitions'), createArray())[copyIndex()], 'assignableScopes')]"
+ },
+ "cassandraRoleAssignments": {
+ "value": "[tryGet(coalesce(parameters('cassandraRoleDefinitions'), createArray())[copyIndex()], 'assignments')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.33.27573",
+ "templateHash": "17859939500809924517"
+ },
+ "name": "DocumentDB Database Account Cassandra Role Definitions.",
+ "description": "This module deploys a Cassandra Role Definition in a CosmosDB Account."
+ },
+ "definitions": {
+ "cassandraRoleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The unique identifier of the role assignment."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The unique identifier for the associated AAD principal."
+ }
+ },
+ "scope": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The data plane resource path for which access is being granted. Defaults to the current account."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ }
+ },
+ "parameters": {
+ "databaseAccountName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The unique identifier of the Role Definition."
+ }
+ },
+ "roleName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. A user-friendly name for the Role Definition. Must be unique for the database account."
+ }
+ },
+ "dataActions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "defaultValue": [],
+ "metadata": {
+ "description": "Optional. An array of data actions that are allowed. Note: Valid data action strings for Cassandra API are currently undocumented (as of API version 2025-05-01-preview). Please refer to official Azure documentation once available."
+ }
+ },
+ "notDataActions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "defaultValue": [],
+ "metadata": {
+ "description": "Optional. An array of data actions that are denied. Note: Unlike SQL RBAC, Cassandra RBAC supports deny rules (notDataActions) for granular access control. Valid data action strings are currently undocumented (as of API version 2025-05-01-preview)."
+ }
+ },
+ "assignableScopes": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A set of fully qualified Scopes at or below which Role Assignments may be created using this Role Definition. This will allow application of this Role Definition on the entire database account or any underlying Database / Keyspace. Must have at least one element. Scopes higher than Database account are not enforceable as assignable Scopes. Note that resources referenced in assignable Scopes need not exist. Defaults to the current account."
+ }
+ },
+ "cassandraRoleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/cassandraRoleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. An array of Cassandra Role Assignments to be created for the Cassandra Role Definition."
+ }
+ }
+ },
+ "resources": {
+ "databaseAccount": {
+ "existing": true,
+ "type": "Microsoft.DocumentDB/databaseAccounts",
+ "apiVersion": "2024-11-15",
+ "name": "[parameters('databaseAccountName')]"
+ },
+ "cassandraRoleDefinition": {
+ "type": "Microsoft.DocumentDB/databaseAccounts/cassandraRoleDefinitions",
+ "apiVersion": "2025-05-01-preview",
+ "name": "[format('{0}/{1}', parameters('databaseAccountName'), coalesce(parameters('name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), parameters('roleName'))))]",
+ "properties": {
+ "assignableScopes": "[coalesce(parameters('assignableScopes'), createArray(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))))]",
+ "permissions": [
+ {
+ "dataActions": "[parameters('dataActions')]",
+ "notDataActions": "[parameters('notDataActions')]"
+ }
+ ],
+ "roleName": "[parameters('roleName')]",
+ "type": "CustomRole"
+ }
+ },
+ "databaseAccount_cassandraRoleAssignments": {
+ "copy": {
+ "name": "databaseAccount_cassandraRoleAssignments",
+ "count": "[length(coalesce(parameters('cassandraRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-cassandra-ra-{1}', uniqueString(deployment().name), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "databaseAccountName": {
+ "value": "[parameters('databaseAccountName')]"
+ },
+ "roleDefinitionId": {
+ "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/cassandraRoleDefinitions', parameters('databaseAccountName'), coalesce(parameters('name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), parameters('roleName'))))]"
+ },
+ "principalId": {
+ "value": "[coalesce(parameters('cassandraRoleAssignments'), createArray())[copyIndex()].principalId]"
+ },
+ "name": {
+ "value": "[tryGet(coalesce(parameters('cassandraRoleAssignments'), createArray())[copyIndex()], 'name')]"
+ },
+ "scope": {
+ "value": "[tryGet(coalesce(parameters('cassandraRoleAssignments'), createArray())[copyIndex()], 'scope')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.33.27573",
+ "templateHash": "552115240340341941"
+ },
+ "name": "DocumentDB Database Account Cassandra Role Assignments.",
+ "description": "This module deploys a Cassandra Role Assignment in a CosmosDB Account."
+ },
+ "parameters": {
+ "databaseAccountName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name unique identifier of the Cassandra Role Assignment."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The unique identifier for the associated AAD principal in the AAD graph to which access is being granted through this Role Assignment. Tenant ID for the principal is inferred using the tenant associated with the subscription."
+ }
+ },
+ "roleDefinitionId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The unique identifier of the associated Cassandra Role Definition."
+ }
+ },
+ "scope": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The data plane resource path for which access is being granted through this Cassandra Role Assignment. Defaults to the current account."
+ }
+ }
+ },
+ "resources": {
+ "databaseAccount": {
+ "existing": true,
+ "type": "Microsoft.DocumentDB/databaseAccounts",
+ "apiVersion": "2024-11-15",
+ "name": "[parameters('databaseAccountName')]"
+ },
+ "cassandraRoleAssignment": {
+ "type": "Microsoft.DocumentDB/databaseAccounts/cassandraRoleAssignments",
+ "apiVersion": "2025-05-01-preview",
+ "name": "[format('{0}/{1}', parameters('databaseAccountName'), coalesce(parameters('name'), guid(parameters('roleDefinitionId'), parameters('principalId'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))))]",
+ "properties": {
+ "principalId": "[parameters('principalId')]",
+ "roleDefinitionId": "[parameters('roleDefinitionId')]",
+ "scope": "[coalesce(parameters('scope'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))]"
+ }
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the Cassandra Role Assignment."
+ },
+ "value": "[coalesce(parameters('name'), guid(parameters('roleDefinitionId'), parameters('principalId'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))))]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the Cassandra Role Assignment."
+ },
+ "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/cassandraRoleAssignments', parameters('databaseAccountName'), coalesce(parameters('name'), guid(parameters('roleDefinitionId'), parameters('principalId'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the Cassandra Role Assignment was created in."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "cassandraRoleDefinition"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the cassandra role definition."
+ },
+ "value": "[coalesce(parameters('name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), parameters('roleName')))]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the cassandra role definition."
+ },
+ "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/cassandraRoleDefinitions', parameters('databaseAccountName'), coalesce(parameters('name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), parameters('roleName'))))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the cassandra role definition was created in."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "databaseAccount"
+ ]
+ },
+ "databaseAccount_cassandraRoleAssignments": {
+ "copy": {
+ "name": "databaseAccount_cassandraRoleAssignments",
+ "count": "[length(coalesce(parameters('cassandraRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-cassandra-ra-{1}', uniqueString(deployment().name), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "databaseAccountName": {
+ "value": "[parameters('name')]"
+ },
+ "roleDefinitionId": {
+ "value": "[coalesce(parameters('cassandraRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]"
+ },
+ "principalId": {
+ "value": "[coalesce(parameters('cassandraRoleAssignments'), createArray())[copyIndex()].principalId]"
+ },
+ "name": {
+ "value": "[tryGet(coalesce(parameters('cassandraRoleAssignments'), createArray())[copyIndex()], 'name')]"
+ },
+ "scope": {
+ "value": "[tryGet(coalesce(parameters('cassandraRoleAssignments'), createArray())[copyIndex()], 'scope')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.33.27573",
+ "templateHash": "552115240340341941"
+ },
+ "name": "DocumentDB Database Account Cassandra Role Assignments.",
+ "description": "This module deploys a Cassandra Role Assignment in a CosmosDB Account."
+ },
+ "parameters": {
+ "databaseAccountName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name unique identifier of the Cassandra Role Assignment."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The unique identifier for the associated AAD principal in the AAD graph to which access is being granted through this Role Assignment. Tenant ID for the principal is inferred using the tenant associated with the subscription."
+ }
+ },
+ "roleDefinitionId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The unique identifier of the associated Cassandra Role Definition."
+ }
+ },
+ "scope": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The data plane resource path for which access is being granted through this Cassandra Role Assignment. Defaults to the current account."
+ }
+ }
+ },
+ "resources": {
+ "databaseAccount": {
+ "existing": true,
+ "type": "Microsoft.DocumentDB/databaseAccounts",
+ "apiVersion": "2024-11-15",
+ "name": "[parameters('databaseAccountName')]"
+ },
+ "cassandraRoleAssignment": {
+ "type": "Microsoft.DocumentDB/databaseAccounts/cassandraRoleAssignments",
+ "apiVersion": "2025-05-01-preview",
+ "name": "[format('{0}/{1}', parameters('databaseAccountName'), coalesce(parameters('name'), guid(parameters('roleDefinitionId'), parameters('principalId'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))))]",
+ "properties": {
+ "principalId": "[parameters('principalId')]",
+ "roleDefinitionId": "[parameters('roleDefinitionId')]",
+ "scope": "[coalesce(parameters('scope'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))]"
+ }
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the Cassandra Role Assignment."
+ },
+ "value": "[coalesce(parameters('name'), guid(parameters('roleDefinitionId'), parameters('principalId'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))))]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the Cassandra Role Assignment."
+ },
+ "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/cassandraRoleAssignments', parameters('databaseAccountName'), coalesce(parameters('name'), guid(parameters('roleDefinitionId'), parameters('principalId'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the Cassandra Role Assignment was created in."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "databaseAccount",
+ "databaseAccount_cassandraKeyspaces",
+ "databaseAccount_cassandraRoleDefinitions"
+ ]
+ },
+ "databaseAccount_mongodbDatabases": {
+ "copy": {
+ "name": "databaseAccount_mongodbDatabases",
+ "count": "[length(coalesce(parameters('mongodbDatabases'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-mongodb-{1}', uniqueString(deployment().name, parameters('location')), coalesce(parameters('mongodbDatabases'), createArray())[copyIndex()].name)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "databaseAccountName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('mongodbDatabases'), createArray())[copyIndex()].name]"
+ },
+ "tags": {
+ "value": "[coalesce(tryGet(coalesce(parameters('mongodbDatabases'), createArray())[copyIndex()], 'tags'), parameters('tags'))]"
+ },
+ "collections": {
+ "value": "[tryGet(coalesce(parameters('mongodbDatabases'), createArray())[copyIndex()], 'collections')]"
+ },
+ "throughput": {
+ "value": "[tryGet(coalesce(parameters('mongodbDatabases'), createArray())[copyIndex()], 'throughput')]"
+ },
+ "autoscaleSettings": {
+ "value": "[tryGet(coalesce(parameters('mongodbDatabases'), createArray())[copyIndex()], 'autoscaleSettings')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.33.27573",
+ "templateHash": "7289795303297936310"
+ },
+ "name": "DocumentDB Database Account MongoDB Databases",
+ "description": "This module deploys a MongoDB Database within a CosmosDB Account."
+ },
+ "definitions": {
+ "collectionType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the collection."
+ }
+ },
+ "throughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Request Units per second. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the collection level and not at the database level."
+ }
+ },
+ "indexes": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections@2025-04-15#properties/properties/properties/resource/properties/indexes"
+ },
+ "description": "Required. Indexes for the collection."
+ }
+ },
+ "shardKey": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections@2025-04-15#properties/properties/properties/resource/properties/shardKey"
+ },
+ "description": "Required. ShardKey for the collection."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of a collection."
+ }
+ }
+ },
+ "parameters": {
+ "databaseAccountName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Cosmos DB database account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the mongodb database."
+ }
+ },
+ "throughput": {
+ "type": "int",
+ "defaultValue": 400,
+ "metadata": {
+ "description": "Optional. Request Units per second. Setting throughput at the database level is only recommended for development/test or when workload across all collections in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the collection level and not at the database level."
+ }
+ },
+ "collections": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/collectionType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Collections in the mongodb database."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases@2025-04-15#properties/tags"
+ },
+ "description": "Optional. Tags of the resource."
+ },
+ "nullable": true
+ },
+ "autoscaleSettings": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases@2025-04-15#properties/properties/properties/options/properties/autoscaleSettings"
+ },
+ "description": "Optional. Specifies the Autoscale settings. Note: Either throughput or autoscaleSettings is required, but not both."
+ },
+ "nullable": true
+ }
+ },
+ "resources": {
+ "databaseAccount": {
+ "existing": true,
+ "type": "Microsoft.DocumentDB/databaseAccounts",
+ "apiVersion": "2025-04-15",
+ "name": "[parameters('databaseAccountName')]"
+ },
+ "mongodbDatabase": {
+ "type": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases",
+ "apiVersion": "2025-04-15",
+ "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('name'))]",
+ "tags": "[parameters('tags')]",
+ "properties": {
+ "resource": {
+ "id": "[parameters('name')]"
+ },
+ "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), null(), createObject('throughput', parameters('throughput'), 'autoscaleSettings', parameters('autoscaleSettings')))]"
+ },
+ "dependsOn": [
+ "databaseAccount"
+ ]
+ },
+ "mongodbDatabase_collections": {
+ "copy": {
+ "name": "mongodbDatabase_collections",
+ "count": "[length(coalesce(parameters('collections'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-collection-{1}', uniqueString(deployment().name, parameters('name')), coalesce(parameters('collections'), createArray())[copyIndex()].name)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "databaseAccountName": {
+ "value": "[parameters('databaseAccountName')]"
+ },
+ "mongodbDatabaseName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('collections'), createArray())[copyIndex()].name]"
+ },
+ "indexes": {
+ "value": "[coalesce(parameters('collections'), createArray())[copyIndex()].indexes]"
+ },
+ "shardKey": {
+ "value": "[coalesce(parameters('collections'), createArray())[copyIndex()].shardKey]"
+ },
+ "throughput": {
+ "value": "[tryGet(coalesce(parameters('collections'), createArray())[copyIndex()], 'throughput')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.33.27573",
+ "templateHash": "4317369978166598876"
+ },
+ "name": "DocumentDB Database Account MongoDB Database Collections",
+ "description": "This module deploys a MongoDB Database Collection."
+ },
+ "parameters": {
+ "databaseAccountName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Cosmos DB database account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "mongodbDatabaseName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent mongodb database. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the collection."
+ }
+ },
+ "throughput": {
+ "type": "int",
+ "defaultValue": 400,
+ "metadata": {
+ "description": "Optional. Request Units per second. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the collection level and not at the database level."
+ }
+ },
+ "indexes": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections@2025-04-15#properties/properties/properties/resource/properties/indexes"
+ },
+ "description": "Required. Indexes for the collection."
+ }
+ },
+ "shardKey": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections@2025-04-15#properties/properties/properties/resource/properties/shardKey"
+ },
+ "description": "Required. ShardKey for the collection."
+ }
+ }
+ },
+ "resources": [
+ {
+ "type": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections",
+ "apiVersion": "2025-04-15",
+ "name": "[format('{0}/{1}/{2}', parameters('databaseAccountName'), parameters('mongodbDatabaseName'), parameters('name'))]",
+ "properties": {
+ "options": "[if(contains(reference(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), '2025-04-15').capabilities, createObject('name', 'EnableServerless')), null(), createObject('throughput', parameters('throughput')))]",
+ "resource": {
+ "id": "[parameters('name')]",
+ "indexes": "[parameters('indexes')]",
+ "shardKey": "[parameters('shardKey')]"
+ }
+ }
+ }
+ ],
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the mongodb database collection."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the mongodb database collection."
+ },
+ "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections', parameters('databaseAccountName'), parameters('mongodbDatabaseName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the mongodb database collection was created in."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "mongodbDatabase"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the mongodb database."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the mongodb database."
+ },
+ "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/mongodbDatabases', parameters('databaseAccountName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the mongodb database was created in."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "databaseAccount"
+ ]
+ },
+ "databaseAccount_gremlinDatabases": {
+ "copy": {
+ "name": "databaseAccount_gremlinDatabases",
+ "count": "[length(coalesce(parameters('gremlinDatabases'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-gremlin-{1}', uniqueString(deployment().name, parameters('location')), coalesce(parameters('gremlinDatabases'), createArray())[copyIndex()].name)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "databaseAccountName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('gremlinDatabases'), createArray())[copyIndex()].name]"
+ },
+ "tags": {
+ "value": "[coalesce(tryGet(coalesce(parameters('gremlinDatabases'), createArray())[copyIndex()], 'tags'), parameters('tags'))]"
+ },
+ "graphs": {
+ "value": "[tryGet(coalesce(parameters('gremlinDatabases'), createArray())[copyIndex()], 'graphs')]"
+ },
+ "maxThroughput": {
+ "value": "[tryGet(coalesce(parameters('gremlinDatabases'), createArray())[copyIndex()], 'maxThroughput')]"
+ },
+ "throughput": {
+ "value": "[tryGet(coalesce(parameters('gremlinDatabases'), createArray())[copyIndex()], 'throughput')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.33.27573",
+ "templateHash": "14708982296215631776"
+ },
+ "name": "DocumentDB Database Account Gremlin Databases",
+ "description": "This module deploys a Gremlin Database within a CosmosDB Account."
+ },
+ "definitions": {
+ "graphType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the graph."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs@2025-04-15#properties/tags"
+ },
+ "description": "Optional. Tags of the Gremlin graph resource."
+ },
+ "nullable": true
+ },
+ "indexingPolicy": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs@2025-04-15#properties/properties/properties/resource/properties/indexingPolicy"
+ },
+ "description": "Optional. Indexing policy of the graph."
+ },
+ "nullable": true
+ },
+ "partitionKeyPaths": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs@2025-04-15#properties/properties/properties/resource/properties/partitionKey/properties/paths"
+ },
+ "description": "Optional. List of paths using which data within the container can be partitioned."
+ },
+ "nullable": true
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of a graph."
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the Gremlin database."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases@2024-11-15#properties/tags"
+ },
+ "description": "Optional. Tags of the Gremlin database resource."
+ },
+ "nullable": true
+ },
+ "databaseAccountName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Gremlin database. Required if the template is used in a standalone deployment."
+ }
+ },
+ "graphs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/graphType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of graphs to deploy in the Gremlin database."
+ }
+ },
+ "maxThroughput": {
+ "type": "int",
+ "defaultValue": 4000,
+ "metadata": {
+ "description": "Optional. Represents maximum throughput, the resource can scale up to. Cannot be set together with `throughput`. If `throughput` is set to something else than -1, this autoscale setting is ignored. Setting throughput at the database level is only recommended for development/test or when workload across all graphs in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the graph level and not at the database level."
+ }
+ },
+ "throughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Request Units per second (for example 10000). Cannot be set together with `maxThroughput`. Setting throughput at the database level is only recommended for development/test or when workload across all graphs in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the graph level and not at the database level."
+ }
+ }
+ },
+ "resources": {
+ "databaseAccount": {
+ "existing": true,
+ "type": "Microsoft.DocumentDB/databaseAccounts",
+ "apiVersion": "2025-04-15",
+ "name": "[parameters('databaseAccountName')]"
+ },
+ "gremlinDatabase": {
+ "type": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases",
+ "apiVersion": "2025-04-15",
+ "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('name'))]",
+ "tags": "[parameters('tags')]",
+ "properties": {
+ "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), createObject(), createObject('autoscaleSettings', if(equals(parameters('throughput'), null()), createObject('maxThroughput', parameters('maxThroughput')), null()), 'throughput', parameters('throughput')))]",
+ "resource": {
+ "id": "[parameters('name')]"
+ }
+ },
+ "dependsOn": [
+ "databaseAccount"
+ ]
+ },
+ "gremlinDatabase_gremlinGraphs": {
+ "copy": {
+ "name": "gremlinDatabase_gremlinGraphs",
+ "count": "[length(coalesce(parameters('graphs'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-gremlindb-{1}', uniqueString(deployment().name, parameters('name')), coalesce(parameters('graphs'), createArray())[copyIndex()].name)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[coalesce(parameters('graphs'), createArray())[copyIndex()].name]"
+ },
+ "gremlinDatabaseName": {
+ "value": "[parameters('name')]"
+ },
+ "databaseAccountName": {
+ "value": "[parameters('databaseAccountName')]"
+ },
+ "indexingPolicy": {
+ "value": "[tryGet(coalesce(parameters('graphs'), createArray())[copyIndex()], 'indexingPolicy')]"
+ },
+ "partitionKeyPaths": {
+ "value": "[tryGet(coalesce(parameters('graphs'), createArray())[copyIndex()], 'partitionKeyPaths')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.33.27573",
+ "templateHash": "15097132107382000570"
+ },
+ "name": "DocumentDB Database Accounts Gremlin Databases Graphs",
+ "description": "This module deploys a DocumentDB Database Accounts Gremlin Database Graph."
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the graph."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs@2025-04-15#properties/tags"
+ },
+ "description": "Optional. Tags of the Gremlin graph resource."
+ },
+ "nullable": true
+ },
+ "databaseAccountName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "gremlinDatabaseName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Gremlin Database. Required if the template is used in a standalone deployment."
+ }
+ },
+ "indexingPolicy": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs@2025-04-15#properties/properties/properties/resource/properties/indexingPolicy"
+ },
+ "description": "Optional. Indexing policy of the graph."
+ },
+ "nullable": true
+ },
+ "partitionKeyPaths": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs@2025-04-15#properties/properties/properties/resource/properties/partitionKey/properties/paths"
+ },
+ "description": "Optional. List of paths using which data within the container can be partitioned."
+ },
+ "nullable": true
+ }
+ },
+ "resources": {
+ "databaseAccount::gremlinDatabase": {
+ "existing": true,
+ "type": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases",
+ "apiVersion": "2025-04-15",
+ "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('gremlinDatabaseName'))]"
+ },
+ "databaseAccount": {
+ "existing": true,
+ "type": "Microsoft.DocumentDB/databaseAccounts",
+ "apiVersion": "2025-04-15",
+ "name": "[parameters('databaseAccountName')]"
+ },
+ "gremlinGraph": {
+ "type": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs",
+ "apiVersion": "2025-04-15",
+ "name": "[format('{0}/{1}/{2}', parameters('databaseAccountName'), parameters('gremlinDatabaseName'), parameters('name'))]",
+ "tags": "[parameters('tags')]",
+ "properties": {
+ "resource": {
+ "id": "[parameters('name')]",
+ "indexingPolicy": "[parameters('indexingPolicy')]",
+ "partitionKey": {
+ "paths": "[parameters('partitionKeyPaths')]"
+ }
+ }
+ }
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the graph."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the graph."
+ },
+ "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs', parameters('databaseAccountName'), parameters('gremlinDatabaseName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the graph was created in."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "gremlinDatabase"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the Gremlin database."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the Gremlin database."
+ },
+ "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/gremlinDatabases', parameters('databaseAccountName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the Gremlin database was created in."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "databaseAccount"
+ ]
+ },
+ "databaseAccount_tables": {
+ "copy": {
+ "name": "databaseAccount_tables",
+ "count": "[length(coalesce(parameters('tables'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-table-{1}', uniqueString(deployment().name, parameters('location')), coalesce(parameters('tables'), createArray())[copyIndex()].name)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "databaseAccountName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('tables'), createArray())[copyIndex()].name]"
+ },
+ "tags": {
+ "value": "[coalesce(tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'tags'), parameters('tags'))]"
+ },
+ "maxThroughput": {
+ "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'maxThroughput')]"
+ },
+ "throughput": {
+ "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'throughput')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.33.27573",
+ "templateHash": "11768488776074268398"
+ },
+ "name": "Azure Cosmos DB account tables",
+ "description": "This module deploys a table within an Azure Cosmos DB Account."
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the table."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/tables@2025-04-15#properties/tags"
+ },
+ "description": "Optional. Tags for the table."
+ },
+ "nullable": true
+ },
+ "databaseAccountName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Azure Cosmos DB account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "maxThroughput": {
+ "type": "int",
+ "defaultValue": 4000,
+ "metadata": {
+ "description": "Optional. Represents maximum throughput, the resource can scale up to. Cannot be set together with `throughput`. If `throughput` is set to something else than -1, this autoscale setting is ignored."
+ }
+ },
+ "throughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Request Units per second (for example 10000). Cannot be set together with `maxThroughput`."
+ }
+ }
+ },
+ "resources": {
+ "databaseAccount": {
+ "existing": true,
+ "type": "Microsoft.DocumentDB/databaseAccounts",
+ "apiVersion": "2025-04-15",
+ "name": "[parameters('databaseAccountName')]"
+ },
+ "table": {
+ "type": "Microsoft.DocumentDB/databaseAccounts/tables",
+ "apiVersion": "2025-04-15",
+ "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('name'))]",
+ "tags": "[parameters('tags')]",
+ "properties": {
+ "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), createObject(), createObject('autoscaleSettings', if(equals(parameters('throughput'), null()), createObject('maxThroughput', parameters('maxThroughput')), null()), 'throughput', parameters('throughput')))]",
+ "resource": {
+ "id": "[parameters('name')]"
+ }
+ },
+ "dependsOn": [
+ "databaseAccount"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the table."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the table."
+ },
+ "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/tables', parameters('databaseAccountName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the table was created in."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "databaseAccount"
+ ]
+ },
+ "databaseAccount_cassandraKeyspaces": {
+ "copy": {
+ "name": "databaseAccount_cassandraKeyspaces",
+ "count": "[length(coalesce(parameters('cassandraKeyspaces'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-cassandradb-{1}', uniqueString(deployment().name, parameters('location')), coalesce(parameters('cassandraKeyspaces'), createArray())[copyIndex()].name)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "databaseAccountName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('cassandraKeyspaces'), createArray())[copyIndex()].name]"
+ },
+ "tags": {
+ "value": "[coalesce(tryGet(coalesce(parameters('cassandraKeyspaces'), createArray())[copyIndex()], 'tags'), parameters('tags'))]"
+ },
+ "tables": {
+ "value": "[tryGet(coalesce(parameters('cassandraKeyspaces'), createArray())[copyIndex()], 'tables')]"
+ },
+ "views": {
+ "value": "[tryGet(coalesce(parameters('cassandraKeyspaces'), createArray())[copyIndex()], 'views')]"
+ },
+ "autoscaleSettingsMaxThroughput": {
+ "value": "[tryGet(coalesce(parameters('cassandraKeyspaces'), createArray())[copyIndex()], 'autoscaleSettingsMaxThroughput')]"
+ },
+ "throughput": {
+ "value": "[tryGet(coalesce(parameters('cassandraKeyspaces'), createArray())[copyIndex()], 'throughput')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.33.27573",
+ "templateHash": "63327155428300562"
+ },
+ "name": "DocumentDB Database Account Cassandra Keyspaces",
+ "description": "This module deploys a Cassandra Keyspace within a CosmosDB Account."
+ },
+ "definitions": {
+ "tableType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the table."
+ }
+ },
+ "schema": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables@2024-11-15#properties/properties/properties/resource/properties/schema"
+ },
+ "description": "Required. Schema definition for the table."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables@2024-11-15#properties/tags"
+ },
+ "description": "Optional. Tags for the table."
+ },
+ "nullable": true
+ },
+ "defaultTtl": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Default TTL (Time To Live) in seconds for data in the table."
+ }
+ },
+ "analyticalStorageTtl": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Analytical TTL for the table."
+ }
+ },
+ "throughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Request units per second. Cannot be used with autoscaleSettingsMaxThroughput."
+ }
+ },
+ "autoscaleSettingsMaxThroughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Maximum autoscale throughput for the table. Cannot be used with throughput."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of a Cassandra table."
+ }
+ },
+ "viewType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the view."
+ }
+ },
+ "viewDefinition": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. View definition (CQL statement)."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/views@2025-05-01-preview#properties/tags"
+ },
+ "description": "Optional. Tags for the view."
+ },
+ "nullable": true
+ },
+ "throughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Request units per second. Cannot be used with autoscaleSettingsMaxThroughput."
+ }
+ },
+ "autoscaleSettingsMaxThroughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Maximum autoscale throughput for the view. Cannot be used with throughput."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of a Cassandra view (materialized view)."
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the Cassandra keyspace."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces@2024-11-15#properties/tags"
+ },
+ "description": "Optional. Tags of the Cassandra keyspace resource."
+ },
+ "nullable": true
+ },
+ "databaseAccountName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Cosmos DB account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "tables": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/tableType"
+ },
+ "defaultValue": [],
+ "metadata": {
+ "description": "Optional. Array of Cassandra tables to deploy in the keyspace."
+ }
+ },
+ "views": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/viewType"
+ },
+ "defaultValue": [],
+ "metadata": {
+ "description": "Optional. Array of Cassandra views (materialized views) to deploy in the keyspace."
+ }
+ },
+ "autoscaleSettingsMaxThroughput": {
+ "type": "int",
+ "defaultValue": 4000,
+ "metadata": {
+ "description": "Optional. Maximum autoscale throughput for the keyspace. If not set, autoscale will be disabled. Setting throughput at the keyspace level is only recommended for development/test or when workload across all tables in the shared throughput keyspace is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the table level."
+ }
+ },
+ "throughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Request units per second. Cannot be used with autoscaleSettingsMaxThroughput. Setting throughput at the keyspace level is only recommended for development/test or when workload across all tables in the shared throughput keyspace is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the table level."
+ }
+ }
+ },
+ "resources": {
+ "databaseAccount": {
+ "existing": true,
+ "type": "Microsoft.DocumentDB/databaseAccounts",
+ "apiVersion": "2024-11-15",
+ "name": "[parameters('databaseAccountName')]"
+ },
+ "cassandraKeyspace": {
+ "type": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces",
+ "apiVersion": "2024-11-15",
+ "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('name'))]",
+ "tags": "[parameters('tags')]",
+ "properties": {
+ "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), createObject(), createObject('autoscaleSettings', if(equals(parameters('throughput'), null()), createObject('maxThroughput', parameters('autoscaleSettingsMaxThroughput')), null()), 'throughput', parameters('throughput')))]",
+ "resource": {
+ "id": "[parameters('name')]"
+ }
+ },
+ "dependsOn": [
+ "databaseAccount"
+ ]
+ },
+ "cassandraKeyspace_tables": {
+ "copy": {
+ "name": "cassandraKeyspace_tables",
+ "count": "[length(parameters('tables'))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-cassandradb-{1}', uniqueString(deployment().name, parameters('name')), parameters('tables')[copyIndex()].name)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[parameters('tables')[copyIndex()].name]"
+ },
+ "cassandraKeyspaceName": {
+ "value": "[parameters('name')]"
+ },
+ "databaseAccountName": {
+ "value": "[parameters('databaseAccountName')]"
+ },
+ "schema": {
+ "value": "[parameters('tables')[copyIndex()].schema]"
+ },
+ "analyticalStorageTtl": {
+ "value": "[tryGet(parameters('tables')[copyIndex()], 'analyticalStorageTtl')]"
+ },
+ "throughput": {
+ "value": "[tryGet(parameters('tables')[copyIndex()], 'throughput')]"
+ },
+ "autoscaleSettingsMaxThroughput": {
+ "value": "[tryGet(parameters('tables')[copyIndex()], 'autoscaleSettingsMaxThroughput')]"
+ },
+ "defaultTtl": {
+ "value": "[tryGet(parameters('tables')[copyIndex()], 'defaultTtl')]"
+ },
+ "tags": {
+ "value": "[coalesce(tryGet(parameters('tables')[copyIndex()], 'tags'), parameters('tags'))]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.33.27573",
+ "templateHash": "785607874724829202"
+ },
+ "name": "DocumentDB Database Account Cassandra Keyspaces Tables",
+ "description": "This module deploys a Cassandra Table within a Cassandra Keyspace in a CosmosDB Account."
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the Cassandra table."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables@2024-11-15#properties/tags"
+ },
+ "description": "Optional. Tags of the Cassandra table resource."
+ },
+ "nullable": true
+ },
+ "databaseAccountName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "cassandraKeyspaceName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Cassandra Keyspace. Required if the template is used in a standalone deployment."
+ }
+ },
+ "schema": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables@2024-11-15#properties/properties/properties/resource/properties/schema"
+ },
+ "description": "Required. Schema definition for the Cassandra table."
+ }
+ },
+ "analyticalStorageTtl": {
+ "type": "int",
+ "defaultValue": 0,
+ "metadata": {
+ "description": "Optional. Analytical TTL for the table. Default to 0 (disabled). Analytical store is enabled when set to a value other than 0. If set to -1, analytical store retains all historical data."
+ }
+ },
+ "throughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Request units per second. Cannot be used with autoscaleSettingsMaxThroughput. If not specified, the table will inherit throughput from the keyspace."
+ }
+ },
+ "autoscaleSettingsMaxThroughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Maximum autoscale throughput for the table. Cannot be used with throughput. If not specified, the table will inherit throughput from the keyspace."
+ }
+ },
+ "defaultTtl": {
+ "type": "int",
+ "defaultValue": 0,
+ "metadata": {
+ "description": "Optional. Default time to live in seconds. Default to 0 (disabled). If set to -1, items do not expire."
+ }
+ }
+ },
+ "resources": {
+ "databaseAccount::cassandraKeyspace": {
+ "existing": true,
+ "type": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces",
+ "apiVersion": "2024-11-15",
+ "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('cassandraKeyspaceName'))]"
+ },
+ "databaseAccount": {
+ "existing": true,
+ "type": "Microsoft.DocumentDB/databaseAccounts",
+ "apiVersion": "2024-11-15",
+ "name": "[parameters('databaseAccountName')]"
+ },
+ "cassandraTable": {
+ "type": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables",
+ "apiVersion": "2024-11-15",
+ "name": "[format('{0}/{1}/{2}', parameters('databaseAccountName'), parameters('cassandraKeyspaceName'), parameters('name'))]",
+ "tags": "[parameters('tags')]",
+ "properties": {
+ "resource": {
+ "id": "[parameters('name')]",
+ "schema": "[parameters('schema')]",
+ "defaultTtl": "[parameters('defaultTtl')]",
+ "analyticalStorageTtl": "[parameters('analyticalStorageTtl')]"
+ },
+ "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), createObject(), createObject('autoscaleSettings', if(and(equals(parameters('throughput'), null()), not(equals(parameters('autoscaleSettingsMaxThroughput'), null()))), createObject('maxThroughput', parameters('autoscaleSettingsMaxThroughput')), null()), 'throughput', parameters('throughput')))]"
+ },
+ "dependsOn": [
+ "databaseAccount"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the Cassandra table."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the Cassandra table."
+ },
+ "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables', parameters('databaseAccountName'), parameters('cassandraKeyspaceName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the Cassandra table was created in."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "cassandraKeyspace"
+ ]
+ },
+ "cassandraKeyspace_views": {
+ "copy": {
+ "name": "cassandraKeyspace_views",
+ "count": "[length(parameters('views'))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-cassandraview-{1}', uniqueString(deployment().name, parameters('name')), parameters('views')[copyIndex()].name)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[parameters('views')[copyIndex()].name]"
+ },
+ "cassandraKeyspaceName": {
+ "value": "[parameters('name')]"
+ },
+ "databaseAccountName": {
+ "value": "[parameters('databaseAccountName')]"
+ },
+ "viewDefinition": {
+ "value": "[parameters('views')[copyIndex()].viewDefinition]"
+ },
+ "throughput": {
+ "value": "[tryGet(parameters('views')[copyIndex()], 'throughput')]"
+ },
+ "autoscaleSettingsMaxThroughput": {
+ "value": "[tryGet(parameters('views')[copyIndex()], 'autoscaleSettingsMaxThroughput')]"
+ },
+ "tags": {
+ "value": "[coalesce(tryGet(parameters('views')[copyIndex()], 'tags'), parameters('tags'))]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.33.27573",
+ "templateHash": "14021794949328228224"
+ },
+ "name": "DocumentDB Database Account Cassandra Keyspaces Views",
+ "description": "This module deploys a Cassandra View (Materialized View) within a Cassandra Keyspace in a CosmosDB Account."
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the Cassandra view."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/views@2025-05-01-preview#properties/tags"
+ },
+ "description": "Optional. Tags of the Cassandra view resource."
+ },
+ "nullable": true
+ },
+ "databaseAccountName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment."
+ }
+ },
+ "cassandraKeyspaceName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent Cassandra Keyspace. Required if the template is used in a standalone deployment."
+ }
+ },
+ "viewDefinition": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. View definition of the Cassandra view. This is the CQL statement that defines the materialized view."
+ }
+ },
+ "throughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Request units per second. Cannot be used with autoscaleSettingsMaxThroughput."
+ }
+ },
+ "autoscaleSettingsMaxThroughput": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Maximum autoscale throughput for the view. Cannot be used with throughput."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Optional. Location for all resources."
+ }
+ }
+ },
+ "resources": {
+ "databaseAccount::cassandraKeyspace": {
+ "existing": true,
+ "type": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces",
+ "apiVersion": "2025-05-01-preview",
+ "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('cassandraKeyspaceName'))]"
+ },
+ "databaseAccount": {
+ "existing": true,
+ "type": "Microsoft.DocumentDB/databaseAccounts",
+ "apiVersion": "2025-05-01-preview",
+ "name": "[parameters('databaseAccountName')]"
+ },
+ "cassandraView": {
+ "type": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/views",
+ "apiVersion": "2025-05-01-preview",
+ "name": "[format('{0}/{1}/{2}', parameters('databaseAccountName'), parameters('cassandraKeyspaceName'), parameters('name'))]",
+ "tags": "[parameters('tags')]",
+ "location": "[parameters('location')]",
+ "properties": {
+ "resource": {
+ "id": "[parameters('name')]",
+ "viewDefinition": "[parameters('viewDefinition')]"
+ },
+ "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), createObject(), createObject('autoscaleSettings', if(and(equals(parameters('throughput'), null()), not(equals(parameters('autoscaleSettingsMaxThroughput'), null()))), createObject('maxThroughput', parameters('autoscaleSettingsMaxThroughput')), null()), 'throughput', parameters('throughput')))]"
+ },
+ "dependsOn": [
+ "databaseAccount"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the Cassandra view."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the Cassandra view."
+ },
+ "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/views', parameters('databaseAccountName'), parameters('cassandraKeyspaceName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the Cassandra view was created in."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "cassandraKeyspace"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the Cassandra keyspace."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the Cassandra keyspace."
+ },
+ "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces', parameters('databaseAccountName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the Cassandra keyspace was created in."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "databaseAccount"
+ ]
+ },
+ "databaseAccount_privateEndpoints": {
+ "copy": {
+ "name": "databaseAccount_privateEndpoints",
+ "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-dbAccount-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]",
+ "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]",
+ "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex()))]"
+ },
+ "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), 'groupIds', createArray(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service))))), createObject('value', null()))]",
+ "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), 'groupIds', createArray(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service), 'requestMessage', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualConnectionRequestMessage'), 'Manual approval required.'))))), createObject('value', null()))]",
+ "subnetResourceId": {
+ "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]"
+ },
+ "enableTelemetry": {
+ "value": "[variables('enableReferencedModulesTelemetry')]"
+ },
+ "location": {
+ "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]"
+ },
+ "lock": {
+ "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]"
+ },
+ "privateDnsZoneGroup": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroup')]"
+ },
+ "roleAssignments": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]"
+ },
+ "tags": {
+ "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]"
+ },
+ "customDnsConfigs": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]"
+ },
+ "ipConfigurations": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]"
+ },
+ "applicationSecurityGroupResourceIds": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]"
+ },
+ "customNetworkInterfaceName": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.5.1644",
+ "templateHash": "16604612898799598358"
+ },
+ "name": "Private Endpoints",
+ "description": "This module deploys a Private Endpoint."
+ },
+ "definitions": {
+ "privateDnsZoneGroupType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the Private DNS Zone Group."
+ }
+ },
+ "privateDnsZoneGroupConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateDnsZoneGroupConfigType"
+ },
+ "metadata": {
+ "description": "Required. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of a private dns zone group."
+ }
+ },
+ "lockType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the name of lock."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "CanNotDelete",
+ "None",
+ "ReadOnly"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ },
+ "notes": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the notes of the lock."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a lock.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ },
+ "privateDnsZoneGroupConfigType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private DNS zone group config."
+ }
+ },
+ "privateDnsZoneResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource id of the private DNS zone."
+ }
+ }
+ },
+ "metadata": {
+ "description": "The type of a private DNS zone group configuration.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "private-dns-zone-group/main.bicep"
+ }
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the private endpoint resource to create."
+ }
+ },
+ "subnetResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Resource ID of the subnet where the endpoint needs to be created."
+ }
+ },
+ "applicationSecurityGroupResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Application security groups in which the private endpoint IP configuration is included."
+ }
+ },
+ "customNetworkInterfaceName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The custom name of the network interface attached to the private endpoint."
+ }
+ },
+ "ipConfigurations": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/ipConfigurations"
+ },
+ "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints."
+ },
+ "nullable": true
+ },
+ "privateDnsZoneGroup": {
+ "$ref": "#/definitions/privateDnsZoneGroupType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The private DNS zone group to configure for the private endpoint."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Optional. Location for all Resources."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The lock settings of the service."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/tags"
+ },
+ "description": "Optional. Tags to be applied on all resources/resource groups in this deployment."
+ },
+ "nullable": true
+ },
+ "customDnsConfigs": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs"
+ },
+ "description": "Optional. Custom DNS configurations."
+ },
+ "nullable": true
+ },
+ "manualPrivateLinkServiceConnections": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/manualPrivateLinkServiceConnections"
+ },
+ "description": "Conditional. A grouping of information about the connection to the remote resource. Used when the network admin does not have access to approve connections to the remote resource. Required if `privateLinkServiceConnections` is empty."
+ },
+ "nullable": true
+ },
+ "privateLinkServiceConnections": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/privateLinkServiceConnections"
+ },
+ "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty."
+ },
+ "nullable": true
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "DNS Resolver Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f2ebee7-ffd4-4fc0-b3b7-664099fdad5d')]",
+ "DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314')]",
+ "Domain Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'eeaeda52-9324-47f6-8069-5d5bade478b2')]",
+ "Domain Services Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '361898ef-9ed1-48c2-849c-a832951106bb')]",
+ "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.11.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "privateEndpoint": {
+ "type": "Microsoft.Network/privateEndpoints",
+ "apiVersion": "2024-10-01",
+ "name": "[parameters('name')]",
+ "location": "[parameters('location')]",
+ "tags": "[parameters('tags')]",
+ "properties": {
+ "copy": [
+ {
+ "name": "applicationSecurityGroups",
+ "count": "[length(coalesce(parameters('applicationSecurityGroupResourceIds'), createArray()))]",
+ "input": {
+ "id": "[coalesce(parameters('applicationSecurityGroupResourceIds'), createArray())[copyIndex('applicationSecurityGroups')]]"
+ }
+ }
+ ],
+ "customDnsConfigs": "[coalesce(parameters('customDnsConfigs'), createArray())]",
+ "customNetworkInterfaceName": "[coalesce(parameters('customNetworkInterfaceName'), '')]",
+ "ipConfigurations": "[coalesce(parameters('ipConfigurations'), createArray())]",
+ "manualPrivateLinkServiceConnections": "[coalesce(parameters('manualPrivateLinkServiceConnections'), createArray())]",
+ "privateLinkServiceConnections": "[coalesce(parameters('privateLinkServiceConnections'), createArray())]",
+ "subnet": {
+ "id": "[parameters('subnetResourceId')]"
+ }
+ }
+ },
+ "privateEndpoint_lock": {
+ "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]",
+ "type": "Microsoft.Authorization/locks",
+ "apiVersion": "2020-05-01",
+ "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]",
+ "properties": {
+ "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]",
+ "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]"
+ },
+ "dependsOn": [
+ "privateEndpoint"
+ ]
+ },
+ "privateEndpoint_roleAssignments": {
+ "copy": {
+ "name": "privateEndpoint_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateEndpoints', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "privateEndpoint"
+ ]
+ },
+ "privateEndpoint_privateDnsZoneGroup": {
+ "condition": "[not(empty(parameters('privateDnsZoneGroup')))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[tryGet(parameters('privateDnsZoneGroup'), 'name')]"
+ },
+ "privateEndpointName": {
+ "value": "[parameters('name')]"
+ },
+ "privateDnsZoneConfigs": {
+ "value": "[parameters('privateDnsZoneGroup').privateDnsZoneGroupConfigs]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.38.5.1644",
+ "templateHash": "24141742673128945"
+ },
+ "name": "Private Endpoint Private DNS Zone Groups",
+ "description": "This module deploys a Private Endpoint Private DNS Zone Group."
+ },
+ "definitions": {
+ "privateDnsZoneGroupConfigType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private DNS zone group config."
+ }
+ },
+ "privateDnsZoneResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource id of the private DNS zone."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of a private DNS zone group configuration."
+ }
+ }
+ },
+ "parameters": {
+ "privateEndpointName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment."
+ }
+ },
+ "privateDnsZoneConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateDnsZoneGroupConfigType"
+ },
+ "minLength": 1,
+ "maxLength": 5,
+ "metadata": {
+ "description": "Required. Array of private DNS zone configurations of the private DNS zone group. A DNS zone group can support up to 5 DNS zones."
+ }
+ },
+ "name": {
+ "type": "string",
+ "defaultValue": "default",
+ "metadata": {
+ "description": "Optional. The name of the private DNS zone group."
+ }
+ }
+ },
+ "resources": {
+ "privateEndpoint": {
+ "existing": true,
+ "type": "Microsoft.Network/privateEndpoints",
+ "apiVersion": "2024-10-01",
+ "name": "[parameters('privateEndpointName')]"
+ },
+ "privateDnsZoneGroup": {
+ "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups",
+ "apiVersion": "2024-10-01",
+ "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]",
+ "properties": {
+ "copy": [
+ {
+ "name": "privateDnsZoneConfigs",
+ "count": "[length(parameters('privateDnsZoneConfigs'))]",
+ "input": {
+ "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')].privateDnsZoneResourceId, '/')))]",
+ "properties": {
+ "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')].privateDnsZoneResourceId]"
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the private endpoint DNS zone group."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the private endpoint DNS zone group."
+ },
+ "value": "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', parameters('privateEndpointName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the private endpoint DNS zone group was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "privateEndpoint"
+ ]
+ }
+ },
+ "outputs": {
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the private endpoint was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the private endpoint."
+ },
+ "value": "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]"
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the private endpoint."
+ },
+ "value": "[parameters('name')]"
+ },
+ "location": {
+ "type": "string",
+ "metadata": {
+ "description": "The location the resource was deployed into."
+ },
+ "value": "[reference('privateEndpoint', '2024-10-01', 'full').location]"
+ },
+ "customDnsConfigs": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs",
+ "output": true
+ },
+ "description": "The custom DNS configurations of the private endpoint."
+ },
+ "value": "[reference('privateEndpoint').customDnsConfigs]"
+ },
+ "networkInterfaceResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "The resource IDs of the network interfaces associated with the private endpoint."
+ },
+ "value": "[map(reference('privateEndpoint').networkInterfaces, lambda('nic', lambdaVariables('nic').id))]"
+ },
+ "groupId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "The group Id for the private endpoint Group."
+ },
+ "value": "[coalesce(tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'manualPrivateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0), tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'privateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0))]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "databaseAccount"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the database account."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the database account."
+ },
+ "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the resource group the database account was created in."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "systemAssignedMIPrincipalId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "The principal ID of the system assigned identity."
+ },
+ "value": "[tryGet(tryGet(reference('databaseAccount', '2025-04-15', 'full'), 'identity'), 'principalId')]"
+ },
+ "location": {
+ "type": "string",
+ "metadata": {
+ "description": "The location the resource was deployed into."
+ },
+ "value": "[reference('databaseAccount', '2025-04-15', 'full').location]"
+ },
+ "endpoint": {
+ "type": "string",
+ "metadata": {
+ "description": "The endpoint of the database account."
+ },
+ "value": "[reference('databaseAccount').documentEndpoint]"
+ },
+ "privateEndpoints": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateEndpointOutputType"
+ },
+ "metadata": {
+ "description": "The private endpoints of the database account."
+ },
+ "copy": {
+ "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]",
+ "input": {
+ "name": "[reference(format('databaseAccount_privateEndpoints[{0}]', copyIndex())).outputs.name.value]",
+ "resourceId": "[reference(format('databaseAccount_privateEndpoints[{0}]', copyIndex())).outputs.resourceId.value]",
+ "groupId": "[tryGet(tryGet(reference(format('databaseAccount_privateEndpoints[{0}]', copyIndex())).outputs, 'groupId'), 'value')]",
+ "customDnsConfigs": "[reference(format('databaseAccount_privateEndpoints[{0}]', copyIndex())).outputs.customDnsConfigs.value]",
+ "networkInterfaceResourceIds": "[reference(format('databaseAccount_privateEndpoints[{0}]', copyIndex())).outputs.networkInterfaceResourceIds.value]"
+ }
+ }
+ },
+ "primaryReadWriteKey": {
+ "type": "securestring",
+ "metadata": {
+ "description": "The primary read-write key."
+ },
+ "value": "[listKeys('databaseAccount', '2025-04-15').primaryMasterKey]"
+ },
+ "primaryReadOnlyKey": {
+ "type": "securestring",
+ "metadata": {
+ "description": "The primary read-only key."
+ },
+ "value": "[listKeys('databaseAccount', '2025-04-15').primaryReadonlyMasterKey]"
+ },
+ "primaryReadWriteConnectionString": {
+ "type": "securestring",
+ "metadata": {
+ "description": "The primary read-write connection string."
+ },
+ "value": "[listConnectionStrings('databaseAccount', '2025-04-15').connectionStrings[0].connectionString]"
+ },
+ "primaryReadOnlyConnectionString": {
+ "type": "securestring",
+ "metadata": {
+ "description": "The primary read-only connection string."
+ },
+ "value": "[listConnectionStrings('databaseAccount', '2025-04-15').connectionStrings[2].connectionString]"
+ },
+ "secondaryReadWriteKey": {
+ "type": "securestring",
+ "metadata": {
+ "description": "The secondary read-write key."
+ },
+ "value": "[listKeys('databaseAccount', '2025-04-15').secondaryMasterKey]"
+ },
+ "secondaryReadOnlyKey": {
+ "type": "securestring",
+ "metadata": {
+ "description": "The secondary read-only key."
+ },
+ "value": "[listKeys('databaseAccount', '2025-04-15').secondaryReadonlyMasterKey]"
+ },
+ "secondaryReadWriteConnectionString": {
+ "type": "securestring",
+ "metadata": {
+ "description": "The secondary read-write connection string."
+ },
+ "value": "[listConnectionStrings('databaseAccount', '2025-04-15').connectionStrings[1].connectionString]"
+ },
+ "secondaryReadOnlyConnectionString": {
+ "type": "securestring",
+ "metadata": {
+ "description": "The secondary read-only connection string."
+ },
+ "value": "[listConnectionStrings('databaseAccount', '2025-04-15').connectionStrings[3].connectionString]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cosmosDB)]",
+ "logAnalyticsWorkspace",
+ "userAssignedIdentity",
+ "virtualNetwork"
+ ]
+ },
+ "webServerFarm": {
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[take(format('avm.res.web.serverfarm.{0}', variables('webServerFarmResourceName')), 64)]",
+ "resourceGroup": "[resourceGroup().name]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[variables('webServerFarmResourceName')]"
+ },
+ "tags": {
+ "value": "[parameters('tags')]"
+ },
+ "enableTelemetry": {
+ "value": "[parameters('enableTelemetry')]"
+ },
+ "location": {
+ "value": "[variables('solutionLocation')]"
+ },
+ "reserved": {
+ "value": true
+ },
+ "kind": {
+ "value": "linux"
+ },
+ "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), if(parameters('enableMonitoring'), reference('logAnalyticsWorkspace').outputs.resourceId.value, ''))))), createObject('value', null()))]",
+ "skuName": "[if(or(parameters('enableScalability'), parameters('enableRedundancy')), createObject('value', 'P1v3'), createObject('value', 'B1'))]",
+ "skuCapacity": {
+ "value": 1
+ },
+ "zoneRedundant": "[if(parameters('enableRedundancy'), createObject('value', true()), createObject('value', false()))]"
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.36.177.2456",
+ "templateHash": "16945786131371363466"
+ },
+ "name": "App Service Plan",
+ "description": "This module deploys an App Service Plan."
+ },
+ "definitions": {
+ "diagnosticSettingMetricsOnlyType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of diagnostic setting."
+ }
+ },
+ "metricCategories": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection."
+ }
+ },
+ "logAnalyticsDestinationType": {
+ "type": "string",
+ "allowedValues": [
+ "AzureDiagnostics",
+ "Dedicated"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type."
+ }
+ },
+ "workspaceResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "storageAccountResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "eventHubAuthorizationRuleResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to."
+ }
+ },
+ "eventHubName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "marketplacePartnerResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a diagnostic setting. To be used if only metrics are supported by the resource provider.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0"
+ }
+ }
+ },
+ "lockType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the name of lock."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "CanNotDelete",
+ "None",
+ "ReadOnly"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ },
+ "notes": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the notes of the lock."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a lock.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0"
+ }
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 60,
+ "metadata": {
+ "description": "Required. Name of the app service plan."
+ }
+ },
+ "skuName": {
+ "type": "string",
+ "defaultValue": "P1v3",
+ "metadata": {
+ "example": " 'F1'\n 'B1'\n 'P1v3'\n 'I1v2'\n 'FC1'\n ",
+ "description": "Optional. The name of the SKU will Determine the tier, size, family of the App Service Plan. This defaults to P1v3 to leverage availability zones."
+ }
+ },
+ "skuCapacity": {
+ "type": "int",
+ "defaultValue": 3,
+ "metadata": {
+ "description": "Optional. Number of workers associated with the App Service Plan. This defaults to 3, to leverage availability zones."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Optional. Location for all resources."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "defaultValue": "app",
+ "allowedValues": [
+ "app",
+ "elastic",
+ "functionapp",
+ "windows",
+ "linux"
+ ],
+ "metadata": {
+ "description": "Optional. Kind of server OS."
+ }
+ },
+ "reserved": {
+ "type": "bool",
+ "defaultValue": "[equals(parameters('kind'), 'linux')]",
+ "metadata": {
+ "description": "Conditional. Defaults to false when creating Windows/app App Service Plan. Required if creating a Linux App Service Plan and must be set to true."
+ }
+ },
+ "appServiceEnvironmentResourceId": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. The Resource ID of the App Service Environment to use for the App Service Plan."
+ }
+ },
+ "workerTierName": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. Target worker tier assigned to the App Service plan."
+ }
+ },
+ "perSiteScaling": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. If true, apps assigned to this App Service plan can be scaled independently. If false, apps assigned to this App Service plan will scale to all instances of the plan."
+ }
+ },
+ "elasticScaleEnabled": {
+ "type": "bool",
+ "defaultValue": "[greater(parameters('maximumElasticWorkerCount'), 1)]",
+ "metadata": {
+ "description": "Optional. Enable/Disable ElasticScaleEnabled App Service Plan."
+ }
+ },
+ "maximumElasticWorkerCount": {
+ "type": "int",
+ "defaultValue": 1,
+ "metadata": {
+ "description": "Optional. Maximum number of total workers allowed for this ElasticScaleEnabled App Service Plan."
+ }
+ },
+ "targetWorkerCount": {
+ "type": "int",
+ "defaultValue": 0,
+ "metadata": {
+ "description": "Optional. Scaling worker count."
+ }
+ },
+ "targetWorkerSize": {
+ "type": "int",
+ "defaultValue": 0,
+ "allowedValues": [
+ 0,
+ 1,
+ 2
+ ],
+ "metadata": {
+ "description": "Optional. The instance size of the hosting plan (small, medium, or large)."
+ }
+ },
+ "zoneRedundant": {
+ "type": "bool",
+ "defaultValue": "[if(or(startsWith(parameters('skuName'), 'P'), startsWith(parameters('skuName'), 'EP')), true(), false())]",
+ "metadata": {
+ "description": "Optional. Zone Redundant server farms can only be used on Premium or ElasticPremium SKU tiers within ZRS Supported regions (https://learn.microsoft.com/en-us/azure/storage/common/redundancy-regions-zrs)."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The lock settings of the service."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Web/serverfarms@2024-11-01#properties/tags"
+ },
+ "description": "Optional. Tags of the resource."
+ },
+ "nullable": true
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ },
+ "diagnosticSettings": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/diagnosticSettingMetricsOnlyType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The diagnostic settings of the service."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
+ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]",
+ "Web Plan Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2cc479cb-7b4d-49a8-b449-8c00fd0f0a4b')]",
+ "Website Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.web-serverfarm.{0}.{1}', replace('0.5.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "appServicePlan": {
+ "type": "Microsoft.Web/serverfarms",
+ "apiVersion": "2024-11-01",
+ "name": "[parameters('name')]",
+ "kind": "[parameters('kind')]",
+ "location": "[parameters('location')]",
+ "tags": "[parameters('tags')]",
+ "sku": "[if(equals(parameters('skuName'), 'FC1'), createObject('name', parameters('skuName'), 'tier', 'FlexConsumption'), createObject('name', parameters('skuName'), 'capacity', parameters('skuCapacity')))]",
+ "properties": {
+ "workerTierName": "[parameters('workerTierName')]",
+ "hostingEnvironmentProfile": "[if(not(empty(parameters('appServiceEnvironmentResourceId'))), createObject('id', parameters('appServiceEnvironmentResourceId')), null())]",
+ "perSiteScaling": "[parameters('perSiteScaling')]",
+ "maximumElasticWorkerCount": "[parameters('maximumElasticWorkerCount')]",
+ "elasticScaleEnabled": "[parameters('elasticScaleEnabled')]",
+ "reserved": "[parameters('reserved')]",
+ "targetWorkerCount": "[parameters('targetWorkerCount')]",
+ "targetWorkerSizeId": "[parameters('targetWorkerSize')]",
+ "zoneRedundant": "[parameters('zoneRedundant')]"
+ }
+ },
+ "appServicePlan_diagnosticSettings": {
+ "copy": {
+ "name": "appServicePlan_diagnosticSettings",
+ "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]"
+ },
+ "type": "Microsoft.Insights/diagnosticSettings",
+ "apiVersion": "2021-05-01-preview",
+ "scope": "[format('Microsoft.Web/serverfarms/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]",
+ "properties": {
+ "copy": [
+ {
+ "name": "metrics",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]",
+ "input": {
+ "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]",
+ "timeGrain": null
+ }
+ }
+ ],
+ "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]",
+ "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]",
+ "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]",
+ "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]",
+ "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]",
+ "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]"
+ },
+ "dependsOn": [
+ "appServicePlan"
+ ]
+ },
+ "appServicePlan_lock": {
+ "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]",
+ "type": "Microsoft.Authorization/locks",
+ "apiVersion": "2020-05-01",
+ "scope": "[format('Microsoft.Web/serverfarms/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]",
+ "properties": {
+ "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]",
+ "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]"
+ },
+ "dependsOn": [
+ "appServicePlan"
+ ]
+ },
+ "appServicePlan_roleAssignments": {
+ "copy": {
+ "name": "appServicePlan_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Web/serverfarms/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Web/serverfarms', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "appServicePlan"
+ ]
+ }
+ },
+ "outputs": {
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the app service plan was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the app service plan."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the app service plan."
+ },
+ "value": "[resourceId('Microsoft.Web/serverfarms', parameters('name'))]"
+ },
+ "location": {
+ "type": "string",
+ "metadata": {
+ "description": "The location the resource was deployed into."
+ },
+ "value": "[reference('appServicePlan', '2024-11-01', 'full').location]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "logAnalyticsWorkspace"
+ ]
+ },
+ "webSite": {
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[take(format('module.web-sites.{0}', variables('webSiteResourceName')), 64)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[variables('webSiteResourceName')]"
+ },
+ "tags": {
+ "value": "[parameters('tags')]"
+ },
+ "location": {
+ "value": "[variables('solutionLocation')]"
+ },
+ "kind": {
+ "value": "app,linux,container"
+ },
+ "serverFarmResourceId": {
+ "value": "[reference('webServerFarm').outputs.resourceId.value]"
+ },
+ "managedIdentities": {
+ "value": {
+ "userAssignedResourceIds": [
+ "[reference('userAssignedIdentity').outputs.resourceId.value]"
+ ]
+ }
+ },
+ "siteConfig": {
+ "value": {
+ "linuxFxVersion": "[format('DOCKER|{0}.azurecr.io/content-gen-app:{1}', variables('acrResourceName'), parameters('imageTag'))]",
+ "minTlsVersion": "1.2",
+ "alwaysOn": true,
+ "ftpsState": "FtpsOnly"
+ }
+ },
+ "virtualNetworkSubnetId": "[if(parameters('enablePrivateNetworking'), createObject('value', reference('virtualNetwork').outputs.webSubnetResourceId.value), createObject('value', null()))]",
+ "configs": {
+ "value": "[concat(createArray(createObject('name', 'appsettings', 'properties', createObject('DOCKER_REGISTRY_SERVER_URL', format('https://{0}.azurecr.io', variables('acrResourceName')), 'BACKEND_URL', variables('aciBackendUrl'), 'AZURE_CLIENT_ID', reference('userAssignedIdentity').outputs.clientId.value), 'applicationInsightResourceId', if(parameters('enableMonitoring'), reference('applicationInsights').outputs.resourceId.value, null()))), if(parameters('enableMonitoring'), createArray(createObject('name', 'logs', 'properties', createObject())), createArray()))]"
+ },
+ "enableMonitoring": {
+ "value": "[parameters('enableMonitoring')]"
+ },
+ "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), if(parameters('enableMonitoring'), reference('logAnalyticsWorkspace').outputs.resourceId.value, ''))))), createObject('value', null()))]",
+ "vnetRouteAllEnabled": {
+ "value": "[parameters('enablePrivateNetworking')]"
+ },
+ "vnetImagePullEnabled": {
+ "value": "[parameters('enablePrivateNetworking')]"
+ },
+ "publicNetworkAccess": {
+ "value": "Enabled"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "11911473200605315360"
+ }
+ },
+ "definitions": {
+ "appSettingsConfigType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "allowedValues": [
+ "appsettings",
+ "logs"
+ ],
+ "metadata": {
+ "description": "Required. The type of config."
+ }
+ },
+ "storageAccountUseIdentityAuthentication": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. If the provided storage account requires Identity based authentication."
+ }
+ },
+ "storageAccountResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Required if app of kind functionapp. Resource ID of the storage account to manage triggers and logging function executions."
+ }
+ },
+ "applicationInsightResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the application insight to leverage for this resource."
+ }
+ },
+ "retainCurrentAppSettings": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The retain the current app settings. Defaults to true."
+ }
+ },
+ "properties": {
+ "type": "object",
+ "properties": {},
+ "additionalProperties": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. An app settings key-value pair."
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The app settings key-value pairs."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true,
+ "description": "The type of an app settings configuration."
+ }
+ },
+ "_1.lockType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the name of lock."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "CanNotDelete",
+ "None",
+ "ReadOnly"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a lock.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ },
+ "_1.privateEndpointCustomDnsConfigType": {
+ "type": "object",
+ "properties": {
+ "fqdn": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. FQDN that resolves to private endpoint IP address."
+ }
+ },
+ "ipAddresses": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of private IP addresses of the private endpoint."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ },
+ "_1.privateEndpointIpConfigurationType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the resource that is unique within a resource group."
+ }
+ },
+ "properties": {
+ "type": "object",
+ "properties": {
+ "groupId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to."
+ }
+ },
+ "memberName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to."
+ }
+ },
+ "privateIPAddress": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. A private IP address obtained from the private endpoint's subnet."
+ }
+ }
+ },
+ "metadata": {
+ "description": "Required. Properties of private endpoint IP configurations."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ },
+ "_1.privateEndpointPrivateDnsZoneGroupType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the Private DNS Zone Group."
+ }
+ },
+ "privateDnsZoneGroupConfigs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private DNS Zone Group config."
+ }
+ },
+ "privateDnsZoneResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource id of the private DNS zone."
+ }
+ }
+ }
+ },
+ "metadata": {
+ "description": "Required. The private DNS Zone Groups to associate the Private Endpoint. A DNS Zone Group can support up to 5 DNS zones."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ },
+ "_1.roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ },
+ "diagnosticSettingFullType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the diagnostic setting."
+ }
+ },
+ "logCategoriesAndGroups": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here."
+ }
+ },
+ "categoryGroup": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection."
+ }
+ },
+ "metricCategories": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable or disable the category explicitly. Default is `true`."
+ }
+ }
+ }
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection."
+ }
+ },
+ "logAnalyticsDestinationType": {
+ "type": "string",
+ "allowedValues": [
+ "AzureDiagnostics",
+ "Dedicated"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type."
+ }
+ },
+ "workspaceResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "storageAccountResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "eventHubAuthorizationRuleResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to."
+ }
+ },
+ "eventHubName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
+ }
+ },
+ "marketplacePartnerResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ },
+ "managedIdentityAllType": {
+ "type": "object",
+ "properties": {
+ "systemAssigned": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enables system assigned managed identity on the resource."
+ }
+ },
+ "userAssignedResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID(s) to assign to the resource. Required if a user assigned identity is used for encryption."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ },
+ "privateEndpointSingleServiceType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the Private Endpoint."
+ }
+ },
+ "location": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The location to deploy the Private Endpoint to."
+ }
+ },
+ "privateLinkServiceConnectionName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private link connection to create."
+ }
+ },
+ "service": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The subresource to deploy the Private Endpoint for. For example \"vault\" for a Key Vault Private Endpoint."
+ }
+ },
+ "subnetResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Resource ID of the subnet where the endpoint needs to be created."
+ }
+ },
+ "resourceGroupResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID of the Resource Group the Private Endpoint will be created in. If not specified, the Resource Group of the provided Virtual Network Subnet is used."
+ }
+ },
+ "privateDnsZoneGroup": {
+ "$ref": "#/definitions/_1.privateEndpointPrivateDnsZoneGroupType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The private DNS Zone Group to configure for the Private Endpoint."
+ }
+ },
+ "isManualConnection": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. If Manual Private Link Connection is required."
+ }
+ },
+ "manualConnectionRequestMessage": {
+ "type": "string",
+ "nullable": true,
+ "maxLength": 140,
+ "metadata": {
+ "description": "Optional. A message passed to the owner of the remote resource with the manual connection request."
+ }
+ },
+ "customDnsConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/_1.privateEndpointCustomDnsConfigType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Custom DNS configurations."
+ }
+ },
+ "ipConfigurations": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/_1.privateEndpointIpConfigurationType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A list of IP configurations of the Private Endpoint. This will be used to map to the first-party Service endpoints."
+ }
+ },
+ "applicationSecurityGroupResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Application security groups in which the Private Endpoint IP configuration is included."
+ }
+ },
+ "customNetworkInterfaceName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The custom name of the network interface attached to the Private Endpoint."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/_1.lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/_1.roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Tags to be applied on all resources/Resource Groups in this deployment."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a private endpoint. To be used if the private endpoint's default service / groupId can be assumed (i.e., for services that only have one Private Endpoint type like 'vault' for key vault).",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the site."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Optional. Location for all Resources."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "functionapp",
+ "functionapp,linux",
+ "functionapp,workflowapp",
+ "functionapp,workflowapp,linux",
+ "functionapp,linux,container",
+ "functionapp,linux,container,azurecontainerapps",
+ "app,linux",
+ "app",
+ "linux,api",
+ "api",
+ "app,linux,container",
+ "app,container,windows"
+ ],
+ "metadata": {
+ "description": "Required. Type of site to deploy."
+ }
+ },
+ "serverFarmResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource ID of the app service plan to use for the site."
+ }
+ },
+ "managedEnvironmentId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Azure Resource Manager ID of the customers selected Managed Environment on which to host this app."
+ }
+ },
+ "httpsOnly": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Configures a site to accept only HTTPS requests."
+ }
+ },
+ "clientAffinityEnabled": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. If client affinity is enabled."
+ }
+ },
+ "appServiceEnvironmentResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID of the app service environment to use for this resource."
+ }
+ },
+ "managedIdentities": {
+ "$ref": "#/definitions/managedIdentityAllType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The managed identity definition for this resource."
+ }
+ },
+ "keyVaultAccessIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The resource ID of the assigned identity to be used to access a key vault with."
+ }
+ },
+ "storageAccountRequired": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Checks if Customer provided storage account is required."
+ }
+ },
+ "enableMonitoring": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Enable monitoring and logging configuration."
+ }
+ },
+ "virtualNetworkSubnetId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Azure Resource Manager ID of the Virtual network and subnet to be joined by Regional VNET Integration."
+ }
+ },
+ "vnetContentShareEnabled": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. To enable accessing content over virtual network."
+ }
+ },
+ "vnetImagePullEnabled": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. To enable pulling image over Virtual Network."
+ }
+ },
+ "vnetRouteAllEnabled": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Virtual Network Route All enabled."
+ }
+ },
+ "scmSiteAlsoStopped": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Stop SCM (KUDU) site when the app is stopped."
+ }
+ },
+ "siteConfig": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Web/sites@2024-04-01#properties/properties/properties/siteConfig"
+ },
+ "description": "Optional. The site config object."
+ },
+ "defaultValue": {
+ "alwaysOn": true,
+ "minTlsVersion": "1.2",
+ "ftpsState": "FtpsOnly"
+ }
+ },
+ "configs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/appSettingsConfigType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The web site config."
+ }
+ },
+ "functionAppConfig": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Web/sites@2024-04-01#properties/properties/properties/functionAppConfig"
+ },
+ "description": "Optional. The Function App configuration object."
+ },
+ "nullable": true
+ },
+ "privateEndpoints": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateEndpointSingleServiceType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Configuration details for private endpoints."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Tags of the resource."
+ }
+ },
+ "diagnosticSettings": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/diagnosticSettingFullType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The diagnostic settings of the service."
+ }
+ },
+ "clientCertEnabled": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. To enable client certificate authentication (TLS mutual authentication)."
+ }
+ },
+ "clientCertExclusionPaths": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Client certificate authentication comma-separated exclusion paths."
+ }
+ },
+ "clientCertMode": {
+ "type": "string",
+ "defaultValue": "Optional",
+ "allowedValues": [
+ "Optional",
+ "OptionalInteractiveUser",
+ "Required"
+ ],
+ "metadata": {
+ "description": "Optional. Client certificate mode."
+ }
+ },
+ "cloningInfo": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Web/sites@2024-04-01#properties/properties/properties/cloningInfo"
+ },
+ "description": "Optional. If specified during app creation, the app is cloned from a source app."
+ },
+ "nullable": true
+ },
+ "containerSize": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Size of the function container."
+ }
+ },
+ "dailyMemoryTimeQuota": {
+ "type": "int",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Maximum allowed daily memory-time quota (applicable on dynamic apps only)."
+ }
+ },
+ "enabled": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Setting this value to false disables the app (takes the app offline)."
+ }
+ },
+ "hostNameSslStates": {
+ "type": "array",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Web/sites@2024-04-01#properties/properties/properties/hostNameSslStates"
+ },
+ "description": "Optional. Hostname SSL states are used to manage the SSL bindings for app's hostnames."
+ },
+ "nullable": true
+ },
+ "hyperV": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Hyper-V sandbox."
+ }
+ },
+ "redundancyMode": {
+ "type": "string",
+ "defaultValue": "None",
+ "allowedValues": [
+ "ActiveActive",
+ "Failover",
+ "GeoRedundant",
+ "Manual",
+ "None"
+ ],
+ "metadata": {
+ "description": "Optional. Site redundancy mode."
+ }
+ },
+ "publicNetworkAccess": {
+ "type": "string",
+ "nullable": true,
+ "allowedValues": [
+ "Enabled",
+ "Disabled"
+ ],
+ "metadata": {
+ "description": "Optional. Whether or not public network access is allowed for this resource."
+ }
+ },
+ "e2eEncryptionEnabled": {
+ "type": "bool",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. End to End Encryption Setting."
+ }
+ },
+ "dnsConfiguration": {
+ "type": "object",
+ "metadata": {
+ "__bicep_resource_derived_type!": {
+ "source": "Microsoft.Web/sites@2024-04-01#properties/properties/properties/dnsConfiguration"
+ },
+ "description": "Optional. Property to configure various DNS related settings for a site."
+ },
+ "nullable": true
+ },
+ "autoGeneratedDomainNameLabelScope": {
+ "type": "string",
+ "nullable": true,
+ "allowedValues": [
+ "NoReuse",
+ "ResourceGroupReuse",
+ "SubscriptionReuse",
+ "TenantReuse"
+ ],
+ "metadata": {
+ "description": "Optional. Specifies the scope of uniqueness for the default hostname during resource creation."
+ }
+ }
+ },
+ "variables": {
+ "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]",
+ "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'SystemAssigned, UserAssigned', 'SystemAssigned'), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', 'None')), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]"
+ },
+ "resources": {
+ "app": {
+ "type": "Microsoft.Web/sites",
+ "apiVersion": "2024-04-01",
+ "name": "[parameters('name')]",
+ "location": "[parameters('location')]",
+ "kind": "[parameters('kind')]",
+ "tags": "[parameters('tags')]",
+ "identity": "[variables('identity')]",
+ "properties": {
+ "managedEnvironmentId": "[if(not(empty(parameters('managedEnvironmentId'))), parameters('managedEnvironmentId'), null())]",
+ "serverFarmId": "[parameters('serverFarmResourceId')]",
+ "clientAffinityEnabled": "[parameters('clientAffinityEnabled')]",
+ "httpsOnly": "[parameters('httpsOnly')]",
+ "hostingEnvironmentProfile": "[if(not(empty(parameters('appServiceEnvironmentResourceId'))), createObject('id', parameters('appServiceEnvironmentResourceId')), null())]",
+ "storageAccountRequired": "[parameters('storageAccountRequired')]",
+ "keyVaultReferenceIdentity": "[parameters('keyVaultAccessIdentityResourceId')]",
+ "virtualNetworkSubnetId": "[parameters('virtualNetworkSubnetId')]",
+ "siteConfig": "[parameters('siteConfig')]",
+ "functionAppConfig": "[parameters('functionAppConfig')]",
+ "clientCertEnabled": "[parameters('clientCertEnabled')]",
+ "clientCertExclusionPaths": "[parameters('clientCertExclusionPaths')]",
+ "clientCertMode": "[parameters('clientCertMode')]",
+ "cloningInfo": "[parameters('cloningInfo')]",
+ "containerSize": "[parameters('containerSize')]",
+ "dailyMemoryTimeQuota": "[parameters('dailyMemoryTimeQuota')]",
+ "enabled": "[parameters('enabled')]",
+ "hostNameSslStates": "[parameters('hostNameSslStates')]",
+ "hyperV": "[parameters('hyperV')]",
+ "redundancyMode": "[parameters('redundancyMode')]",
+ "publicNetworkAccess": "[if(not(empty(parameters('publicNetworkAccess'))), parameters('publicNetworkAccess'), if(not(empty(parameters('privateEndpoints'))), 'Disabled', 'Enabled'))]",
+ "vnetContentShareEnabled": "[parameters('vnetContentShareEnabled')]",
+ "vnetImagePullEnabled": "[parameters('vnetImagePullEnabled')]",
+ "vnetRouteAllEnabled": "[parameters('vnetRouteAllEnabled')]",
+ "scmSiteAlsoStopped": "[parameters('scmSiteAlsoStopped')]",
+ "endToEndEncryptionEnabled": "[parameters('e2eEncryptionEnabled')]",
+ "dnsConfiguration": "[parameters('dnsConfiguration')]",
+ "autoGeneratedDomainNameLabelScope": "[parameters('autoGeneratedDomainNameLabelScope')]"
+ }
+ },
+ "app_diagnosticSettings": {
+ "copy": {
+ "name": "app_diagnosticSettings",
+ "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]"
+ },
+ "type": "Microsoft.Insights/diagnosticSettings",
+ "apiVersion": "2021-05-01-preview",
+ "scope": "[format('Microsoft.Web/sites/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]",
+ "properties": {
+ "copy": [
+ {
+ "name": "metrics",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]",
+ "input": {
+ "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]",
+ "timeGrain": null
+ }
+ },
+ {
+ "name": "logs",
+ "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]",
+ "input": {
+ "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]",
+ "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]",
+ "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]"
+ }
+ }
+ ],
+ "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]",
+ "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]",
+ "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]",
+ "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]",
+ "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]",
+ "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]"
+ },
+ "dependsOn": [
+ "app"
+ ]
+ },
+ "app_config": {
+ "copy": {
+ "name": "app_config",
+ "count": "[length(coalesce(parameters('configs'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-Site-Config-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "appName": {
+ "value": "[parameters('name')]"
+ },
+ "name": {
+ "value": "[coalesce(parameters('configs'), createArray())[copyIndex()].name]"
+ },
+ "applicationInsightResourceId": {
+ "value": "[tryGet(coalesce(parameters('configs'), createArray())[copyIndex()], 'applicationInsightResourceId')]"
+ },
+ "storageAccountResourceId": {
+ "value": "[tryGet(coalesce(parameters('configs'), createArray())[copyIndex()], 'storageAccountResourceId')]"
+ },
+ "storageAccountUseIdentityAuthentication": {
+ "value": "[tryGet(coalesce(parameters('configs'), createArray())[copyIndex()], 'storageAccountUseIdentityAuthentication')]"
+ },
+ "properties": {
+ "value": "[tryGet(coalesce(parameters('configs'), createArray())[copyIndex()], 'properties')]"
+ },
+ "currentAppSettings": "[if(coalesce(tryGet(coalesce(parameters('configs'), createArray())[copyIndex()], 'retainCurrentAppSettings'), and(true(), equals(coalesce(parameters('configs'), createArray())[copyIndex()].name, 'appsettings'))), createObject('value', list(format('{0}/config/appsettings', resourceId('Microsoft.Web/sites', parameters('name'))), '2023-12-01').properties), createObject('value', createObject()))]",
+ "enableMonitoring": {
+ "value": "[parameters('enableMonitoring')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "13592577410661714505"
+ },
+ "name": "Site App Settings",
+ "description": "This module deploys a Site App Setting."
+ },
+ "parameters": {
+ "appName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent site resource. Required if the template is used in a standalone deployment."
+ }
+ },
+ "name": {
+ "type": "string",
+ "allowedValues": [
+ "appsettings",
+ "authsettings",
+ "authsettingsV2",
+ "azurestorageaccounts",
+ "backup",
+ "connectionstrings",
+ "logs",
+ "metadata",
+ "pushsettings",
+ "slotConfigNames",
+ "web"
+ ],
+ "metadata": {
+ "description": "Required. The name of the config."
+ }
+ },
+ "properties": {
+ "type": "object",
+ "defaultValue": {},
+ "metadata": {
+ "description": "Optional. The properties of the config."
+ }
+ },
+ "storageAccountUseIdentityAuthentication": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. If the provided storage account requires Identity based authentication."
+ }
+ },
+ "storageAccountResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Required if app of kind functionapp. Resource ID of the storage account."
+ }
+ },
+ "applicationInsightResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Resource ID of the application insight to leverage for this resource."
+ }
+ },
+ "currentAppSettings": {
+ "type": "object",
+ "properties": {},
+ "additionalProperties": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The key-values pairs of the current app settings."
+ }
+ },
+ "defaultValue": {},
+ "metadata": {
+ "description": "Optional. The current app settings."
+ }
+ },
+ "enableMonitoring": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "Optional. Enable monitoring and logging configuration."
+ }
+ }
+ },
+ "variables": {
+ "loggingProperties": "[if(and(parameters('enableMonitoring'), equals(parameters('name'), 'logs')), createObject('applicationLogs', createObject('fileSystem', createObject('level', 'Verbose')), 'httpLogs', createObject('fileSystem', createObject('enabled', true(), 'retentionInDays', 3, 'retentionInMb', 100)), 'detailedErrorMessages', createObject('enabled', true()), 'failedRequestsTracing', createObject('enabled', true())), createObject())]"
+ },
+ "resources": {
+ "applicationInsights": {
+ "condition": "[not(empty(parameters('applicationInsightResourceId')))]",
+ "existing": true,
+ "type": "Microsoft.Insights/components",
+ "apiVersion": "2020-02-02",
+ "subscriptionId": "[split(parameters('applicationInsightResourceId'), '/')[2]]",
+ "resourceGroup": "[split(parameters('applicationInsightResourceId'), '/')[4]]",
+ "name": "[last(split(parameters('applicationInsightResourceId'), '/'))]"
+ },
+ "storageAccount": {
+ "condition": "[not(empty(parameters('storageAccountResourceId')))]",
+ "existing": true,
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2024-01-01",
+ "subscriptionId": "[split(parameters('storageAccountResourceId'), '/')[2]]",
+ "resourceGroup": "[split(parameters('storageAccountResourceId'), '/')[4]]",
+ "name": "[last(split(parameters('storageAccountResourceId'), '/'))]"
+ },
+ "app": {
+ "existing": true,
+ "type": "Microsoft.Web/sites",
+ "apiVersion": "2023-12-01",
+ "name": "[parameters('appName')]"
+ },
+ "config": {
+ "type": "Microsoft.Web/sites/config",
+ "apiVersion": "2024-04-01",
+ "name": "[format('{0}/{1}', parameters('appName'), parameters('name'))]",
+ "properties": "[union(parameters('properties'), parameters('currentAppSettings'), if(and(not(empty(parameters('storageAccountResourceId'))), not(parameters('storageAccountUseIdentityAuthentication'))), createObject('AzureWebJobsStorage', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', last(split(parameters('storageAccountResourceId'), '/')), listKeys('storageAccount', '2024-01-01').keys[0].value, environment().suffixes.storage)), if(and(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountUseIdentityAuthentication')), createObject('AzureWebJobsStorage__accountName', last(split(parameters('storageAccountResourceId'), '/')), 'AzureWebJobsStorage__blobServiceUri', reference('storageAccount').primaryEndpoints.blob, 'AzureWebJobsStorage__queueServiceUri', reference('storageAccount').primaryEndpoints.queue, 'AzureWebJobsStorage__tableServiceUri', reference('storageAccount').primaryEndpoints.table), createObject())), if(not(empty(parameters('applicationInsightResourceId'))), createObject('APPLICATIONINSIGHTS_CONNECTION_STRING', reference('applicationInsights').ConnectionString), createObject()), variables('loggingProperties'))]",
+ "dependsOn": [
+ "applicationInsights",
+ "storageAccount"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the site config."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the site config."
+ },
+ "value": "[resourceId('Microsoft.Web/sites/config', parameters('appName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the site config was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "app"
+ ]
+ },
+ "app_privateEndpoints": {
+ "copy": {
+ "name": "app_privateEndpoints",
+ "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]"
+ },
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[format('{0}-app-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]",
+ "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]",
+ "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Web/sites', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sites'), copyIndex()))]"
+ },
+ "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Web/sites', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sites'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Web/sites', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sites')))))), createObject('value', null()))]",
+ "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Web/sites', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sites'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Web/sites', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sites')), 'requestMessage', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualConnectionRequestMessage'), 'Manual approval required.'))))), createObject('value', null()))]",
+ "subnetResourceId": {
+ "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]"
+ },
+ "enableTelemetry": {
+ "value": false
+ },
+ "location": {
+ "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]"
+ },
+ "lock": {
+ "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), null())]"
+ },
+ "privateDnsZoneGroup": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroup')]"
+ },
+ "roleAssignments": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]"
+ },
+ "tags": {
+ "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]"
+ },
+ "customDnsConfigs": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]"
+ },
+ "ipConfigurations": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]"
+ },
+ "applicationSecurityGroupResourceIds": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]"
+ },
+ "customNetworkInterfaceName": {
+ "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.34.44.8038",
+ "templateHash": "12389807800450456797"
+ },
+ "name": "Private Endpoints",
+ "description": "This module deploys a Private Endpoint."
+ },
+ "definitions": {
+ "privateDnsZoneGroupType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the Private DNS Zone Group."
+ }
+ },
+ "privateDnsZoneGroupConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateDnsZoneGroupConfigType"
+ },
+ "metadata": {
+ "description": "Required. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ },
+ "ipConfigurationType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the resource that is unique within a resource group."
+ }
+ },
+ "properties": {
+ "type": "object",
+ "properties": {
+ "groupId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string."
+ }
+ },
+ "memberName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string."
+ }
+ },
+ "privateIPAddress": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. A private IP address obtained from the private endpoint's subnet."
+ }
+ }
+ },
+ "metadata": {
+ "description": "Required. Properties of private endpoint IP configurations."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ },
+ "privateLinkServiceConnectionType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The name of the private link service connection."
+ }
+ },
+ "properties": {
+ "type": "object",
+ "properties": {
+ "groupIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string array `[]`."
+ }
+ },
+ "privateLinkServiceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource id of private link service."
+ }
+ },
+ "requestMessage": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars."
+ }
+ }
+ },
+ "metadata": {
+ "description": "Required. Properties of private link service connection."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ },
+ "customDnsConfigType": {
+ "type": "object",
+ "properties": {
+ "fqdn": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. FQDN that resolves to private endpoint IP address."
+ }
+ },
+ "ipAddresses": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Required. A list of private IP addresses of the private endpoint."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ },
+ "lockType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the name of lock."
+ }
+ },
+ "kind": {
+ "type": "string",
+ "allowedValues": [
+ "CanNotDelete",
+ "None",
+ "ReadOnly"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Specify the type of lock."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a lock.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ },
+ "privateDnsZoneGroupConfigType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private DNS zone group config."
+ }
+ },
+ "privateDnsZoneResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource id of the private DNS zone."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_imported_from!": {
+ "sourceTemplate": "private-dns-zone-group/main.bicep"
+ }
+ }
+ },
+ "roleAssignmentType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
+ }
+ },
+ "roleDefinitionIdOrName": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
+ }
+ },
+ "principalId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
+ }
+ },
+ "principalType": {
+ "type": "string",
+ "allowedValues": [
+ "Device",
+ "ForeignGroup",
+ "Group",
+ "ServicePrincipal",
+ "User"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The principal type of the assigned principal ID."
+ }
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The description of the role assignment."
+ }
+ },
+ "condition": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
+ }
+ },
+ "conditionVersion": {
+ "type": "string",
+ "allowedValues": [
+ "2.0"
+ ],
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Version of the condition."
+ }
+ },
+ "delegatedManagedIdentityResourceId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The Resource Id of the delegated managed identity resource."
+ }
+ }
+ },
+ "metadata": {
+ "description": "An AVM-aligned type for a role assignment.",
+ "__bicep_imported_from!": {
+ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the private endpoint resource to create."
+ }
+ },
+ "subnetResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Resource ID of the subnet where the endpoint needs to be created."
+ }
+ },
+ "applicationSecurityGroupResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Application security groups in which the private endpoint IP configuration is included."
+ }
+ },
+ "customNetworkInterfaceName": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The custom name of the network interface attached to the private endpoint."
+ }
+ },
+ "ipConfigurations": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ipConfigurationType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints."
+ }
+ },
+ "privateDnsZoneGroup": {
+ "$ref": "#/definitions/privateDnsZoneGroupType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The private DNS zone group to configure for the private endpoint."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Optional. Location for all Resources."
+ }
+ },
+ "lock": {
+ "$ref": "#/definitions/lockType",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The lock settings of the service."
+ }
+ },
+ "roleAssignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/roleAssignmentType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Array of role assignments to create."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Tags to be applied on all resources/resource groups in this deployment."
+ }
+ },
+ "customDnsConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/customDnsConfigType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. Custom DNS configurations."
+ }
+ },
+ "manualPrivateLinkServiceConnections": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateLinkServiceConnectionType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Conditional. A grouping of information about the connection to the remote resource. Used when the network admin does not have access to approve connections to the remote resource. Required if `privateLinkServiceConnections` is empty."
+ }
+ },
+ "privateLinkServiceConnections": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateLinkServiceConnectionType"
+ },
+ "nullable": true,
+ "metadata": {
+ "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable/Disable usage telemetry for module."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "formattedRoleAssignments",
+ "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
+ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
+ }
+ ],
+ "builtInRoleNames": {
+ "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
+ "DNS Resolver Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f2ebee7-ffd4-4fc0-b3b7-664099fdad5d')]",
+ "DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314')]",
+ "Domain Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'eeaeda52-9324-47f6-8069-5d5bade478b2')]",
+ "Domain Services Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '361898ef-9ed1-48c2-849c-a832951106bb')]",
+ "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
+ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
+ "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]",
+ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]"
+ }
+ },
+ "resources": {
+ "avmTelemetry": {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.11.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [],
+ "outputs": {
+ "telemetry": {
+ "type": "String",
+ "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
+ }
+ }
+ }
+ }
+ },
+ "privateEndpoint": {
+ "type": "Microsoft.Network/privateEndpoints",
+ "apiVersion": "2024-05-01",
+ "name": "[parameters('name')]",
+ "location": "[parameters('location')]",
+ "tags": "[parameters('tags')]",
+ "properties": {
+ "copy": [
+ {
+ "name": "applicationSecurityGroups",
+ "count": "[length(coalesce(parameters('applicationSecurityGroupResourceIds'), createArray()))]",
+ "input": {
+ "id": "[coalesce(parameters('applicationSecurityGroupResourceIds'), createArray())[copyIndex('applicationSecurityGroups')]]"
+ }
+ }
+ ],
+ "customDnsConfigs": "[coalesce(parameters('customDnsConfigs'), createArray())]",
+ "customNetworkInterfaceName": "[coalesce(parameters('customNetworkInterfaceName'), '')]",
+ "ipConfigurations": "[coalesce(parameters('ipConfigurations'), createArray())]",
+ "manualPrivateLinkServiceConnections": "[coalesce(parameters('manualPrivateLinkServiceConnections'), createArray())]",
+ "privateLinkServiceConnections": "[coalesce(parameters('privateLinkServiceConnections'), createArray())]",
+ "subnet": {
+ "id": "[parameters('subnetResourceId')]"
+ }
+ }
+ },
+ "privateEndpoint_lock": {
+ "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]",
+ "type": "Microsoft.Authorization/locks",
+ "apiVersion": "2020-05-01",
+ "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]",
+ "properties": {
+ "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]",
+ "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]"
+ },
+ "dependsOn": [
+ "privateEndpoint"
+ ]
+ },
+ "privateEndpoint_roleAssignments": {
+ "copy": {
+ "name": "privateEndpoint_roleAssignments",
+ "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
+ },
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]",
+ "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateEndpoints', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
+ "properties": {
+ "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
+ "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
+ "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
+ "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
+ "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
+ "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
+ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
+ },
+ "dependsOn": [
+ "privateEndpoint"
+ ]
+ },
+ "privateEndpoint_privateDnsZoneGroup": {
+ "condition": "[not(empty(parameters('privateDnsZoneGroup')))]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2022-09-01",
+ "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[tryGet(parameters('privateDnsZoneGroup'), 'name')]"
+ },
+ "privateEndpointName": {
+ "value": "[parameters('name')]"
+ },
+ "privateDnsZoneConfigs": {
+ "value": "[parameters('privateDnsZoneGroup').privateDnsZoneGroupConfigs]"
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.34.44.8038",
+ "templateHash": "13997305779829540948"
+ },
+ "name": "Private Endpoint Private DNS Zone Groups",
+ "description": "This module deploys a Private Endpoint Private DNS Zone Group."
+ },
+ "definitions": {
+ "privateDnsZoneGroupConfigType": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "Optional. The name of the private DNS zone group config."
+ }
+ },
+ "privateDnsZoneResourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. The resource id of the private DNS zone."
+ }
+ }
+ },
+ "metadata": {
+ "__bicep_export!": true
+ }
+ }
+ },
+ "parameters": {
+ "privateEndpointName": {
+ "type": "string",
+ "metadata": {
+ "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment."
+ }
+ },
+ "privateDnsZoneConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/privateDnsZoneGroupConfigType"
+ },
+ "minLength": 1,
+ "maxLength": 5,
+ "metadata": {
+ "description": "Required. Array of private DNS zone configurations of the private DNS zone group. A DNS zone group can support up to 5 DNS zones."
+ }
+ },
+ "name": {
+ "type": "string",
+ "defaultValue": "default",
+ "metadata": {
+ "description": "Optional. The name of the private DNS zone group."
+ }
+ }
+ },
+ "variables": {
+ "copy": [
+ {
+ "name": "privateDnsZoneConfigsVar",
+ "count": "[length(parameters('privateDnsZoneConfigs'))]",
+ "input": {
+ "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId, '/')))]",
+ "properties": {
+ "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId]"
+ }
+ }
+ }
+ ]
+ },
+ "resources": {
+ "privateEndpoint": {
+ "existing": true,
+ "type": "Microsoft.Network/privateEndpoints",
+ "apiVersion": "2024-05-01",
+ "name": "[parameters('privateEndpointName')]"
+ },
+ "privateDnsZoneGroup": {
+ "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups",
+ "apiVersion": "2024-05-01",
+ "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]",
+ "properties": {
+ "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigsVar')]"
+ }
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the private endpoint DNS zone group."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the private endpoint DNS zone group."
+ },
+ "value": "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', parameters('privateEndpointName'), parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the private endpoint DNS zone group was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "privateEndpoint"
+ ]
+ }
+ },
+ "outputs": {
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the private endpoint was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the private endpoint."
+ },
+ "value": "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]"
+ },
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the private endpoint."
+ },
+ "value": "[parameters('name')]"
+ },
+ "location": {
+ "type": "string",
+ "metadata": {
+ "description": "The location the resource was deployed into."
+ },
+ "value": "[reference('privateEndpoint', '2024-05-01', 'full').location]"
+ },
+ "customDnsConfigs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/customDnsConfigType"
+ },
+ "metadata": {
+ "description": "The custom DNS configurations of the private endpoint."
+ },
+ "value": "[reference('privateEndpoint').customDnsConfigs]"
+ },
+ "networkInterfaceResourceIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "metadata": {
+ "description": "The resource IDs of the network interfaces associated with the private endpoint."
+ },
+ "value": "[map(reference('privateEndpoint').networkInterfaces, lambda('nic', lambdaVariables('nic').id))]"
+ },
+ "groupId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "The group Id for the private endpoint Group."
+ },
+ "value": "[coalesce(tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'manualPrivateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0), tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'privateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0))]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "app"
+ ]
+ }
+ },
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the site."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the site."
+ },
+ "value": "[resourceId('Microsoft.Web/sites', parameters('name'))]"
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource group the site was deployed into."
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "systemAssignedMIPrincipalId": {
+ "type": "string",
+ "nullable": true,
+ "metadata": {
+ "description": "The principal ID of the system assigned identity."
+ },
+ "value": "[tryGet(tryGet(reference('app', '2024-04-01', 'full'), 'identity'), 'principalId')]"
+ },
+ "location": {
+ "type": "string",
+ "metadata": {
+ "description": "The location the resource was deployed into."
+ },
+ "value": "[reference('app', '2024-04-01', 'full').location]"
+ },
+ "defaultHostname": {
+ "type": "string",
+ "metadata": {
+ "description": "Default hostname of the app."
+ },
+ "value": "[format('https://{0}.azurewebsites.net', parameters('name'))]"
+ },
+ "customDomainVerificationId": {
+ "type": "string",
+ "metadata": {
+ "description": "Unique identifier that verifies the custom domains assigned to the app."
+ },
+ "value": "[reference('app').customDomainVerificationId]"
+ },
+ "outboundIpAddresses": {
+ "type": "string",
+ "metadata": {
+ "description": "The outbound IP addresses of the app."
+ },
+ "value": "[reference('app').outboundIpAddresses]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "applicationInsights",
+ "logAnalyticsWorkspace",
+ "userAssignedIdentity",
+ "virtualNetwork",
+ "webServerFarm"
+ ]
+ },
+ "containerInstance": {
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2025-04-01",
+ "name": "[take(format('module.container-instance.{0}', variables('containerInstanceName')), 64)]",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {
+ "name": {
+ "value": "[variables('containerInstanceName')]"
+ },
+ "location": {
+ "value": "[variables('solutionLocation')]"
+ },
+ "tags": {
+ "value": "[parameters('tags')]"
+ },
+ "containerImage": {
+ "value": "[format('{0}.azurecr.io/content-gen-api:{1}', variables('acrResourceName'), parameters('imageTag'))]"
+ },
+ "cpu": {
+ "value": 2
+ },
+ "memoryInGB": {
+ "value": 4
+ },
+ "port": {
+ "value": 8000
+ },
+ "subnetResourceId": "[if(parameters('enablePrivateNetworking'), createObject('value', reference('virtualNetwork').outputs.aciSubnetResourceId.value), createObject('value', ''))]",
+ "registryServer": {
+ "value": "[format('{0}.azurecr.io', variables('acrResourceName'))]"
+ },
+ "userAssignedIdentityResourceId": {
+ "value": "[reference('userAssignedIdentity').outputs.resourceId.value]"
+ },
+ "enableTelemetry": {
+ "value": "[parameters('enableTelemetry')]"
+ },
+ "environmentVariables": {
+ "value": [
+ {
+ "name": "AZURE_OPENAI_ENDPOINT",
+ "value": "[format('https://{0}.openai.azure.com/', variables('aiFoundryAiServicesResourceName'))]"
+ },
+ {
+ "name": "AZURE_OPENAI_GPT_MODEL",
+ "value": "[parameters('gptModelName')]"
+ },
+ {
+ "name": "AZURE_OPENAI_IMAGE_MODEL",
+ "value": "[variables('imageModelConfig')[parameters('imageModelChoice')].name]"
+ },
+ {
+ "name": "AZURE_OPENAI_GPT_IMAGE_ENDPOINT",
+ "value": "[if(not(equals(parameters('imageModelChoice'), 'none')), format('https://{0}.openai.azure.com/', variables('aiFoundryAiServicesResourceName')), '')]"
+ },
+ {
+ "name": "AZURE_OPENAI_API_VERSION",
+ "value": "[parameters('azureOpenaiAPIVersion')]"
+ },
+ {
+ "name": "AZURE_COSMOS_ENDPOINT",
+ "value": "[format('https://cosmos-{0}.documents.azure.com:443/', variables('solutionSuffix'))]"
+ },
+ {
+ "name": "AZURE_COSMOS_DATABASE_NAME",
+ "value": "[variables('cosmosDBDatabaseName')]"
+ },
+ {
+ "name": "AZURE_COSMOS_PRODUCTS_CONTAINER",
+ "value": "[variables('cosmosDBProductsContainer')]"
+ },
+ {
+ "name": "AZURE_COSMOS_CONVERSATIONS_CONTAINER",
+ "value": "[variables('cosmosDBConversationsContainer')]"
+ },
+ {
+ "name": "AZURE_BLOB_ACCOUNT_NAME",
+ "value": "[variables('storageAccountName')]"
+ },
+ {
+ "name": "AZURE_BLOB_PRODUCT_IMAGES_CONTAINER",
+ "value": "[variables('productImagesContainer')]"
+ },
+ {
+ "name": "AZURE_BLOB_GENERATED_IMAGES_CONTAINER",
+ "value": "[variables('generatedImagesContainer')]"
+ },
+ {
+ "name": "AZURE_AI_SEARCH_ENDPOINT",
+ "value": "[format('https://{0}.search.windows.net', variables('aiSearchName'))]"
+ },
+ {
+ "name": "AZURE_AI_SEARCH_PRODUCTS_INDEX",
+ "value": "[variables('azureSearchIndex')]"
+ },
+ {
+ "name": "AZURE_AI_SEARCH_IMAGE_INDEX",
+ "value": "product-images"
+ },
+ {
+ "name": "AZURE_CLIENT_ID",
+ "value": "[reference('userAssignedIdentity').outputs.clientId.value]"
+ },
+ {
+ "name": "PORT",
+ "value": "8000"
+ },
+ {
+ "name": "WORKERS",
+ "value": "4"
+ },
+ {
+ "name": "RUNNING_IN_PRODUCTION",
+ "value": "true"
+ },
+ {
+ "name": "USE_FOUNDRY",
+ "value": "[if(parameters('useFoundryMode'), 'true', 'false')]"
+ },
+ {
+ "name": "AZURE_AI_PROJECT_ENDPOINT",
+ "value": "[if(variables('useExistingAiFoundryAiProject'), format('https://{0}.services.ai.azure.com/api/projects/{1}', variables('aiFoundryAiServicesResourceName'), variables('aiFoundryAiProjectResourceName')), reference('aiFoundryAiServicesProject').outputs.apiEndpoint.value)]"
+ },
+ {
+ "name": "AZURE_AI_MODEL_DEPLOYMENT_NAME",
+ "value": "[parameters('gptModelName')]"
+ },
+ {
+ "name": "AZURE_AI_IMAGE_MODEL_DEPLOYMENT",
+ "value": "[variables('imageModelConfig')[parameters('imageModelChoice')].name]"
+ }
+ ]
+ }
+ },
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.39.26.7824",
+ "templateHash": "11247487291315089538"
+ }
+ },
+ "parameters": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Name of the container group."
+ }
+ },
+ "location": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Location for the container instance."
+ }
+ },
+ "tags": {
+ "type": "object",
+ "defaultValue": {},
+ "metadata": {
+ "description": "Optional. Tags for all resources."
+ }
+ },
+ "containerImage": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Container image to deploy."
+ }
+ },
+ "cpu": {
+ "type": "int",
+ "defaultValue": 2,
+ "metadata": {
+ "description": "Optional. CPU cores for the container."
+ }
+ },
+ "memoryInGB": {
+ "type": "int",
+ "defaultValue": 4,
+ "metadata": {
+ "description": "Optional. Memory in GB for the container."
+ }
+ },
+ "port": {
+ "type": "int",
+ "defaultValue": 8000,
+ "metadata": {
+ "description": "Optional. Port to expose."
+ }
+ },
+ "subnetResourceId": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. Subnet resource ID for VNet integration. If empty, public IP will be used."
+ }
+ },
+ "environmentVariables": {
+ "type": "array",
+ "metadata": {
+ "description": "Required. Environment variables for the container."
+ }
+ },
+ "enableTelemetry": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "Optional. Enable telemetry."
+ }
+ },
+ "registryServer": {
+ "type": "string",
+ "metadata": {
+ "description": "Required. Container registry server."
+ }
+ },
+ "userAssignedIdentityResourceId": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. User-assigned managed identity resource ID for ACR pull."
+ }
+ }
+ },
+ "variables": {
+ "isPrivateNetworking": "[not(empty(parameters('subnetResourceId')))]"
+ },
+ "resources": [
+ {
+ "condition": "[parameters('enableTelemetry')]",
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2024-03-01",
+ "name": "[format('46d3xbcp.res.containerinstance.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": []
+ }
+ }
+ },
+ {
+ "type": "Microsoft.ContainerInstance/containerGroups",
+ "apiVersion": "2023-05-01",
+ "name": "[parameters('name')]",
+ "location": "[parameters('location')]",
+ "tags": "[parameters('tags')]",
+ "identity": {
+ "type": "UserAssigned",
+ "userAssignedIdentities": {
+ "[format('{0}', parameters('userAssignedIdentityResourceId'))]": {}
+ }
+ },
+ "properties": {
+ "containers": [
+ {
+ "name": "[parameters('name')]",
+ "properties": {
+ "image": "[parameters('containerImage')]",
+ "resources": {
+ "requests": {
+ "cpu": "[parameters('cpu')]",
+ "memoryInGB": "[parameters('memoryInGB')]"
+ }
+ },
+ "ports": [
+ {
+ "port": "[parameters('port')]",
+ "protocol": "TCP"
+ }
+ ],
+ "environmentVariables": "[parameters('environmentVariables')]"
+ }
+ }
+ ],
+ "osType": "Linux",
+ "restartPolicy": "Always",
+ "subnetIds": "[if(variables('isPrivateNetworking'), createArray(createObject('id', parameters('subnetResourceId'))), null())]",
+ "ipAddress": {
+ "type": "[if(variables('isPrivateNetworking'), 'Private', 'Public')]",
+ "ports": [
+ {
+ "port": "[parameters('port')]",
+ "protocol": "TCP"
+ }
+ ],
+ "dnsNameLabel": "[if(variables('isPrivateNetworking'), null(), parameters('name'))]"
+ }
+ }
+ }
+ ],
+ "outputs": {
+ "name": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the container group."
+ },
+ "value": "[parameters('name')]"
+ },
+ "resourceId": {
+ "type": "string",
+ "metadata": {
+ "description": "The resource ID of the container group."
+ },
+ "value": "[resourceId('Microsoft.ContainerInstance/containerGroups', parameters('name'))]"
+ },
+ "ipAddress": {
+ "type": "string",
+ "metadata": {
+ "description": "The IP address of the container (private or public depending on mode)."
+ },
+ "value": "[reference(resourceId('Microsoft.ContainerInstance/containerGroups', parameters('name')), '2023-05-01').ipAddress.ip]"
+ },
+ "fqdn": {
+ "type": "string",
+ "metadata": {
+ "description": "The FQDN of the container (only available for public mode)."
+ },
+ "value": "[if(variables('isPrivateNetworking'), '', reference(resourceId('Microsoft.ContainerInstance/containerGroups', parameters('name')), '2023-05-01').ipAddress.fqdn)]"
+ }
+ }
+ }
+ },
+ "dependsOn": [
+ "aiFoundryAiServicesProject",
+ "userAssignedIdentity",
+ "virtualNetwork"
+ ]
+ }
+ },
+ "outputs": {
+ "APP_SERVICE_NAME": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains App Service Name"
+ },
+ "value": "[reference('webSite').outputs.name.value]"
+ },
+ "WEB_APP_URL": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains WebApp URL"
+ },
+ "value": "[format('https://{0}.azurewebsites.net', reference('webSite').outputs.name.value)]"
+ },
+ "AZURE_BLOB_ACCOUNT_NAME": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains Storage Account Name"
+ },
+ "value": "[reference('storageAccount').outputs.name.value]"
+ },
+ "AZURE_BLOB_PRODUCT_IMAGES_CONTAINER": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains Product Images Container"
+ },
+ "value": "[variables('productImagesContainer')]"
+ },
+ "AZURE_BLOB_GENERATED_IMAGES_CONTAINER": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains Generated Images Container"
+ },
+ "value": "[variables('generatedImagesContainer')]"
+ },
+ "COSMOSDB_ACCOUNT_NAME": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains CosmosDB Account Name"
+ },
+ "value": "[reference('cosmosDB').outputs.name.value]"
+ },
+ "AZURE_COSMOS_ENDPOINT": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains CosmosDB Endpoint URL"
+ },
+ "value": "[format('https://cosmos-{0}.documents.azure.com:443/', variables('solutionSuffix'))]"
+ },
+ "AZURE_COSMOS_DATABASE_NAME": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains CosmosDB Database Name"
+ },
+ "value": "[variables('cosmosDBDatabaseName')]"
+ },
+ "AZURE_COSMOS_PRODUCTS_CONTAINER": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains CosmosDB Products Container"
+ },
+ "value": "[variables('cosmosDBProductsContainer')]"
+ },
+ "AZURE_COSMOS_CONVERSATIONS_CONTAINER": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains CosmosDB Conversations Container"
+ },
+ "value": "[variables('cosmosDBConversationsContainer')]"
+ },
+ "RESOURCE_GROUP_NAME": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains Resource Group Name"
+ },
+ "value": "[resourceGroup().name]"
+ },
+ "AI_FOUNDRY_NAME": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains AI Foundry Name"
+ },
+ "value": "[variables('aiFoundryAiProjectResourceName')]"
+ },
+ "AI_FOUNDRY_RG_NAME": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains AI Foundry RG Name"
+ },
+ "value": "[variables('aiFoundryAiServicesResourceGroupName')]"
+ },
+ "AI_FOUNDRY_RESOURCE_ID": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains AI Foundry Resource ID"
+ },
+ "value": "[if(variables('useExistingAiFoundryAiProject'), '', reference('aiFoundryAiServices').outputs.resourceId.value)]"
+ },
+ "AZURE_EXISTING_AI_PROJECT_RESOURCE_ID": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains existing AI project resource ID."
+ },
+ "value": "[parameters('azureExistingAIProjectResourceId')]"
+ },
+ "AZURE_AI_SEARCH_ENDPOINT": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains AI Search Service Endpoint URL"
+ },
+ "value": "[format('https://{0}.search.windows.net/', reference('aiSearch').outputs.name.value)]"
+ },
+ "AI_SEARCH_SERVICE_NAME": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains AI Search Service Name"
+ },
+ "value": "[reference('aiSearch').outputs.name.value]"
+ },
+ "AZURE_AI_SEARCH_PRODUCTS_INDEX": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains AI Search Product Index"
+ },
+ "value": "[variables('azureSearchIndex')]"
+ },
+ "AZURE_AI_SEARCH_IMAGE_INDEX": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains AI Search Image Index"
+ },
+ "value": "product-images"
+ },
+ "AZURE_OPENAI_ENDPOINT": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains Azure OpenAI endpoint URL"
+ },
+ "value": "[format('https://{0}.openai.azure.com/', variables('aiFoundryAiServicesResourceName'))]"
+ },
+ "AZURE_OPENAI_GPT_MODEL": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains GPT Model"
+ },
+ "value": "[parameters('gptModelName')]"
+ },
+ "AZURE_OPENAI_IMAGE_MODEL": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains Image Model (empty if none selected)"
+ },
+ "value": "[variables('imageModelConfig')[parameters('imageModelChoice')].name]"
+ },
+ "AZURE_OPENAI_GPT_IMAGE_ENDPOINT": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains Azure OpenAI GPT/Image endpoint URL (empty if no image model selected)"
+ },
+ "value": "[if(not(equals(parameters('imageModelChoice'), 'none')), format('https://{0}.openai.azure.com/', variables('aiFoundryAiServicesResourceName')), '')]"
+ },
+ "AZURE_OPENAI_API_VERSION": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains Azure OpenAI API Version"
+ },
+ "value": "[parameters('azureOpenaiAPIVersion')]"
+ },
+ "AZURE_OPENAI_RESOURCE": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains OpenAI Resource"
+ },
+ "value": "[variables('aiFoundryAiServicesResourceName')]"
+ },
+ "AZURE_AI_AGENT_ENDPOINT": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains AI Agent Endpoint"
+ },
+ "value": "[if(variables('useExistingAiFoundryAiProject'), format('https://{0}.services.ai.azure.com/api/projects/{1}', variables('aiFoundryAiServicesResourceName'), variables('aiFoundryAiProjectResourceName')), reference('aiFoundryAiServicesProject').outputs.apiEndpoint.value)]"
+ },
+ "AZURE_AI_AGENT_API_VERSION": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains AI Agent API Version"
+ },
+ "value": "[parameters('azureAiAgentApiVersion')]"
+ },
+ "AZURE_APPLICATION_INSIGHTS_CONNECTION_STRING": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains Application Insights Connection String"
+ },
+ "value": "[if(and(parameters('enableMonitoring'), not(variables('useExistingLogAnalytics'))), reference('applicationInsights').outputs.connectionString.value, '')]"
+ },
+ "AI_SERVICE_LOCATION": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains the location used for AI Services deployment"
+ },
+ "value": "[variables('aiServiceLocation')]"
+ },
+ "CONTAINER_INSTANCE_NAME": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains Container Instance Name"
+ },
+ "value": "[reference('containerInstance').outputs.name.value]"
+ },
+ "CONTAINER_INSTANCE_IP": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains Container Instance IP Address"
+ },
+ "value": "[reference('containerInstance').outputs.ipAddress.value]"
+ },
+ "CONTAINER_INSTANCE_FQDN": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains Container Instance FQDN (only for non-private networking)"
+ },
+ "value": "[if(parameters('enablePrivateNetworking'), '', reference('containerInstance').outputs.fqdn.value)]"
+ },
+ "ACR_NAME": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains ACR Name"
+ },
+ "value": "[variables('acrResourceName')]"
+ },
+ "USE_FOUNDRY": {
+ "type": "bool",
+ "metadata": {
+ "description": "Contains flag for Azure AI Foundry usage"
+ },
+ "value": "[if(parameters('useFoundryMode'), true(), false())]"
+ },
+ "AZURE_AI_PROJECT_ENDPOINT": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains Azure AI Project Endpoint"
+ },
+ "value": "[if(variables('useExistingAiFoundryAiProject'), format('https://{0}.services.ai.azure.com/api/projects/{1}', variables('aiFoundryAiServicesResourceName'), variables('aiFoundryAiProjectResourceName')), reference('aiFoundryAiServicesProject').outputs.apiEndpoint.value)]"
+ },
+ "AZURE_AI_MODEL_DEPLOYMENT_NAME": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains Azure AI Model Deployment Name"
+ },
+ "value": "[parameters('gptModelName')]"
+ },
+ "AZURE_AI_IMAGE_MODEL_DEPLOYMENT": {
+ "type": "string",
+ "metadata": {
+ "description": "Contains Azure AI Image Model Deployment Name (empty if none selected)"
+ },
+ "value": "[variables('imageModelConfig')[parameters('imageModelChoice')].name]"
+ }
+ }
+}
\ No newline at end of file
diff --git a/content-gen/infra/main.parameters.json b/content-gen/infra/main.parameters.json
new file mode 100644
index 000000000..051635a50
--- /dev/null
+++ b/content-gen/infra/main.parameters.json
@@ -0,0 +1,66 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "solutionName": {
+ "value": "${AZURE_ENV_NAME}"
+ },
+ "location": {
+ "value": "${AZURE_LOCATION}"
+ },
+ "gptModelName": {
+ "value": "${gptModelName}"
+ },
+ "gptModelVersion": {
+ "value": "${gptModelVersion}"
+ },
+ "gptModelDeploymentType": {
+ "value": "${gptModelDeploymentType}"
+ },
+ "gptModelCapacity": {
+ "value": "${gptModelCapacity}"
+ },
+ "imageModelChoice": {
+ "value": "${imageModelChoice}"
+ },
+ "dalleModelCapacity": {
+ "value": "${dalleModelCapacity}"
+ },
+ "embeddingModel": {
+ "value": "${embeddingModel}"
+ },
+ "embeddingDeploymentCapacity": {
+ "value": "${embeddingDeploymentCapacity}"
+ },
+ "azureOpenaiAPIVersion": {
+ "value": "${azureOpenaiAPIVersion}"
+ },
+ "azureAiServiceLocation": {
+ "value": "${azureAiServiceLocation}"
+ },
+ "existingLogAnalyticsWorkspaceId": {
+ "value": "${existingLogAnalyticsWorkspaceId}"
+ },
+ "azureExistingAIProjectResourceId": {
+ "value": "${azureExistingAIProjectResourceId}"
+ },
+ "acrName": {
+ "value": "${acrName}"
+ },
+ "imageTag": {
+ "value": "${imageTag=latest}"
+ },
+ "enablePrivateNetworking": {
+ "value": "${enablePrivateNetworking}"
+ },
+ "enableMonitoring": {
+ "value": "${enableMonitoring}"
+ },
+ "enableScalability": {
+ "value": "${enableScalability}"
+ },
+ "enableRedundancy": {
+ "value": "${enableRedundancy}"
+ }
+ }
+}
diff --git a/content-gen/infra/modules/ai-project.bicep b/content-gen/infra/modules/ai-project.bicep
new file mode 100644
index 000000000..6809b0aac
--- /dev/null
+++ b/content-gen/infra/modules/ai-project.bicep
@@ -0,0 +1,58 @@
+@description('Required. Name of the AI Services project.')
+param name string
+
+@description('Required. The location of the Project resource.')
+param location string = resourceGroup().location
+
+@description('Optional. The description of the AI Foundry project to create. Defaults to the project name.')
+param desc string = name
+
+@description('Required. Name of the existing Cognitive Services resource to create the AI Foundry project in.')
+param aiServicesName string
+
+@description('Required. Azure Existing AI Project ResourceID.')
+param azureExistingAIProjectResourceId string = ''
+
+@description('Optional. Tags to be applied to the resources.')
+param tags object = {}
+
+var useExistingAiFoundryAiProject = !empty(azureExistingAIProjectResourceId)
+var existingOpenAIEndpoint = useExistingAiFoundryAiProject
+ ? format('https://{0}.openai.azure.com/', split(azureExistingAIProjectResourceId, '/')[8])
+ : ''
+
+// Reference to cognitive service in current resource group for new projects
+resource cogServiceReference 'Microsoft.CognitiveServices/accounts@2025-06-01' existing = {
+ name: aiServicesName
+}
+
+resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-06-01' = {
+ parent: cogServiceReference
+ name: name
+ tags: tags
+ location: location
+ identity: {
+ type: 'SystemAssigned'
+ }
+ properties: {
+ description: desc
+ displayName: name
+ }
+}
+
+@description('Required. Name of the AI project.')
+output name string = aiProject.name
+
+@description('Required. Resource ID of the AI project.')
+output resourceId string = aiProject.id
+
+@description('Required. API endpoint for the AI project.')
+output apiEndpoint string = aiProject!.properties.endpoints['AI Foundry API']
+
+@description('Contains AI Endpoint.')
+output aoaiEndpoint string = !empty(existingOpenAIEndpoint)
+ ? existingOpenAIEndpoint
+ : cogServiceReference.properties.endpoints['OpenAI Language Model Instance API']
+
+@description('Required. Principal ID of the AI project system-assigned managed identity.')
+output systemAssignedMIPrincipalId string = aiProject.identity.principalId
diff --git a/content-gen/infra/modules/container-instance.bicep b/content-gen/infra/modules/container-instance.bicep
new file mode 100644
index 000000000..53f7267db
--- /dev/null
+++ b/content-gen/infra/modules/container-instance.bicep
@@ -0,0 +1,127 @@
+// ========== container-instance.bicep ========== //
+// Azure Container Instance module for backend API deployment
+
+@description('Required. Name of the container group.')
+param name string
+
+@description('Required. Location for the container instance.')
+param location string
+
+@description('Optional. Tags for all resources.')
+param tags object = {}
+
+@description('Required. Container image to deploy.')
+param containerImage string
+
+@description('Optional. CPU cores for the container.')
+param cpu int = 2
+
+@description('Optional. Memory in GB for the container.')
+param memoryInGB int = 4
+
+@description('Optional. Port to expose.')
+param port int = 8000
+
+@description('Optional. Subnet resource ID for VNet integration. If empty, public IP will be used.')
+param subnetResourceId string = ''
+
+@description('Required. Environment variables for the container.')
+param environmentVariables array
+
+@description('Optional. Enable telemetry.')
+param enableTelemetry bool = true
+
+@description('Required. Container registry server.')
+param registryServer string
+
+@description('Optional. User-assigned managed identity resource ID for ACR pull.')
+param userAssignedIdentityResourceId string = ''
+
+var isPrivateNetworking = !empty(subnetResourceId)
+
+// ============== //
+// Resources //
+// ============== //
+
+#disable-next-line no-deployments-resources
+resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableTelemetry) {
+ name: '46d3xbcp.res.containerinstance.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}'
+ properties: {
+ mode: 'Incremental'
+ template: {
+ '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#'
+ contentVersion: '1.0.0.0'
+ resources: []
+ }
+ }
+}
+
+resource containerGroup 'Microsoft.ContainerInstance/containerGroups@2023-05-01' = {
+ name: name
+ location: location
+ tags: tags
+ identity: {
+ type: 'UserAssigned'
+ userAssignedIdentities: {
+ '${userAssignedIdentityResourceId}': {}
+ }
+ }
+ properties: {
+ containers: [
+ {
+ name: name
+ properties: {
+ image: containerImage
+ resources: {
+ requests: {
+ cpu: cpu
+ memoryInGB: memoryInGB
+ }
+ }
+ ports: [
+ {
+ port: port
+ protocol: 'TCP'
+ }
+ ]
+ environmentVariables: environmentVariables
+ }
+ }
+ ]
+ osType: 'Linux'
+ restartPolicy: 'Always'
+ subnetIds: isPrivateNetworking ? [
+ {
+ id: subnetResourceId
+ }
+ ] : null
+ ipAddress: {
+ type: isPrivateNetworking ? 'Private' : 'Public'
+ ports: [
+ {
+ port: port
+ protocol: 'TCP'
+ }
+ ]
+ dnsNameLabel: isPrivateNetworking ? null : name
+ }
+ // Removed imageRegistryCredentials - ACR is public with anonymous pull enabled
+ // If you need managed identity auth, add AcrPull role to the managed identity on the ACR
+ }
+}
+
+// ============== //
+// Outputs //
+// ============== //
+
+@description('The name of the container group.')
+output name string = containerGroup.name
+
+@description('The resource ID of the container group.')
+output resourceId string = containerGroup.id
+
+@description('The IP address of the container (private or public depending on mode).')
+output ipAddress string = containerGroup.properties.ipAddress.ip
+
+@description('The FQDN of the container (only available for public mode).')
+output fqdn string = isPrivateNetworking ? '' : containerGroup.properties.ipAddress.fqdn
diff --git a/content-gen/infra/modules/virtualNetwork.bicep b/content-gen/infra/modules/virtualNetwork.bicep
new file mode 100644
index 000000000..d4ec64795
--- /dev/null
+++ b/content-gen/infra/modules/virtualNetwork.bicep
@@ -0,0 +1,270 @@
+/****************************************************************************************************************************/
+// Networking - NSGs, VNET and Subnets for Content Generation Solution
+/****************************************************************************************************************************/
+@description('Name of the virtual network.')
+param vnetName string
+
+@description('Azure region to deploy resources.')
+param vnetLocation string = resourceGroup().location
+
+@description('Required. An Array of 1 or more IP Address Prefixes for the Virtual Network.')
+param vnetAddressPrefixes array = ['10.0.0.0/20']
+
+@description('Optional. Deploy Azure Bastion and Jumpbox subnets for VM-based administration.')
+param deployBastionAndJumpbox bool = false
+
+@description('An array of subnets to be created within the virtual network.')
+// Core subnets: web (App Service), peps (Private Endpoints), aci (Container Instance)
+// Optional: AzureBastionSubnet and jumpbox (only when deployBastionAndJumpbox is true)
+var coreSubnets = [
+ {
+ name: 'web'
+ addressPrefixes: ['10.0.0.0/23']
+ delegation: 'Microsoft.Web/serverFarms'
+ networkSecurityGroup: {
+ name: 'nsg-web'
+ securityRules: [
+ {
+ name: 'AllowHttpsInbound'
+ properties: {
+ access: 'Allow'
+ direction: 'Inbound'
+ priority: 100
+ protocol: 'Tcp'
+ sourcePortRange: '*'
+ destinationPortRange: '443'
+ sourceAddressPrefixes: ['0.0.0.0/0']
+ destinationAddressPrefixes: ['10.0.0.0/23']
+ }
+ }
+ {
+ name: 'AllowIntraSubnetTraffic'
+ properties: {
+ access: 'Allow'
+ direction: 'Inbound'
+ priority: 200
+ protocol: '*'
+ sourcePortRange: '*'
+ destinationPortRange: '*'
+ sourceAddressPrefixes: ['10.0.0.0/23']
+ destinationAddressPrefixes: ['10.0.0.0/23']
+ }
+ }
+ {
+ name: 'AllowAzureLoadBalancer'
+ properties: {
+ access: 'Allow'
+ direction: 'Inbound'
+ priority: 300
+ protocol: '*'
+ sourcePortRange: '*'
+ destinationPortRange: '*'
+ sourceAddressPrefix: 'AzureLoadBalancer'
+ destinationAddressPrefix: '10.0.0.0/23'
+ }
+ }
+ ]
+ }
+ }
+ {
+ name: 'peps'
+ addressPrefixes: ['10.0.2.0/23']
+ privateEndpointNetworkPolicies: 'Disabled'
+ privateLinkServiceNetworkPolicies: 'Disabled'
+ networkSecurityGroup: {
+ name: 'nsg-peps'
+ securityRules: []
+ }
+ }
+ {
+ name: 'aci'
+ addressPrefixes: ['10.0.4.0/24']
+ delegation: 'Microsoft.ContainerInstance/containerGroups'
+ networkSecurityGroup: {
+ name: 'nsg-aci'
+ securityRules: [
+ {
+ name: 'AllowHttpsInbound'
+ properties: {
+ access: 'Allow'
+ direction: 'Inbound'
+ priority: 100
+ protocol: 'Tcp'
+ sourcePortRange: '*'
+ destinationPortRange: '8000'
+ sourceAddressPrefixes: ['10.0.0.0/20']
+ destinationAddressPrefixes: ['10.0.4.0/24']
+ }
+ }
+ ]
+ }
+ }
+]
+
+// Optional Bastion and Jumpbox subnets (only deployed when needed for VM administration)
+var bastionSubnets = deployBastionAndJumpbox ? [
+ {
+ name: 'AzureBastionSubnet'
+ addressPrefixes: ['10.0.10.0/26']
+ networkSecurityGroup: {
+ name: 'nsg-bastion'
+ securityRules: [
+ {
+ name: 'AllowGatewayManager'
+ properties: {
+ access: 'Allow'
+ direction: 'Inbound'
+ priority: 2702
+ protocol: '*'
+ sourcePortRange: '*'
+ destinationPortRange: '443'
+ sourceAddressPrefix: 'GatewayManager'
+ destinationAddressPrefix: '*'
+ }
+ }
+ {
+ name: 'AllowHttpsInBound'
+ properties: {
+ access: 'Allow'
+ direction: 'Inbound'
+ priority: 2703
+ protocol: '*'
+ sourcePortRange: '*'
+ destinationPortRange: '443'
+ sourceAddressPrefix: 'Internet'
+ destinationAddressPrefix: '*'
+ }
+ }
+ {
+ name: 'AllowSshRdpOutbound'
+ properties: {
+ access: 'Allow'
+ direction: 'Outbound'
+ priority: 100
+ protocol: '*'
+ sourcePortRange: '*'
+ destinationPortRanges: ['22', '3389']
+ sourceAddressPrefix: '*'
+ destinationAddressPrefix: 'VirtualNetwork'
+ }
+ }
+ {
+ name: 'AllowAzureCloudOutbound'
+ properties: {
+ access: 'Allow'
+ direction: 'Outbound'
+ priority: 110
+ protocol: 'Tcp'
+ sourcePortRange: '*'
+ destinationPortRange: '443'
+ sourceAddressPrefix: '*'
+ destinationAddressPrefix: 'AzureCloud'
+ }
+ }
+ ]
+ }
+ }
+ {
+ name: 'jumpbox'
+ addressPrefixes: ['10.0.12.0/23']
+ networkSecurityGroup: {
+ name: 'nsg-jumpbox'
+ securityRules: [
+ {
+ name: 'AllowRdpFromBastion'
+ properties: {
+ access: 'Allow'
+ direction: 'Inbound'
+ priority: 100
+ protocol: 'Tcp'
+ sourcePortRange: '*'
+ destinationPortRange: '3389'
+ sourceAddressPrefixes: ['10.0.10.0/26']
+ destinationAddressPrefixes: ['10.0.12.0/23']
+ }
+ }
+ ]
+ }
+ }
+] : []
+
+var vnetSubnets = concat(coreSubnets, bastionSubnets)
+
+@description('Optional. Tags to be applied to the resources.')
+param tags object = {}
+
+@description('Optional. The resource ID of the Log Analytics Workspace to send diagnostic logs to.')
+param logAnalyticsWorkspaceId string = ''
+
+@description('Optional. Enable/Disable usage telemetry for module.')
+param enableTelemetry bool = true
+
+@description('Required. Suffix for resource naming.')
+param resourceSuffix string
+
+// Create NSGs for subnets using AVM Network Security Group module
+@batchSize(1)
+module nsgs 'br/public:avm/res/network/network-security-group:0.5.2' = [
+ for (subnet, i) in vnetSubnets: if (!empty(subnet.?networkSecurityGroup)) {
+ name: take('avm.res.network.network-security-group.${subnet.?networkSecurityGroup.name}.${resourceSuffix}', 64)
+ params: {
+ name: '${subnet.?networkSecurityGroup.name}-${resourceSuffix}'
+ location: vnetLocation
+ securityRules: subnet.?networkSecurityGroup.securityRules
+ tags: tags
+ enableTelemetry: enableTelemetry
+ }
+ }
+]
+
+// Create VNet and subnets using AVM Virtual Network module
+module virtualNetwork 'br/public:avm/res/network/virtual-network:0.7.2' = {
+ name: take('avm.res.network.virtual-network.${vnetName}', 64)
+ params: {
+ name: vnetName
+ location: vnetLocation
+ addressPrefixes: vnetAddressPrefixes
+ subnets: [
+ for (subnet, i) in vnetSubnets: {
+ name: subnet.name
+ addressPrefixes: subnet.?addressPrefixes
+ networkSecurityGroupResourceId: !empty(subnet.?networkSecurityGroup) ? nsgs[i]!.outputs.resourceId : null
+ privateEndpointNetworkPolicies: subnet.?privateEndpointNetworkPolicies
+ privateLinkServiceNetworkPolicies: subnet.?privateLinkServiceNetworkPolicies
+ delegation: subnet.?delegation
+ }
+ ]
+ diagnosticSettings: !empty(logAnalyticsWorkspaceId) ? [
+ {
+ name: 'vnetDiagnostics'
+ workspaceResourceId: logAnalyticsWorkspaceId
+ logCategoriesAndGroups: [
+ {
+ categoryGroup: 'allLogs'
+ enabled: true
+ }
+ ]
+ metricCategories: [
+ {
+ category: 'AllMetrics'
+ enabled: true
+ }
+ ]
+ }
+ ] : []
+ tags: tags
+ enableTelemetry: enableTelemetry
+ }
+}
+
+output name string = virtualNetwork.outputs.name
+output resourceId string = virtualNetwork.outputs.resourceId
+
+// Core subnet outputs (always present)
+output webSubnetResourceId string = contains(map(vnetSubnets, subnet => subnet.name), 'web') ? virtualNetwork.outputs.subnetResourceIds[indexOf(map(vnetSubnets, subnet => subnet.name), 'web')] : ''
+output pepsSubnetResourceId string = contains(map(vnetSubnets, subnet => subnet.name), 'peps') ? virtualNetwork.outputs.subnetResourceIds[indexOf(map(vnetSubnets, subnet => subnet.name), 'peps')] : ''
+output aciSubnetResourceId string = contains(map(vnetSubnets, subnet => subnet.name), 'aci') ? virtualNetwork.outputs.subnetResourceIds[indexOf(map(vnetSubnets, subnet => subnet.name), 'aci')] : ''
+
+// Optional bastion/jumpbox subnet outputs (only present when deployBastionAndJumpbox is true)
+output bastionSubnetResourceId string = deployBastionAndJumpbox && contains(map(vnetSubnets, subnet => subnet.name), 'AzureBastionSubnet') ? virtualNetwork.outputs.subnetResourceIds[indexOf(map(vnetSubnets, subnet => subnet.name), 'AzureBastionSubnet')] : ''
+output jumpboxSubnetResourceId string = deployBastionAndJumpbox && contains(map(vnetSubnets, subnet => subnet.name), 'jumpbox') ? virtualNetwork.outputs.subnetResourceIds[indexOf(map(vnetSubnets, subnet => subnet.name), 'jumpbox')] : ''
diff --git a/content-gen/infra/modules/web-sites.bicep b/content-gen/infra/modules/web-sites.bicep
new file mode 100644
index 000000000..9f78af838
--- /dev/null
+++ b/content-gen/infra/modules/web-sites.bicep
@@ -0,0 +1,367 @@
+@description('Required. Name of the site.')
+param name string
+
+@description('Optional. Location for all Resources.')
+param location string = resourceGroup().location
+
+@description('Required. Type of site to deploy.')
+@allowed([
+ 'functionapp'
+ 'functionapp,linux'
+ 'functionapp,workflowapp'
+ 'functionapp,workflowapp,linux'
+ 'functionapp,linux,container'
+ 'functionapp,linux,container,azurecontainerapps'
+ 'app,linux'
+ 'app'
+ 'linux,api'
+ 'api'
+ 'app,linux,container'
+ 'app,container,windows'
+])
+param kind string
+
+@description('Required. The resource ID of the app service plan to use for the site.')
+param serverFarmResourceId string
+
+@description('Optional. Azure Resource Manager ID of the customers selected Managed Environment on which to host this app.')
+param managedEnvironmentId string?
+
+@description('Optional. Configures a site to accept only HTTPS requests.')
+param httpsOnly bool = true
+
+@description('Optional. If client affinity is enabled.')
+param clientAffinityEnabled bool = true
+
+@description('Optional. The resource ID of the app service environment to use for this resource.')
+param appServiceEnvironmentResourceId string?
+
+import { managedIdentityAllType } from 'br/public:avm/utl/types/avm-common-types:0.5.1'
+@description('Optional. The managed identity definition for this resource.')
+param managedIdentities managedIdentityAllType?
+
+@description('Optional. The resource ID of the assigned identity to be used to access a key vault with.')
+param keyVaultAccessIdentityResourceId string?
+
+@description('Optional. Checks if Customer provided storage account is required.')
+param storageAccountRequired bool = false
+
+@description('Optional. Enable monitoring and logging configuration.')
+param enableMonitoring bool = false
+
+@description('Optional. Azure Resource Manager ID of the Virtual network and subnet to be joined by Regional VNET Integration.')
+param virtualNetworkSubnetId string?
+
+@description('Optional. To enable accessing content over virtual network.')
+param vnetContentShareEnabled bool = false
+
+@description('Optional. To enable pulling image over Virtual Network.')
+param vnetImagePullEnabled bool = false
+
+@description('Optional. Virtual Network Route All enabled.')
+param vnetRouteAllEnabled bool = false
+
+@description('Optional. Stop SCM (KUDU) site when the app is stopped.')
+param scmSiteAlsoStopped bool = false
+
+@description('Optional. The site config object.')
+param siteConfig resourceInput<'Microsoft.Web/sites@2024-04-01'>.properties.siteConfig = {
+ alwaysOn: true
+ minTlsVersion: '1.2'
+ ftpsState: 'FtpsOnly'
+}
+
+@description('Optional. The web site config.')
+param configs appSettingsConfigType[]?
+
+@description('Optional. The Function App configuration object.')
+param functionAppConfig resourceInput<'Microsoft.Web/sites@2024-04-01'>.properties.functionAppConfig?
+
+import { privateEndpointSingleServiceType } from 'br/public:avm/utl/types/avm-common-types:0.5.1'
+@description('Optional. Configuration details for private endpoints.')
+param privateEndpoints privateEndpointSingleServiceType[]?
+
+@description('Optional. Tags of the resource.')
+param tags object?
+
+import { diagnosticSettingFullType } from 'br/public:avm/utl/types/avm-common-types:0.5.1'
+@description('Optional. The diagnostic settings of the service.')
+param diagnosticSettings diagnosticSettingFullType[]?
+
+@description('Optional. To enable client certificate authentication (TLS mutual authentication).')
+param clientCertEnabled bool = false
+
+@description('Optional. Client certificate authentication comma-separated exclusion paths.')
+param clientCertExclusionPaths string?
+
+@description('Optional. Client certificate mode.')
+@allowed([
+ 'Optional'
+ 'OptionalInteractiveUser'
+ 'Required'
+])
+param clientCertMode string = 'Optional'
+
+@description('Optional. If specified during app creation, the app is cloned from a source app.')
+param cloningInfo resourceInput<'Microsoft.Web/sites@2024-04-01'>.properties.cloningInfo?
+
+@description('Optional. Size of the function container.')
+param containerSize int?
+
+@description('Optional. Maximum allowed daily memory-time quota (applicable on dynamic apps only).')
+param dailyMemoryTimeQuota int?
+
+@description('Optional. Setting this value to false disables the app (takes the app offline).')
+param enabled bool = true
+
+@description('Optional. Hostname SSL states are used to manage the SSL bindings for app\'s hostnames.')
+param hostNameSslStates resourceInput<'Microsoft.Web/sites@2024-04-01'>.properties.hostNameSslStates?
+
+@description('Optional. Hyper-V sandbox.')
+param hyperV bool = false
+
+@description('Optional. Site redundancy mode.')
+@allowed([
+ 'ActiveActive'
+ 'Failover'
+ 'GeoRedundant'
+ 'Manual'
+ 'None'
+])
+param redundancyMode string = 'None'
+
+@description('Optional. Whether or not public network access is allowed for this resource.')
+@allowed([
+ 'Enabled'
+ 'Disabled'
+])
+param publicNetworkAccess string?
+
+@description('Optional. End to End Encryption Setting.')
+param e2eEncryptionEnabled bool?
+
+@description('Optional. Property to configure various DNS related settings for a site.')
+param dnsConfiguration resourceInput<'Microsoft.Web/sites@2024-04-01'>.properties.dnsConfiguration?
+
+@description('Optional. Specifies the scope of uniqueness for the default hostname during resource creation.')
+@allowed([
+ 'NoReuse'
+ 'ResourceGroupReuse'
+ 'SubscriptionReuse'
+ 'TenantReuse'
+])
+param autoGeneratedDomainNameLabelScope string?
+
+var formattedUserAssignedIdentities = reduce(
+ map((managedIdentities.?userAssignedResourceIds ?? []), (id) => { '${id}': {} }),
+ {},
+ (cur, next) => union(cur, next)
+)
+
+var identity = !empty(managedIdentities)
+ ? {
+ type: (managedIdentities.?systemAssigned ?? false)
+ ? (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'SystemAssigned, UserAssigned' : 'SystemAssigned')
+ : (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'UserAssigned' : 'None')
+ userAssignedIdentities: !empty(formattedUserAssignedIdentities) ? formattedUserAssignedIdentities : null
+ }
+ : null
+
+resource app 'Microsoft.Web/sites@2024-04-01' = {
+ name: name
+ location: location
+ kind: kind
+ tags: tags
+ identity: identity
+ properties: {
+ managedEnvironmentId: !empty(managedEnvironmentId) ? managedEnvironmentId : null
+ serverFarmId: serverFarmResourceId
+ clientAffinityEnabled: clientAffinityEnabled
+ httpsOnly: httpsOnly
+ hostingEnvironmentProfile: !empty(appServiceEnvironmentResourceId)
+ ? {
+ id: appServiceEnvironmentResourceId
+ }
+ : null
+ storageAccountRequired: storageAccountRequired
+ keyVaultReferenceIdentity: keyVaultAccessIdentityResourceId
+ virtualNetworkSubnetId: virtualNetworkSubnetId
+ siteConfig: siteConfig
+ functionAppConfig: functionAppConfig
+ clientCertEnabled: clientCertEnabled
+ clientCertExclusionPaths: clientCertExclusionPaths
+ clientCertMode: clientCertMode
+ cloningInfo: cloningInfo
+ containerSize: containerSize
+ dailyMemoryTimeQuota: dailyMemoryTimeQuota
+ enabled: enabled
+ hostNameSslStates: hostNameSslStates
+ hyperV: hyperV
+ redundancyMode: redundancyMode
+ publicNetworkAccess: !empty(publicNetworkAccess)
+ ? any(publicNetworkAccess)
+ : (!empty(privateEndpoints) ? 'Disabled' : 'Enabled')
+ vnetContentShareEnabled: vnetContentShareEnabled
+ vnetImagePullEnabled: vnetImagePullEnabled
+ vnetRouteAllEnabled: vnetRouteAllEnabled
+ scmSiteAlsoStopped: scmSiteAlsoStopped
+ endToEndEncryptionEnabled: e2eEncryptionEnabled
+ dnsConfiguration: dnsConfiguration
+ autoGeneratedDomainNameLabelScope: autoGeneratedDomainNameLabelScope
+ }
+}
+
+module app_config 'web-sites.config.bicep' = [
+ for (config, index) in (configs ?? []): {
+ name: '${uniqueString(deployment().name, location)}-Site-Config-${index}'
+ params: {
+ appName: app.name
+ name: config.name
+ applicationInsightResourceId: config.?applicationInsightResourceId
+ storageAccountResourceId: config.?storageAccountResourceId
+ storageAccountUseIdentityAuthentication: config.?storageAccountUseIdentityAuthentication
+ properties: config.?properties
+ currentAppSettings: config.?retainCurrentAppSettings ?? true && config.name == 'appsettings'
+ ? list('${app.id}/config/appsettings', '2023-12-01').properties
+ : {}
+ enableMonitoring: enableMonitoring
+ }
+ }
+]
+
+#disable-next-line use-recent-api-versions
+resource app_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [
+ for (diagnosticSetting, index) in (diagnosticSettings ?? []): {
+ name: diagnosticSetting.?name ?? '${name}-diagnosticSettings'
+ properties: {
+ storageAccountId: diagnosticSetting.?storageAccountResourceId
+ workspaceId: diagnosticSetting.?workspaceResourceId
+ eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId
+ eventHubName: diagnosticSetting.?eventHubName
+ metrics: [
+ for group in (diagnosticSetting.?metricCategories ?? [{ category: 'AllMetrics' }]): {
+ category: group.category
+ enabled: group.?enabled ?? true
+ timeGrain: null
+ }
+ ]
+ logs: [
+ for group in (diagnosticSetting.?logCategoriesAndGroups ?? [{ categoryGroup: 'allLogs' }]): {
+ categoryGroup: group.?categoryGroup
+ category: group.?category
+ enabled: group.?enabled ?? true
+ }
+ ]
+ marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId
+ logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType
+ }
+ scope: app
+ }
+]
+
+module app_privateEndpoints 'br/public:avm/res/network/private-endpoint:0.11.0' = [
+ for (privateEndpoint, index) in (privateEndpoints ?? []): {
+ name: '${uniqueString(deployment().name, location)}-app-PrivateEndpoint-${index}'
+ scope: resourceGroup(
+ split(privateEndpoint.?resourceGroupResourceId ?? resourceGroup().id, '/')[2],
+ split(privateEndpoint.?resourceGroupResourceId ?? resourceGroup().id, '/')[4]
+ )
+ params: {
+ name: privateEndpoint.?name ?? 'pep-${last(split(app.id, '/'))}-${privateEndpoint.?service ?? 'sites'}-${index}'
+ privateLinkServiceConnections: privateEndpoint.?isManualConnection != true
+ ? [
+ {
+ name: privateEndpoint.?privateLinkServiceConnectionName ?? '${last(split(app.id, '/'))}-${privateEndpoint.?service ?? 'sites'}-${index}'
+ properties: {
+ privateLinkServiceId: app.id
+ groupIds: [
+ privateEndpoint.?service ?? 'sites'
+ ]
+ }
+ }
+ ]
+ : null
+ manualPrivateLinkServiceConnections: privateEndpoint.?isManualConnection == true
+ ? [
+ {
+ name: privateEndpoint.?privateLinkServiceConnectionName ?? '${last(split(app.id, '/'))}-${privateEndpoint.?service ?? 'sites'}-${index}'
+ properties: {
+ privateLinkServiceId: app.id
+ groupIds: [
+ privateEndpoint.?service ?? 'sites'
+ ]
+ requestMessage: privateEndpoint.?manualConnectionRequestMessage ?? 'Manual approval required.'
+ }
+ }
+ ]
+ : null
+ subnetResourceId: privateEndpoint.subnetResourceId
+ enableTelemetry: false
+ location: privateEndpoint.?location ?? reference(
+ split(privateEndpoint.subnetResourceId, '/subnets/')[0],
+ '2020-06-01',
+ 'Full'
+ ).location
+ lock: privateEndpoint.?lock ?? null
+ privateDnsZoneGroup: privateEndpoint.?privateDnsZoneGroup
+ roleAssignments: privateEndpoint.?roleAssignments
+ tags: privateEndpoint.?tags ?? tags
+ customDnsConfigs: privateEndpoint.?customDnsConfigs
+ ipConfigurations: privateEndpoint.?ipConfigurations
+ applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds
+ customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName
+ }
+ }
+]
+
+@description('The name of the site.')
+output name string = app.name
+
+@description('The resource ID of the site.')
+output resourceId string = app.id
+
+@description('The resource group the site was deployed into.')
+output resourceGroupName string = resourceGroup().name
+
+@description('The principal ID of the system assigned identity.')
+output systemAssignedMIPrincipalId string? = app.?identity.?principalId
+
+@description('The location the resource was deployed into.')
+output location string = app.location
+
+@description('Default hostname of the app.')
+output defaultHostname string = 'https://${name}.azurewebsites.net'
+
+@description('Unique identifier that verifies the custom domains assigned to the app.')
+output customDomainVerificationId string = app.properties.customDomainVerificationId
+
+@description('The outbound IP addresses of the app.')
+output outboundIpAddresses string = app.properties.outboundIpAddresses
+
+// ================ //
+// Definitions //
+// ================ //
+@export()
+@description('The type of an app settings configuration.')
+type appSettingsConfigType = {
+ @description('Required. The type of config.')
+ name: 'appsettings' | 'logs'
+
+ @description('Optional. If the provided storage account requires Identity based authentication.')
+ storageAccountUseIdentityAuthentication: bool?
+
+ @description('Optional. Required if app of kind functionapp. Resource ID of the storage account to manage triggers and logging function executions.')
+ storageAccountResourceId: string?
+
+ @description('Optional. Resource ID of the application insight to leverage for this resource.')
+ applicationInsightResourceId: string?
+
+ @description('Optional. The retain the current app settings. Defaults to true.')
+ retainCurrentAppSettings: bool?
+
+ @description('Optional. The app settings key-value pairs.')
+ properties: {
+ @description('Required. An app settings key-value pair.')
+ *: string
+ }?
+}
diff --git a/content-gen/infra/modules/web-sites.config.bicep b/content-gen/infra/modules/web-sites.config.bicep
new file mode 100644
index 000000000..34ff9e4c9
--- /dev/null
+++ b/content-gen/infra/modules/web-sites.config.bicep
@@ -0,0 +1,122 @@
+metadata name = 'Site App Settings'
+metadata description = 'This module deploys a Site App Setting.'
+
+@description('Conditional. The name of the parent site resource. Required if the template is used in a standalone deployment.')
+param appName string
+
+@description('Required. The name of the config.')
+@allowed([
+ 'appsettings'
+ 'authsettings'
+ 'authsettingsV2'
+ 'azurestorageaccounts'
+ 'backup'
+ 'connectionstrings'
+ 'logs'
+ 'metadata'
+ 'pushsettings'
+ 'slotConfigNames'
+ 'web'
+])
+param name string
+
+@description('Optional. The properties of the config.')
+param properties object = {}
+
+@description('Optional. If the provided storage account requires Identity based authentication.')
+param storageAccountUseIdentityAuthentication bool = false
+
+@description('Optional. Required if app of kind functionapp. Resource ID of the storage account.')
+param storageAccountResourceId string?
+
+@description('Optional. Resource ID of the application insight to leverage for this resource.')
+param applicationInsightResourceId string?
+
+@description('Optional. The current app settings.')
+param currentAppSettings {
+ @description('Required. The key-values pairs of the current app settings.')
+ *: string
+} = {}
+
+@description('Optional. Enable monitoring and logging configuration.')
+param enableMonitoring bool = false
+
+var azureWebJobsValues = !empty(storageAccountResourceId) && !storageAccountUseIdentityAuthentication
+ ? {
+ AzureWebJobsStorage: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount!.listKeys().keys[0].value};EndpointSuffix=${environment().suffixes.storage}'
+ }
+ : !empty(storageAccountResourceId) && storageAccountUseIdentityAuthentication
+ ? {
+ AzureWebJobsStorage__accountName: storageAccount.name
+ AzureWebJobsStorage__blobServiceUri: storageAccount!.properties.primaryEndpoints.blob
+ AzureWebJobsStorage__queueServiceUri: storageAccount!.properties.primaryEndpoints.queue
+ AzureWebJobsStorage__tableServiceUri: storageAccount!.properties.primaryEndpoints.table
+ }
+ : {}
+
+var appInsightsValues = !empty(applicationInsightResourceId)
+ ? {
+ APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights!.properties.ConnectionString
+ }
+ : {}
+
+var loggingProperties = enableMonitoring && name == 'logs'
+ ? {
+ applicationLogs: {
+ fileSystem: {
+ level: 'Verbose'
+ }
+ }
+ httpLogs: {
+ fileSystem: {
+ enabled: true
+ retentionInDays: 3
+ retentionInMb: 100
+ }
+ }
+ detailedErrorMessages: {
+ enabled: true
+ }
+ failedRequestsTracing: {
+ enabled: true
+ }
+ }
+ : {}
+
+var expandedProperties = union(
+ properties,
+ currentAppSettings,
+ azureWebJobsValues,
+ appInsightsValues,
+ loggingProperties
+)
+
+resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightResourceId)) {
+ name: last(split(applicationInsightResourceId!, '/'))
+ scope: resourceGroup(split(applicationInsightResourceId!, '/')[2], split(applicationInsightResourceId!, '/')[4])
+}
+
+resource storageAccount 'Microsoft.Storage/storageAccounts@2024-01-01' existing = if (!empty(storageAccountResourceId)) {
+ name: last(split(storageAccountResourceId!, '/'))
+ scope: resourceGroup(split(storageAccountResourceId!, '/')[2], split(storageAccountResourceId!, '/')[4])
+}
+
+resource app 'Microsoft.Web/sites@2023-12-01' existing = {
+ name: appName
+}
+
+resource config 'Microsoft.Web/sites/config@2024-04-01' = {
+ parent: app
+ #disable-next-line BCP225
+ name: name
+ properties: expandedProperties
+}
+
+@description('The name of the site config.')
+output name string = config.name
+
+@description('The resource ID of the site config.')
+output resourceId string = config.id
+
+@description('The resource group the site config was deployed into.')
+output resourceGroupName string = resourceGroup().name
diff --git a/content-gen/infra/scripts/assign_aci_roles.sh b/content-gen/infra/scripts/assign_aci_roles.sh
new file mode 100755
index 000000000..bf3f6dfec
--- /dev/null
+++ b/content-gen/infra/scripts/assign_aci_roles.sh
@@ -0,0 +1,351 @@
+#!/bin/bash
+#
+# assign_aci_roles.sh
+# Assigns required role assignments to the ACI managed identity for:
+# - Cosmos DB (Built-in Data Contributor)
+# - Storage Account (Storage Blob Data Contributor)
+# - AI Search (Search Index Data Contributor)
+# - Azure OpenAI (Cognitive Services OpenAI Contributor) - optional external resource
+#
+
+set -e
+
+# Color output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+print_status() {
+ echo -e "${GREEN}[✓]${NC} $1"
+}
+
+print_warning() {
+ echo -e "${YELLOW}[!]${NC} $1"
+}
+
+print_error() {
+ echo -e "${RED}[✗]${NC} $1"
+}
+
+# Usage
+usage() {
+ echo "Usage: $0 -g -c [-o ] [-a ]"
+ echo ""
+ echo "Required parameters:"
+ echo " -g Resource group name containing the ACI and other resources"
+ echo " -c Container instance name"
+ echo ""
+ echo "Optional parameters:"
+ echo " -o Resource group containing Azure OpenAI resource (if external)"
+ echo " -a Azure OpenAI account name (if external)"
+ echo ""
+ echo "Example:"
+ echo " $0 -g rg-contentgen-jahunte -c content-gen-app"
+ echo " $0 -g rg-contentgen-jahunte -c content-gen-app -o rg-dev -a ai-account-254ly3n5ky7vw"
+ exit 1
+}
+
+# Parse arguments
+while getopts "g:c:o:a:h" opt; do
+ case $opt in
+ g) RESOURCE_GROUP="$OPTARG" ;;
+ c) CONTAINER_NAME="$OPTARG" ;;
+ o) OPENAI_RESOURCE_GROUP="$OPTARG" ;;
+ a) OPENAI_ACCOUNT_NAME="$OPTARG" ;;
+ h) usage ;;
+ *) usage ;;
+ esac
+done
+
+# Validate required parameters
+if [ -z "$RESOURCE_GROUP" ] || [ -z "$CONTAINER_NAME" ]; then
+ print_error "Resource group and container name are required"
+ usage
+fi
+
+echo "=================================================="
+echo "ACI Role Assignment Script"
+echo "=================================================="
+echo ""
+
+# Authenticate with Azure
+if az account show &> /dev/null; then
+ print_status "Already authenticated with Azure"
+else
+ print_warning "Not authenticated. Attempting to login..."
+ az login
+fi
+
+# Get ACI managed identity principal ID
+echo ""
+echo "Getting ACI managed identity..."
+ACI_PRINCIPAL_ID=$(az container show \
+ --resource-group "$RESOURCE_GROUP" \
+ --name "$CONTAINER_NAME" \
+ --query "identity.principalId" -o tsv 2>/dev/null)
+
+if [ -z "$ACI_PRINCIPAL_ID" ]; then
+ print_error "Could not get principal ID for container '$CONTAINER_NAME'"
+ print_error "Make sure the container exists and has a system-assigned managed identity"
+ exit 1
+fi
+
+print_status "ACI Principal ID: $ACI_PRINCIPAL_ID"
+
+# ========================================
+# 1. Cosmos DB Role Assignment
+# ========================================
+echo ""
+echo "------------------------------------------"
+echo "1. Cosmos DB Role Assignment"
+echo "------------------------------------------"
+
+# Find Cosmos DB account in the resource group
+COSMOS_ACCOUNT=$(az cosmosdb list \
+ --resource-group "$RESOURCE_GROUP" \
+ --query "[0].name" -o tsv 2>/dev/null)
+
+if [ -z "$COSMOS_ACCOUNT" ]; then
+ print_warning "No Cosmos DB account found in resource group '$RESOURCE_GROUP'"
+else
+ print_status "Found Cosmos DB account: $COSMOS_ACCOUNT"
+
+ # Check if role already exists
+ COSMOS_ROLE_EXISTS=$(az cosmosdb sql role assignment list \
+ --resource-group "$RESOURCE_GROUP" \
+ --account-name "$COSMOS_ACCOUNT" \
+ --query "[?roleDefinitionId.ends_with(@, '00000000-0000-0000-0000-000000000002') && principalId == '$ACI_PRINCIPAL_ID']" -o tsv 2>/dev/null)
+
+ if [ -n "$COSMOS_ROLE_EXISTS" ]; then
+ print_status "ACI already has Cosmos DB Built-in Data Contributor role"
+ else
+ echo "Assigning Cosmos DB Built-in Data Contributor role..."
+ MSYS_NO_PATHCONV=1 az cosmosdb sql role assignment create \
+ --resource-group "$RESOURCE_GROUP" \
+ --account-name "$COSMOS_ACCOUNT" \
+ --role-definition-id "00000000-0000-0000-0000-000000000002" \
+ --principal-id "$ACI_PRINCIPAL_ID" \
+ --scope "/" \
+ --output none 2>/dev/null
+
+ if [ $? -eq 0 ]; then
+ print_status "Cosmos DB Built-in Data Contributor role assigned"
+ else
+ print_error "Failed to assign Cosmos DB role"
+ fi
+ fi
+fi
+
+# ========================================
+# 2. Storage Account Role Assignment
+# ========================================
+echo ""
+echo "------------------------------------------"
+echo "2. Storage Account Role Assignment"
+echo "------------------------------------------"
+
+# Find storage account in the resource group
+STORAGE_ACCOUNT=$(az storage account list \
+ --resource-group "$RESOURCE_GROUP" \
+ --query "[0].name" -o tsv 2>/dev/null)
+
+if [ -z "$STORAGE_ACCOUNT" ]; then
+ print_warning "No Storage account found in resource group '$RESOURCE_GROUP'"
+else
+ print_status "Found Storage account: $STORAGE_ACCOUNT"
+
+ STORAGE_ID=$(az storage account show \
+ --resource-group "$RESOURCE_GROUP" \
+ --name "$STORAGE_ACCOUNT" \
+ --query "id" -o tsv)
+
+ # Storage Blob Data Contributor role ID
+ STORAGE_ROLE_ID="ba92f5b4-2d11-453d-a403-e96b0029c9fe"
+
+ # Check if role already exists
+ STORAGE_ROLE_EXISTS=$(az role assignment list \
+ --assignee "$ACI_PRINCIPAL_ID" \
+ --role "$STORAGE_ROLE_ID" \
+ --scope "$STORAGE_ID" \
+ --query "[0].id" -o tsv 2>/dev/null)
+
+ if [ -n "$STORAGE_ROLE_EXISTS" ]; then
+ print_status "ACI already has Storage Blob Data Contributor role"
+ else
+ echo "Assigning Storage Blob Data Contributor role..."
+ az role assignment create \
+ --assignee-object-id "$ACI_PRINCIPAL_ID" \
+ --assignee-principal-type "ServicePrincipal" \
+ --role "$STORAGE_ROLE_ID" \
+ --scope "$STORAGE_ID" \
+ --output none 2>/dev/null
+
+ if [ $? -eq 0 ]; then
+ print_status "Storage Blob Data Contributor role assigned"
+ else
+ print_error "Failed to assign Storage role"
+ fi
+ fi
+fi
+
+# ========================================
+# 3. AI Search Role Assignment
+# ========================================
+echo ""
+echo "------------------------------------------"
+echo "3. AI Search Role Assignment"
+echo "------------------------------------------"
+
+# Find AI Search service in the resource group
+SEARCH_SERVICE=$(az search service list \
+ --resource-group "$RESOURCE_GROUP" \
+ --query "[0].name" -o tsv 2>/dev/null)
+
+if [ -z "$SEARCH_SERVICE" ]; then
+ print_warning "No AI Search service found in resource group '$RESOURCE_GROUP'"
+else
+ print_status "Found AI Search service: $SEARCH_SERVICE"
+
+ SEARCH_ID=$(az search service show \
+ --resource-group "$RESOURCE_GROUP" \
+ --name "$SEARCH_SERVICE" \
+ --query "id" -o tsv)
+
+ # Search Index Data Contributor role ID
+ SEARCH_ROLE_ID="8ebe5a00-799e-43f5-93ac-243d3dce84a7"
+
+ # Check if role already exists
+ SEARCH_ROLE_EXISTS=$(az role assignment list \
+ --assignee "$ACI_PRINCIPAL_ID" \
+ --role "$SEARCH_ROLE_ID" \
+ --scope "$SEARCH_ID" \
+ --query "[0].id" -o tsv 2>/dev/null)
+
+ if [ -n "$SEARCH_ROLE_EXISTS" ]; then
+ print_status "ACI already has Search Index Data Contributor role"
+ else
+ echo "Assigning Search Index Data Contributor role..."
+ az role assignment create \
+ --assignee-object-id "$ACI_PRINCIPAL_ID" \
+ --assignee-principal-type "ServicePrincipal" \
+ --role "$SEARCH_ROLE_ID" \
+ --scope "$SEARCH_ID" \
+ --output none 2>/dev/null
+
+ if [ $? -eq 0 ]; then
+ print_status "Search Index Data Contributor role assigned"
+ else
+ print_error "Failed to assign Search role"
+ fi
+ fi
+fi
+
+# ========================================
+# 4. Azure OpenAI Role Assignment (Optional)
+# ========================================
+echo ""
+echo "------------------------------------------"
+echo "4. Azure OpenAI Role Assignment"
+echo "------------------------------------------"
+
+if [ -n "$OPENAI_RESOURCE_GROUP" ] && [ -n "$OPENAI_ACCOUNT_NAME" ]; then
+ print_status "Using external OpenAI resource: $OPENAI_ACCOUNT_NAME in $OPENAI_RESOURCE_GROUP"
+
+ OPENAI_ID=$(az cognitiveservices account show \
+ --resource-group "$OPENAI_RESOURCE_GROUP" \
+ --name "$OPENAI_ACCOUNT_NAME" \
+ --query "id" -o tsv 2>/dev/null)
+
+ if [ -z "$OPENAI_ID" ]; then
+ print_error "Could not find OpenAI resource '$OPENAI_ACCOUNT_NAME' in '$OPENAI_RESOURCE_GROUP'"
+ else
+ # Cognitive Services OpenAI Contributor role ID
+ OPENAI_ROLE_ID="a001fd3d-188f-4b5d-821b-7da978bf7442"
+
+ # Check if role already exists
+ OPENAI_ROLE_EXISTS=$(az role assignment list \
+ --assignee "$ACI_PRINCIPAL_ID" \
+ --role "$OPENAI_ROLE_ID" \
+ --scope "$OPENAI_ID" \
+ --query "[0].id" -o tsv 2>/dev/null)
+
+ if [ -n "$OPENAI_ROLE_EXISTS" ]; then
+ print_status "ACI already has Cognitive Services OpenAI Contributor role"
+ else
+ echo "Assigning Cognitive Services OpenAI Contributor role..."
+ az role assignment create \
+ --assignee-object-id "$ACI_PRINCIPAL_ID" \
+ --assignee-principal-type "ServicePrincipal" \
+ --role "$OPENAI_ROLE_ID" \
+ --scope "$OPENAI_ID" \
+ --output none 2>/dev/null
+
+ if [ $? -eq 0 ]; then
+ print_status "Cognitive Services OpenAI Contributor role assigned"
+ else
+ print_error "Failed to assign OpenAI role"
+ fi
+ fi
+ fi
+else
+ # Try to find OpenAI in same resource group
+ OPENAI_ACCOUNT=$(az cognitiveservices account list \
+ --resource-group "$RESOURCE_GROUP" \
+ --query "[?kind=='AIServices' || kind=='OpenAI'].name | [0]" -o tsv 2>/dev/null)
+
+ if [ -z "$OPENAI_ACCOUNT" ]; then
+ print_warning "No Azure OpenAI/AI Services found in resource group '$RESOURCE_GROUP'"
+ print_warning "Use -o and -a parameters to specify external OpenAI resource"
+ else
+ print_status "Found AI Services account: $OPENAI_ACCOUNT"
+
+ OPENAI_ID=$(az cognitiveservices account show \
+ --resource-group "$RESOURCE_GROUP" \
+ --name "$OPENAI_ACCOUNT" \
+ --query "id" -o tsv)
+
+ # Cognitive Services OpenAI Contributor role ID
+ OPENAI_ROLE_ID="a001fd3d-188f-4b5d-821b-7da978bf7442"
+
+ # Check if role already exists
+ OPENAI_ROLE_EXISTS=$(az role assignment list \
+ --assignee "$ACI_PRINCIPAL_ID" \
+ --role "$OPENAI_ROLE_ID" \
+ --scope "$OPENAI_ID" \
+ --query "[0].id" -o tsv 2>/dev/null)
+
+ if [ -n "$OPENAI_ROLE_EXISTS" ]; then
+ print_status "ACI already has Cognitive Services OpenAI Contributor role"
+ else
+ echo "Assigning Cognitive Services OpenAI Contributor role..."
+ az role assignment create \
+ --assignee-object-id "$ACI_PRINCIPAL_ID" \
+ --assignee-principal-type "ServicePrincipal" \
+ --role "$OPENAI_ROLE_ID" \
+ --scope "$OPENAI_ID" \
+ --output none 2>/dev/null
+
+ if [ $? -eq 0 ]; then
+ print_status "Cognitive Services OpenAI Contributor role assigned"
+ else
+ print_error "Failed to assign OpenAI role"
+ fi
+ fi
+ fi
+fi
+
+# ========================================
+# Summary
+# ========================================
+echo ""
+echo "=================================================="
+echo "Role Assignment Complete"
+echo "=================================================="
+echo ""
+echo "ACI Container: $CONTAINER_NAME"
+echo "Principal ID: $ACI_PRINCIPAL_ID"
+echo ""
+echo "To verify assignments, run:"
+echo " az role assignment list --assignee $ACI_PRINCIPAL_ID --all -o table"
+echo ""
diff --git a/infra/vscode_web/.env b/content-gen/infra/vscode_web/.env
similarity index 100%
rename from infra/vscode_web/.env
rename to content-gen/infra/vscode_web/.env
diff --git a/content-gen/infra/vscode_web/.gitignore b/content-gen/infra/vscode_web/.gitignore
new file mode 100644
index 000000000..23de01ef5
--- /dev/null
+++ b/content-gen/infra/vscode_web/.gitignore
@@ -0,0 +1,85 @@
+# ========== .NET ========== #
+## Build results
+bin/
+obj/
+[Bb]uild/
+[Ll]ogs/
+*.log
+## User-specific files
+*.user
+*.suo
+*.userosscache
+*.sln.docstates
+*.vsp
+*.vspx
+*.vspscc
+## Rider / VS Code / Visual Studio
+.idea/
+.vscode/
+.vs/
+## NuGet packages
+*.nupkg
+packages/
+*.snupkg
+project.lock.json
+project.assets.json
+## Dotnet tools
+.tools/
+# ========== Java ========== #
+## Compiled class files
+*.class
+## Logs
+*.log
+## Maven
+target/
+## Gradle
+.gradle/
+build/
+## Eclipse
+.project
+.classpath
+.settings/
+.loadpath
+## IntelliJ IDEA
+*.iml
+*.ipr
+*.iws
+out/
+.idea/
+# ========== Python ========== #
+## Byte-compiled / cache
+__pycache__/
+*.py[cod]
+*$py.class
+## Virtual environment
+env/
+venv/
+ENV/
+.venv/
+.env*
+## PyInstaller
+*.spec
+dist/
+build/
+## Jupyter Notebook
+.ipynb_checkpoints/
+## Misc
+*.log
+*.pot
+*.pyc
+.DS_Store
+*.sqlite3
+# ========== General ========== #
+## OS generated
+Thumbs.db
+ehthumbs.db
+Desktop.ini
+.DS_Store
+*.swp
+*.swo
+*.bak
+*.tmp
+*.old
+## Node (just in case mixed project)
+node_modules/
+# End
\ No newline at end of file
diff --git a/content-gen/infra/vscode_web/LICENSE b/content-gen/infra/vscode_web/LICENSE
new file mode 100644
index 000000000..22aed37e6
--- /dev/null
+++ b/content-gen/infra/vscode_web/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) Microsoft Corporation.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/content-gen/infra/vscode_web/README-noazd.md b/content-gen/infra/vscode_web/README-noazd.md
new file mode 100644
index 000000000..1436b6150
--- /dev/null
+++ b/content-gen/infra/vscode_web/README-noazd.md
@@ -0,0 +1,2 @@
+# VS Code for the Web - Azure AI Foundry Templates
+
diff --git a/content-gen/infra/vscode_web/README.md b/content-gen/infra/vscode_web/README.md
new file mode 100644
index 000000000..6ce5aedfa
--- /dev/null
+++ b/content-gen/infra/vscode_web/README.md
@@ -0,0 +1,43 @@
+# VS Code for the Web - Azure AI Foundry Templates
+
+We've generated a simple development environment for you to deploy the templates.
+
+The Azure AI Foundry extension provides tools to help you build, test, and deploy AI models and AI Applications directly from VS Code. It offers simplified operations for interacting with your models, agents, and threads without leaving your development environment. Click on the Azure AI Foundry Icon on the left to see more.
+
+Follow the instructions below to get started!
+
+You should see a terminal opened with the template code already cloned.
+
+## Deploy the template
+
+You can provision and deploy this template using:
+
+```bash
+azd up
+```
+
+Follow any instructions from the deployment script and launch the application.
+
+
+If you need to delete the deployment and stop incurring any charges, run:
+
+```bash
+azd down
+```
+
+## Continuing on your local desktop
+
+You can keep working locally on VS Code Desktop by clicking "Continue On Desktop..." at the bottom left of this screen. Be sure to take the .env file with you using these steps:
+
+- Right-click the .env file
+- Select "Download"
+- Move the file from your Downloads folder to the local git repo directory
+- For Windows, you will need to rename the file back to .env using right-click "Rename..."
+
+## More examples
+
+Check out [Azure AI Projects client library for Python](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/README.md) for more information on using this SDK.
+
+## Troubleshooting
+
+- If you are instantiating your client via endpoint on an Azure AI Foundry project, ensure the endpoint is set in the `.env` as https://{your-foundry-resource-name}.services.ai.azure.com/api/projects/{your-foundry-project-name}`
\ No newline at end of file
diff --git a/content-gen/infra/vscode_web/codeSample.py b/content-gen/infra/vscode_web/codeSample.py
new file mode 100644
index 000000000..2ad2d0413
--- /dev/null
+++ b/content-gen/infra/vscode_web/codeSample.py
@@ -0,0 +1,25 @@
+from azure.ai.projects import AIProjectClient
+from azure.identity import DefaultAzureCredential
+
+project_client = AIProjectClient.from_connection_string(
+ credential=DefaultAzureCredential(),
+ conn_str="<%= connectionString %>")
+
+agent = project_client.agents.get_agent("<%= agentId %>")
+
+thread = project_client.agents.create_thread()
+print(f"Created thread, ID: {thread.id}")
+
+message = project_client.agents.create_message(
+ thread_id=thread.id,
+ role="user",
+ content="<%= userMessage %>"
+)
+
+run = project_client.agents.create_and_process_run(
+ thread_id=thread.id,
+ agent_id=agent.id)
+messages = project_client.agents.list_messages(thread_id=thread.id)
+
+for text_message in messages.text_messages:
+ print(text_message.as_dict())
\ No newline at end of file
diff --git a/content-gen/infra/vscode_web/endpoint-requirements.txt b/content-gen/infra/vscode_web/endpoint-requirements.txt
new file mode 100644
index 000000000..18d6803e8
--- /dev/null
+++ b/content-gen/infra/vscode_web/endpoint-requirements.txt
@@ -0,0 +1,3 @@
+azure-ai-projects==1.0.0b12
+azure-identity==1.20.0
+ansible-core~=2.17.0
\ No newline at end of file
diff --git a/content-gen/infra/vscode_web/endpointCodeSample.py b/content-gen/infra/vscode_web/endpointCodeSample.py
new file mode 100644
index 000000000..21452478a
--- /dev/null
+++ b/content-gen/infra/vscode_web/endpointCodeSample.py
@@ -0,0 +1,31 @@
+from azure.ai.projects import AIProjectClient
+from azure.identity import DefaultAzureCredential
+from azure.ai.agents.models import ListSortOrder
+
+project = AIProjectClient(
+ credential=DefaultAzureCredential(),
+ endpoint="<%= endpoint %>")
+
+agent = project.agents.get_agent("<%= agentId %>")
+
+thread = project.agents.threads.create()
+print(f"Created thread, ID: {thread.id}")
+
+message = project.agents.messages.create(
+ thread_id=thread.id,
+ role="user",
+ content="<%= userMessage %>"
+)
+
+run = project.agents.runs.create_and_process(
+ thread_id=thread.id,
+ agent_id=agent.id)
+
+if run.status == "failed":
+ print(f"Run failed: {run.last_error}")
+else:
+ messages = project.agents.messages.list(thread_id=thread.id, order=ListSortOrder.ASCENDING)
+
+ for message in messages:
+ if message.text_messages:
+ print(f"{message.role}: {message.text_messages[-1].text.value}")
\ No newline at end of file
diff --git a/content-gen/infra/vscode_web/index.json b/content-gen/infra/vscode_web/index.json
new file mode 100644
index 000000000..55157c9da
--- /dev/null
+++ b/content-gen/infra/vscode_web/index.json
@@ -0,0 +1,72 @@
+{
+ "ai-projects-sdk": {
+ "python": {
+ "default-azure-auth": {
+ "connectionString": [
+ {
+ "name": "run_agent.py",
+ "type": "code",
+ "path": "/codeSample.py"
+ },
+ {
+ "name": "INSTRUCTIONS.md",
+ "type": "readme",
+ "path": "/README-noazd.md"
+ },
+ {
+ "name": "requirements.txt",
+ "type": "dependencies",
+ "path": "/requirements.txt"
+ },
+ {
+ "name": ".env",
+ "type": "env",
+ "path": "/.env"
+ },
+ {
+ "name": "install.sh",
+ "type": "install",
+ "path": "/install.sh"
+ },
+ {
+ "name": ".gitignore",
+ "type": "code",
+ "path": "/.gitignore"
+ }
+ ],
+ "endpoint": [
+ {
+ "name": "run_agent.py",
+ "type": "code",
+ "path": "/endpointCodeSample.py"
+ },
+ {
+ "name": "INSTRUCTIONS.md",
+ "type": "readme",
+ "path": "/README.md"
+ },
+ {
+ "name": "requirements.txt",
+ "type": "dependencies",
+ "path": "/endpoint-requirements.txt"
+ },
+ {
+ "name": ".env",
+ "type": "env",
+ "path": "/.env"
+ },
+ {
+ "name": "install.sh",
+ "type": "install",
+ "path": "/install.sh"
+ },
+ {
+ "name": ".gitignore",
+ "type": "code",
+ "path": "/.gitignore"
+ }
+ ]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/content-gen/infra/vscode_web/install.sh b/content-gen/infra/vscode_web/install.sh
new file mode 100644
index 000000000..cdc70c5f2
--- /dev/null
+++ b/content-gen/infra/vscode_web/install.sh
@@ -0,0 +1,3 @@
+pip install -r requirements.txt --user -q
+
+azd init -t hunterjam/content-generation-solution-accelerator
\ No newline at end of file
diff --git a/content-gen/infra/vscode_web/requirements.txt b/content-gen/infra/vscode_web/requirements.txt
new file mode 100644
index 000000000..18d6803e8
--- /dev/null
+++ b/content-gen/infra/vscode_web/requirements.txt
@@ -0,0 +1,3 @@
+azure-ai-projects==1.0.0b12
+azure-identity==1.20.0
+ansible-core~=2.17.0
\ No newline at end of file
diff --git a/content-gen/scripts/create_image_search_index.py b/content-gen/scripts/create_image_search_index.py
new file mode 100644
index 000000000..c67a613e8
--- /dev/null
+++ b/content-gen/scripts/create_image_search_index.py
@@ -0,0 +1,612 @@
+"""
+Create Azure AI Search Index for Image Grounding.
+
+This script creates a search index for product images with:
+- Image metadata (name, description, colors, style)
+- Vector embeddings for semantic search
+- Blob storage integration for image retrieval
+
+Uses DefaultAzureCredential for authentication (RBAC).
+"""
+
+import asyncio
+import json
+import os
+import sys
+from pathlib import Path
+from typing import List, Dict, Any
+
+from azure.identity import DefaultAzureCredential
+from azure.search.documents import SearchClient
+from azure.search.documents.indexes import SearchIndexClient
+from azure.search.documents.indexes.models import (
+ SearchIndex,
+ SearchField,
+ SearchFieldDataType,
+ SimpleField,
+ SearchableField,
+ VectorSearch,
+ VectorSearchProfile,
+ HnswAlgorithmConfiguration,
+ SemanticConfiguration,
+ SemanticField,
+ SemanticPrioritizedFields,
+ SemanticSearch,
+)
+from azure.core.credentials import AzureKeyCredential
+from azure.storage.blob.aio import BlobServiceClient
+from dotenv import load_dotenv
+
+# Add src to path for imports
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
+
+# Load environment variables
+env_path = Path(__file__).parent.parent / ".env"
+load_dotenv(env_path)
+
+# Configuration
+SEARCH_ENDPOINT = os.getenv("AZURE_AI_SEARCH_ENDPOINT", "https://search-contentgen-jh.search.windows.net")
+SEARCH_INDEX_NAME = os.getenv("AZURE_AI_SEARCH_IMAGE_INDEX", "product-images")
+STORAGE_ACCOUNT_NAME = os.getenv("AZURE_BLOB_ACCOUNT_NAME", "storagecontentgenjh")
+CONTAINER_NAME = os.getenv("AZURE_BLOB_PRODUCT_IMAGES_CONTAINER", "product-images")
+
+AZURE_OPENAI_ENDPOINT = os.getenv("AZURE_OPENAI_ENDPOINT", "")
+AZURE_OPENAI_EMBEDDING_MODEL = os.getenv("AZURE_OPENAI_EMBEDDING_MODEL", "text-embedding-ada-002")
+AZURE_SEARCH_ADMIN_KEY = os.getenv("AZURE_AI_SEARCH_ADMIN_KEY", "")
+
+# Image metadata - derived from filenames with color/style info
+IMAGE_METADATA = {
+ "BlueAsh.jpg": {
+ "name": "BlueAsh",
+ "primary_color": "Blue",
+ "secondary_color": "Gray",
+ "color_family": "Cool",
+ "mood": "Calm, Professional",
+ "style": "Modern, Minimalist",
+ "description": "A sophisticated blue-ash tone with subtle gray undertones. Perfect for professional and calming aesthetics.",
+ "use_cases": "Corporate branding, tech products, wellness, professional services",
+ "keywords": ["blue", "ash", "gray", "calm", "professional", "modern", "cool tones"]
+ },
+ "CloudDrift.jpg": {
+ "name": "CloudDrift",
+ "primary_color": "White",
+ "secondary_color": "Light Gray",
+ "color_family": "Neutral",
+ "mood": "Ethereal, Peaceful",
+ "style": "Soft, Dreamy",
+ "description": "A soft, cloudy white with gentle drifting patterns. Evokes serenity and openness.",
+ "use_cases": "Spa, wellness, bedding, clean beauty, minimalist design",
+ "keywords": ["white", "cloud", "soft", "peaceful", "ethereal", "clean", "neutral"]
+ },
+ "FogHarbor.jpg": {
+ "name": "FogHarbor",
+ "primary_color": "Gray",
+ "secondary_color": "Blue-Gray",
+ "color_family": "Cool",
+ "mood": "Mysterious, Coastal",
+ "style": "Moody, Atmospheric",
+ "description": "A misty gray reminiscent of coastal fog rolling into harbor. Mysterious yet inviting.",
+ "use_cases": "Nautical themes, outdoor gear, photography, artisanal products",
+ "keywords": ["fog", "gray", "harbor", "coastal", "misty", "moody", "atmospheric"]
+ },
+ "GlacierTint.jpg": {
+ "name": "GlacierTint",
+ "primary_color": "Ice Blue",
+ "secondary_color": "White",
+ "color_family": "Cool",
+ "mood": "Fresh, Crisp",
+ "style": "Clean, Nordic",
+ "description": "A crisp ice-blue tint inspired by glacial formations. Fresh and invigorating.",
+ "use_cases": "Skincare, beverages, winter sports, Scandinavian design",
+ "keywords": ["glacier", "ice", "blue", "fresh", "crisp", "nordic", "winter"]
+ },
+ "GraphiteFade.jpg": {
+ "name": "GraphiteFade",
+ "primary_color": "Dark Gray",
+ "secondary_color": "Charcoal",
+ "color_family": "Dark",
+ "mood": "Sophisticated, Bold",
+ "style": "Industrial, Premium",
+ "description": "A deep graphite with gradient fade effect. Sophisticated and premium feel.",
+ "use_cases": "Luxury goods, electronics, automotive, high-end fashion",
+ "keywords": ["graphite", "dark", "gray", "sophisticated", "premium", "industrial", "bold"]
+ },
+ "ObsidianPearl.jpg": {
+ "name": "ObsidianPearl",
+ "primary_color": "Black",
+ "secondary_color": "Pearl White",
+ "color_family": "Contrast",
+ "mood": "Elegant, Luxurious",
+ "style": "High-contrast, Dramatic",
+ "description": "A striking contrast of deep obsidian black with pearlescent highlights. Ultimate elegance.",
+ "use_cases": "Jewelry, luxury accessories, evening wear, premium packaging",
+ "keywords": ["obsidian", "pearl", "black", "white", "elegant", "luxurious", "contrast"]
+ },
+ "OliveStone.jpg": {
+ "name": "OliveStone",
+ "primary_color": "Olive Green",
+ "secondary_color": "Brown",
+ "color_family": "Earth",
+ "mood": "Natural, Grounded",
+ "style": "Organic, Rustic",
+ "description": "An earthy olive green with stone-like undertones. Natural and grounded aesthetic.",
+ "use_cases": "Organic products, outdoor brands, home decor, sustainable fashion",
+ "keywords": ["olive", "green", "stone", "earth", "natural", "grounded", "organic"]
+ },
+ "PineShadow.jpg": {
+ "name": "PineShadow",
+ "primary_color": "Dark Green",
+ "secondary_color": "Forest Green",
+ "color_family": "Nature",
+ "mood": "Deep, Mysterious",
+ "style": "Natural, Rich",
+ "description": "A deep pine green with shadowy depth. Evokes dense forests and natural mystery.",
+ "use_cases": "Outdoor recreation, eco brands, luxury camping, artisan products",
+ "keywords": ["pine", "forest", "green", "shadow", "deep", "natural", "mysterious"]
+ },
+ "PorcelainMist.jpg": {
+ "name": "PorcelainMist",
+ "primary_color": "Cream",
+ "secondary_color": "Soft White",
+ "color_family": "Warm Neutral",
+ "mood": "Delicate, Refined",
+ "style": "Classic, Elegant",
+ "description": "A delicate porcelain cream with misty softness. Classic elegance and refinement.",
+ "use_cases": "Ceramics, fine dining, bridal, luxury stationery",
+ "keywords": ["porcelain", "cream", "mist", "delicate", "refined", "classic", "elegant"]
+ },
+ "QuietMoss.jpg": {
+ "name": "QuietMoss",
+ "primary_color": "Moss Green",
+ "secondary_color": "Sage",
+ "color_family": "Nature",
+ "mood": "Tranquil, Peaceful",
+ "style": "Organic, Calming",
+ "description": "A quiet moss green that brings tranquility and connection to nature.",
+ "use_cases": "Wellness brands, botanical products, sustainable goods, meditation spaces",
+ "keywords": ["moss", "green", "quiet", "tranquil", "peaceful", "organic", "calming"]
+ },
+ "SeafoamLight.jpg": {
+ "name": "SeafoamLight",
+ "primary_color": "Seafoam",
+ "secondary_color": "Mint",
+ "color_family": "Cool",
+ "mood": "Fresh, Playful",
+ "style": "Coastal, Light",
+ "description": "A light seafoam with minty freshness. Playful yet sophisticated coastal vibe.",
+ "use_cases": "Summer collections, beach products, refreshing beverages, youth brands",
+ "keywords": ["seafoam", "mint", "light", "fresh", "playful", "coastal", "summer"]
+ },
+ "SilverShore.jpg": {
+ "name": "SilverShore",
+ "primary_color": "Silver",
+ "secondary_color": "Sand",
+ "color_family": "Metallic Neutral",
+ "mood": "Sophisticated, Modern",
+ "style": "Sleek, Contemporary",
+ "description": "A sleek silver with sandy shore warmth. Modern sophistication meets coastal warmth.",
+ "use_cases": "Tech accessories, modern home, jewelry, premium retail",
+ "keywords": ["silver", "shore", "sand", "sophisticated", "modern", "sleek", "metallic"]
+ },
+ "SnowVeil.jpg": {
+ "name": "SnowVeil",
+ "primary_color": "Pure White",
+ "secondary_color": "Ice",
+ "color_family": "Cool Neutral",
+ "mood": "Pure, Serene",
+ "style": "Minimal, Clean",
+ "description": "A pure snow white with delicate veil-like softness. Ultimate purity and serenity.",
+ "use_cases": "Bridal, skincare, clean tech, medical, minimalist design",
+ "keywords": ["snow", "white", "pure", "serene", "minimal", "clean", "veil"]
+ },
+ "SteelSky.jpg": {
+ "name": "SteelSky",
+ "primary_color": "Steel Blue",
+ "secondary_color": "Gray",
+ "color_family": "Cool",
+ "mood": "Strong, Reliable",
+ "style": "Industrial, Modern",
+ "description": "A strong steel blue with sky-like openness. Combines strength with aspiration.",
+ "use_cases": "Automotive, aerospace, industrial design, corporate, sports gear",
+ "keywords": ["steel", "blue", "sky", "strong", "reliable", "industrial", "modern"]
+ },
+ "StoneDusk.jpg": {
+ "name": "StoneDusk",
+ "primary_color": "Warm Gray",
+ "secondary_color": "Taupe",
+ "color_family": "Warm Neutral",
+ "mood": "Warm, Inviting",
+ "style": "Rustic, Cozy",
+ "description": "A warm stone gray with dusk-like golden undertones. Inviting and comfortable.",
+ "use_cases": "Home decor, hospitality, artisan goods, coffee shops",
+ "keywords": ["stone", "dusk", "warm", "gray", "taupe", "inviting", "cozy"]
+ },
+ "VerdantHaze.jpg": {
+ "name": "VerdantHaze",
+ "primary_color": "Green",
+ "secondary_color": "Teal",
+ "color_family": "Nature",
+ "mood": "Lush, Vibrant",
+ "style": "Tropical, Fresh",
+ "description": "A lush verdant green with hazy tropical depth. Vibrant and full of life.",
+ "use_cases": "Tropical products, plant-based brands, health foods, spa resorts",
+ "keywords": ["verdant", "green", "haze", "lush", "vibrant", "tropical", "fresh"]
+ }
+}
+
+
+def create_search_index(index_client: SearchIndexClient) -> SearchIndex:
+ """Create the search index schema for product images."""
+
+ fields = [
+ # Key field
+ SimpleField(
+ name="id",
+ type=SearchFieldDataType.String,
+ key=True,
+ filterable=True
+ ),
+ # Image identification
+ SearchableField(
+ name="filename",
+ type=SearchFieldDataType.String,
+ filterable=True,
+ sortable=True
+ ),
+ SearchableField(
+ name="name",
+ type=SearchFieldDataType.String,
+ filterable=True,
+ sortable=True
+ ),
+ # Color fields
+ SearchableField(
+ name="primary_color",
+ type=SearchFieldDataType.String,
+ filterable=True,
+ facetable=True
+ ),
+ SearchableField(
+ name="secondary_color",
+ type=SearchFieldDataType.String,
+ filterable=True,
+ facetable=True
+ ),
+ SearchableField(
+ name="color_family",
+ type=SearchFieldDataType.String,
+ filterable=True,
+ facetable=True
+ ),
+ # Style and mood
+ SearchableField(
+ name="mood",
+ type=SearchFieldDataType.String,
+ filterable=True
+ ),
+ SearchableField(
+ name="style",
+ type=SearchFieldDataType.String,
+ filterable=True
+ ),
+ # Description and use cases
+ SearchableField(
+ name="description",
+ type=SearchFieldDataType.String
+ ),
+ SearchableField(
+ name="use_cases",
+ type=SearchFieldDataType.String
+ ),
+ # Keywords for search
+ SimpleField(
+ name="keywords",
+ type=SearchFieldDataType.Collection(SearchFieldDataType.String),
+ filterable=True,
+ facetable=True
+ ),
+ # Storage info
+ SimpleField(
+ name="blob_url",
+ type=SearchFieldDataType.String
+ ),
+ SimpleField(
+ name="blob_container",
+ type=SearchFieldDataType.String,
+ filterable=True
+ ),
+ # Combined text for embedding
+ SearchableField(
+ name="combined_text",
+ type=SearchFieldDataType.String
+ ),
+ # Vector field for semantic search
+ SearchField(
+ name="content_vector",
+ type=SearchFieldDataType.Collection(SearchFieldDataType.Single),
+ searchable=True,
+ vector_search_dimensions=1536, # text-embedding-ada-002 dimensions
+ vector_search_profile_name="image-vector-profile"
+ )
+ ]
+
+ # Configure vector search
+ vector_search = VectorSearch(
+ algorithms=[
+ HnswAlgorithmConfiguration(
+ name="hnsw-algorithm",
+ parameters={
+ "m": 4,
+ "efConstruction": 400,
+ "efSearch": 500,
+ "metric": "cosine"
+ }
+ )
+ ],
+ profiles=[
+ VectorSearchProfile(
+ name="image-vector-profile",
+ algorithm_configuration_name="hnsw-algorithm"
+ )
+ ]
+ )
+
+ # Configure semantic search
+ semantic_config = SemanticConfiguration(
+ name="image-semantic-config",
+ prioritized_fields=SemanticPrioritizedFields(
+ title_field=SemanticField(field_name="name"),
+ content_fields=[
+ SemanticField(field_name="description"),
+ SemanticField(field_name="use_cases"),
+ SemanticField(field_name="combined_text")
+ ],
+ keywords_fields=[
+ SemanticField(field_name="primary_color"),
+ SemanticField(field_name="style"),
+ SemanticField(field_name="mood")
+ ]
+ )
+ )
+
+ semantic_search = SemanticSearch(configurations=[semantic_config])
+
+ # Create the index
+ index = SearchIndex(
+ name=SEARCH_INDEX_NAME,
+ fields=fields,
+ vector_search=vector_search,
+ semantic_search=semantic_search
+ )
+
+ return index
+
+
+async def get_embedding(text: str) -> List[float]:
+ """Get embedding vector for text using Azure OpenAI."""
+ from openai import AzureOpenAI
+
+ client = AzureOpenAI(
+ azure_endpoint=AZURE_OPENAI_ENDPOINT,
+ azure_ad_token_provider=lambda: DefaultAzureCredential().get_token(
+ "https://cognitiveservices.azure.com/.default"
+ ).token,
+ api_version="2024-06-01"
+ )
+
+ try:
+ response = client.embeddings.create(
+ input=text,
+ model=AZURE_OPENAI_EMBEDDING_MODEL
+ )
+ return response.data[0].embedding
+ except Exception as e:
+ print(f"Warning: Could not generate embedding: {e}")
+ # Return zero vector as fallback
+ return [0.0] * 1536
+
+
+async def get_blob_images() -> List[Dict[str, Any]]:
+ """Get list of images from blob storage."""
+ account_url = f"https://{STORAGE_ACCOUNT_NAME}.blob.core.windows.net"
+ credential = DefaultAzureCredential()
+
+ images = []
+
+ async with BlobServiceClient(account_url=account_url, credential=credential) as blob_service:
+ container_client = blob_service.get_container_client(CONTAINER_NAME)
+
+ async for blob in container_client.list_blobs():
+ if blob.name.lower().endswith(('.jpg', '.jpeg')):
+ images.append({
+ "filename": blob.name,
+ "url": f"{account_url}/{CONTAINER_NAME}/{blob.name}",
+ "size": blob.size
+ })
+
+ return images
+
+
+def prepare_document(filename: str, blob_url: str) -> Dict[str, Any]:
+ """Prepare a document for indexing."""
+
+ # Get metadata for this image
+ metadata = IMAGE_METADATA.get(filename, {
+ "name": filename.replace(".jpg", "").replace(".JPG", ""),
+ "primary_color": "Unknown",
+ "secondary_color": "Unknown",
+ "color_family": "Unknown",
+ "mood": "Unknown",
+ "style": "Unknown",
+ "description": f"Image file: {filename}",
+ "use_cases": "General marketing",
+ "keywords": [filename.lower().replace(".jpg", "")]
+ })
+
+ # Create combined text for embedding
+ combined_text = f"""
+ {metadata['name']} - {metadata['description']}
+ Colors: {metadata['primary_color']}, {metadata['secondary_color']} ({metadata['color_family']})
+ Mood: {metadata['mood']}
+ Style: {metadata['style']}
+ Use Cases: {metadata['use_cases']}
+ Keywords: {', '.join(metadata['keywords'])}
+ """
+
+ # Create document ID from filename
+ doc_id = filename.lower().replace(".jpg", "").replace(" ", "-").replace(".", "-")
+
+ return {
+ "id": doc_id,
+ "filename": filename,
+ "name": metadata["name"],
+ "primary_color": metadata["primary_color"],
+ "secondary_color": metadata["secondary_color"],
+ "color_family": metadata["color_family"],
+ "mood": metadata["mood"],
+ "style": metadata["style"],
+ "description": metadata["description"],
+ "use_cases": metadata["use_cases"],
+ "keywords": metadata["keywords"],
+ "blob_url": blob_url,
+ "blob_container": CONTAINER_NAME,
+ "combined_text": combined_text.strip()
+ }
+
+
+async def index_images(search_client: SearchClient, images: List[Dict[str, Any]], use_vectors: bool = True):
+ """Index images into the search index."""
+
+ documents = []
+
+ for img in images:
+ doc = prepare_document(img["filename"], img["url"])
+
+ if use_vectors:
+ try:
+ # Generate embedding for the combined text
+ embedding = await get_embedding(doc["combined_text"])
+ doc["content_vector"] = embedding
+ print(f" ✓ Generated embedding for: {img['filename']}")
+ except Exception as e:
+ print(f" ⚠ Embedding failed for {img['filename']}: {e}")
+ doc["content_vector"] = [0.0] * 1536
+ else:
+ doc["content_vector"] = [0.0] * 1536
+
+ documents.append(doc)
+
+ # Upload documents to the index
+ result = search_client.upload_documents(documents)
+
+ succeeded = sum(1 for r in result if r.succeeded)
+ failed = sum(1 for r in result if not r.succeeded)
+
+ return succeeded, failed
+
+
+async def main():
+ """Main entry point."""
+ print("=" * 60)
+ print("Azure AI Search Index Creation for Product Images")
+ print("=" * 60)
+ print()
+
+ # Check for embedding model deployment
+ use_vectors = bool(AZURE_OPENAI_ENDPOINT) and bool(AZURE_OPENAI_EMBEDDING_MODEL)
+ if not use_vectors:
+ print("⚠ Warning: Azure OpenAI not configured. Creating index without embeddings.")
+ print(" Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_EMBEDDING_MODEL in .env")
+ print()
+
+ # Prefer RBAC (DefaultAzureCredential), fall back to API key if RBAC fails
+ try:
+ search_credential = DefaultAzureCredential()
+ # Test the credential
+ test_client = SearchIndexClient(endpoint=SEARCH_ENDPOINT, credential=search_credential)
+ list(test_client.list_indexes()) # Quick test
+ print("Using RBAC authentication for search (DefaultAzureCredential)")
+ except Exception as e:
+ if AZURE_SEARCH_ADMIN_KEY:
+ search_credential = AzureKeyCredential(AZURE_SEARCH_ADMIN_KEY)
+ print("Using API key authentication for search (RBAC failed)")
+ else:
+ print(f"⚠ RBAC failed and no API key configured: {e}")
+ raise
+
+ # Create index client
+ index_client = SearchIndexClient(
+ endpoint=SEARCH_ENDPOINT,
+ credential=search_credential
+ )
+
+ # Create or update the index
+ print(f"Creating search index: {SEARCH_INDEX_NAME}")
+ print(f"Search endpoint: {SEARCH_ENDPOINT}")
+ print()
+
+ try:
+ index = create_search_index(index_client)
+ result = index_client.create_or_update_index(index)
+ print(f"✓ Index '{result.name}' created/updated successfully")
+ except Exception as e:
+ print(f"✗ Failed to create index: {e}")
+ raise
+
+ # Get images from blob storage
+ print()
+ print("Fetching images from blob storage...")
+
+ try:
+ images = await get_blob_images()
+ print(f"Found {len(images)} images in blob storage")
+ except Exception as e:
+ print(f"✗ Failed to list blob images: {e}")
+ print(" Make sure images are uploaded to blob storage first.")
+ print(" Run: python upload_images.py")
+ return
+
+ if not images:
+ print("No images found. Please upload images first using upload_images.py")
+ return
+
+ # Index the images
+ print()
+ print("Indexing images...")
+ print("-" * 50)
+
+ search_client = SearchClient(
+ endpoint=SEARCH_ENDPOINT,
+ index_name=SEARCH_INDEX_NAME,
+ credential=search_credential
+ )
+
+ try:
+ succeeded, failed = await index_images(search_client, images, use_vectors=use_vectors)
+ print("-" * 50)
+ print(f"\n✓ Indexed {succeeded} images successfully")
+ if failed > 0:
+ print(f"✗ Failed to index {failed} images")
+ except Exception as e:
+ print(f"✗ Failed to index images: {e}")
+ raise
+
+ # Summary
+ print()
+ print("=" * 60)
+ print("Index Creation Complete!")
+ print("=" * 60)
+ print(f"Index name: {SEARCH_INDEX_NAME}")
+ print(f"Endpoint: {SEARCH_ENDPOINT}")
+ print(f"Documents indexed: {succeeded}")
+ print()
+ print("You can now use this index for grounding AI content generation.")
+ print()
+ print("Example search queries:")
+ print(" - Search by color: 'blue', 'green', 'warm tones'")
+ print(" - Search by mood: 'calm', 'energetic', 'professional'")
+ print(" - Search by use case: 'luxury products', 'outdoor brands'")
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/content-gen/scripts/deploy.ps1 b/content-gen/scripts/deploy.ps1
new file mode 100644
index 000000000..62947c72d
--- /dev/null
+++ b/content-gen/scripts/deploy.ps1
@@ -0,0 +1,230 @@
+# PowerShell deployment script for Content Generation Solution Accelerator
+#
+# This solution uses Microsoft Agent Framework with HandoffBuilder orchestration
+# for multi-agent content generation workflows.
+
+$ErrorActionPreference = "Stop"
+
+Write-Host "============================================" -ForegroundColor Cyan
+Write-Host "Content Generation Solution Accelerator" -ForegroundColor Cyan
+Write-Host "Deployment Script" -ForegroundColor Cyan
+Write-Host "============================================" -ForegroundColor Cyan
+
+# Check prerequisites
+Write-Host "Checking prerequisites..." -ForegroundColor Yellow
+
+$azCli = Get-Command az -ErrorAction SilentlyContinue
+if (-not $azCli) {
+ Write-Host "Error: Azure CLI is not installed. Please install it first." -ForegroundColor Red
+ exit 1
+}
+
+$dockerCli = Get-Command docker -ErrorAction SilentlyContinue
+if (-not $dockerCli) {
+ Write-Host "Warning: Docker is not installed. Container builds will need to be done in ACR." -ForegroundColor Yellow
+}
+
+# Check Azure login
+Write-Host "Checking Azure login status..." -ForegroundColor Yellow
+try {
+ az account show | Out-Null
+} catch {
+ Write-Host "Not logged in to Azure. Running 'az login'..." -ForegroundColor Yellow
+ az login
+}
+
+# Get current directory
+$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
+$ProjectDir = Split-Path -Parent $ScriptDir
+
+Set-Location $ProjectDir
+
+# Configuration from environment or prompt
+$ResourceGroup = if ($env:RESOURCE_GROUP) { $env:RESOURCE_GROUP } else { $null }
+$Location = if ($env:LOCATION) { $env:LOCATION } else { "eastus" }
+$AcrName = if ($env:ACR_NAME) { $env:ACR_NAME } else { $null }
+$ContainerName = if ($env:CONTAINER_NAME) { $env:CONTAINER_NAME } else { "aci-contentgen-backend" }
+$AppServiceName = if ($env:APP_SERVICE_NAME) { $env:APP_SERVICE_NAME } else { $null }
+$ImageTag = if ($env:IMAGE_TAG) { $env:IMAGE_TAG } else { "latest" }
+
+Write-Host ""
+Write-Host "Current configuration:"
+Write-Host " Resource Group: $($ResourceGroup ?? '')"
+Write-Host " Location: $Location"
+Write-Host " ACR Name: $($AcrName ?? '')"
+Write-Host " Container Name: $ContainerName"
+Write-Host " App Service: $($AppServiceName ?? '')"
+Write-Host " Image Tag: $ImageTag"
+Write-Host ""
+
+# Prompt for missing values
+if (-not $ResourceGroup) {
+ $ResourceGroup = Read-Host "Enter Resource Group name"
+}
+
+if (-not $AcrName) {
+ $AcrName = Read-Host "Enter Azure Container Registry name"
+}
+
+if (-not $AppServiceName) {
+ $AppServiceName = Read-Host "Enter App Service name"
+}
+
+Write-Host ""
+Write-Host "This deployment will:" -ForegroundColor Yellow
+Write-Host " 1. Build and push the backend container to ACR"
+Write-Host " 2. Update the Azure Container Instance"
+Write-Host " 3. Build and deploy the frontend to App Service"
+Write-Host " 4. Configure RBAC roles for managed identity"
+Write-Host ""
+Write-Host "Required Azure resources (should already exist):" -ForegroundColor Yellow
+Write-Host " - Azure Container Registry (ACR)"
+Write-Host " - Azure Container Instance (ACI) in VNet"
+Write-Host " - Azure App Service (frontend)"
+Write-Host " - Azure Cosmos DB (products, conversations containers)"
+Write-Host " - Azure Blob Storage (product-images, generated-images containers)"
+Write-Host " - Azure OpenAI (GPT model for text generation)"
+Write-Host " - Azure OpenAI (DALL-E 3 for image generation - can be separate resource)"
+Write-Host ""
+
+$continue = Read-Host "Continue with deployment? (y/n)"
+
+if ($continue -eq "y" -or $continue -eq "Y") {
+
+ # Step 1: Build and push container
+ Write-Host ""
+ Write-Host "Step 1: Building and pushing backend container..." -ForegroundColor Green
+ Write-Host "==========================================" -ForegroundColor Green
+
+ Set-Location "$ProjectDir\src"
+
+ # Login to ACR
+ az acr login --name $AcrName
+
+ # Build and push using ACR tasks
+ az acr build `
+ --registry $AcrName `
+ --image "contentgen-backend:$ImageTag" `
+ --file WebApp.Dockerfile `
+ .
+
+ Write-Host "✓ Container built and pushed to $AcrName.azurecr.io/contentgen-backend:$ImageTag" -ForegroundColor Green
+
+ # Step 2: Update ACI
+ Write-Host ""
+ Write-Host "Step 2: Updating Azure Container Instance..." -ForegroundColor Green
+ Write-Host "==========================================" -ForegroundColor Green
+
+ try {
+ $currentIP = az container show -g $ResourceGroup -n $ContainerName --query "ipAddress.ip" -o tsv 2>$null
+ if ($currentIP) {
+ Write-Host "Current container IP: $currentIP"
+ Write-Host "Restarting container with new image..."
+ az container restart -g $ResourceGroup -n $ContainerName
+ }
+ } catch {
+ Write-Host "Warning: Container $ContainerName not found. You may need to create it manually." -ForegroundColor Yellow
+ }
+
+ # Step 3: Build and deploy frontend
+ Write-Host ""
+ Write-Host "Step 3: Building and deploying frontend..." -ForegroundColor Green
+ Write-Host "==========================================" -ForegroundColor Green
+
+ Set-Location "$ProjectDir\src\frontend"
+ npm install
+ npm run build
+
+ # Copy built files to server directory
+ Copy-Item -Path "$ProjectDir\src\static\*" -Destination "$ProjectDir\src\frontend-server\static\" -Recurse -Force
+
+ Set-Location "$ProjectDir\src\frontend-server"
+
+ # Create deployment package
+ if (Test-Path "frontend-deploy.zip") {
+ Remove-Item "frontend-deploy.zip"
+ }
+ Compress-Archive -Path "static", "server.js", "package.json", "package-lock.json" -DestinationPath "frontend-deploy.zip"
+
+ # Deploy to App Service
+ az webapp deploy `
+ --resource-group $ResourceGroup `
+ --name $AppServiceName `
+ --src-path "frontend-deploy.zip" `
+ --type zip
+
+ Write-Host "✓ Frontend deployed to $AppServiceName" -ForegroundColor Green
+
+ # Step 4: Get managed identity and show RBAC requirements
+ Write-Host ""
+ Write-Host "Step 4: RBAC Configuration" -ForegroundColor Green
+ Write-Host "==========================================" -ForegroundColor Green
+
+ try {
+ $principalId = az container show -g $ResourceGroup -n $ContainerName --query "identity.principalId" -o tsv 2>$null
+
+ if ($principalId) {
+ Write-Host "Container Managed Identity Principal ID: $principalId" -ForegroundColor Yellow
+ Write-Host ""
+ Write-Host "Required RBAC role assignments for the managed identity:" -ForegroundColor Yellow
+ Write-Host ""
+ Write-Host "1. Azure OpenAI (GPT model):" -ForegroundColor Cyan
+ Write-Host " Role: Cognitive Services OpenAI User"
+ Write-Host " az role assignment create --assignee $principalId ``"
+ Write-Host " --role 'Cognitive Services OpenAI User' ``"
+ Write-Host " --scope "
+ Write-Host ""
+ Write-Host "2. Azure OpenAI (DALL-E model - if separate resource):" -ForegroundColor Cyan
+ Write-Host " Role: Cognitive Services OpenAI User"
+ Write-Host " az role assignment create --assignee $principalId ``"
+ Write-Host " --role 'Cognitive Services OpenAI User' ``"
+ Write-Host " --scope "
+ Write-Host ""
+ Write-Host "3. Azure Cosmos DB:" -ForegroundColor Cyan
+ Write-Host " Role: Cosmos DB Built-in Data Contributor (data plane)"
+ Write-Host " az cosmosdb sql role assignment create ``"
+ Write-Host " --account-name ``"
+ Write-Host " --resource-group $ResourceGroup ``"
+ Write-Host " --scope '/' ``"
+ Write-Host " --principal-id $principalId ``"
+ Write-Host " --role-definition-id 00000000-0000-0000-0000-000000000002"
+ Write-Host ""
+ Write-Host " Role: Cosmos DB Account Reader Role (for metadata)"
+ Write-Host " az role assignment create --assignee $principalId ``"
+ Write-Host " --role 'Cosmos DB Account Reader Role' ``"
+ Write-Host " --scope "
+ Write-Host ""
+ Write-Host "4. Azure Blob Storage:" -ForegroundColor Cyan
+ Write-Host " Role: Storage Blob Data Contributor"
+ Write-Host " az role assignment create --assignee $principalId ``"
+ Write-Host " --role 'Storage Blob Data Contributor' ``"
+ Write-Host " --scope "
+ }
+ } catch {
+ Write-Host "Warning: Could not retrieve managed identity. Configure RBAC manually." -ForegroundColor Yellow
+ }
+
+ Write-Host ""
+ Write-Host "============================================" -ForegroundColor Green
+ Write-Host "Deployment complete!" -ForegroundColor Green
+ Write-Host "============================================" -ForegroundColor Green
+
+ # Get App Service URL
+ try {
+ $webappUrl = az webapp show -g $ResourceGroup -n $AppServiceName --query "defaultHostName" -o tsv 2>$null
+ if ($webappUrl) {
+ Write-Host ""
+ Write-Host "Application URL: https://$webappUrl" -ForegroundColor Cyan
+ }
+ } catch {}
+
+ Write-Host ""
+ Write-Host "Post-deployment steps:" -ForegroundColor Yellow
+ Write-Host "1. Verify RBAC roles are assigned (see above)"
+ Write-Host "2. Upload product data: python scripts/load_sample_data.py"
+ Write-Host "3. Test the application at the URL above"
+ Write-Host ""
+
+} else {
+ Write-Host "Deployment cancelled." -ForegroundColor Yellow
+}
diff --git a/content-gen/scripts/deploy.sh b/content-gen/scripts/deploy.sh
new file mode 100644
index 000000000..fb5b60c20
--- /dev/null
+++ b/content-gen/scripts/deploy.sh
@@ -0,0 +1,222 @@
+#!/bin/bash
+# Deployment script for Content Generation Solution Accelerator
+#
+# This solution uses Microsoft Agent Framework with HandoffBuilder orchestration
+# for multi-agent content generation workflows.
+
+set -e
+
+echo "============================================"
+echo "Content Generation Solution Accelerator"
+echo "Deployment Script"
+echo "============================================"
+
+# Check prerequisites
+echo "Checking prerequisites..."
+
+if ! command -v az &> /dev/null; then
+ echo "Error: Azure CLI is not installed. Please install it first."
+ exit 1
+fi
+
+if ! command -v docker &> /dev/null; then
+ echo "Warning: Docker is not installed. Container builds will need to be done in ACR."
+fi
+
+# Check Azure login
+echo "Checking Azure login status..."
+az account show &> /dev/null || {
+ echo "Not logged in to Azure. Running 'az login'..."
+ az login
+}
+
+# Get current directory
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
+
+cd "$PROJECT_DIR"
+
+# Default configuration
+RESOURCE_GROUP="${RESOURCE_GROUP:-}"
+LOCATION="${LOCATION:-eastus}"
+ACR_NAME="${ACR_NAME:-}"
+CONTAINER_NAME="${CONTAINER_NAME:-aci-contentgen-backend}"
+APP_SERVICE_NAME="${APP_SERVICE_NAME:-}"
+IMAGE_TAG="${IMAGE_TAG:-latest}"
+
+echo ""
+echo "Current configuration:"
+echo " Resource Group: ${RESOURCE_GROUP:-}"
+echo " Location: $LOCATION"
+echo " ACR Name: ${ACR_NAME:-}"
+echo " Container Name: $CONTAINER_NAME"
+echo " App Service: ${APP_SERVICE_NAME:-}"
+echo " Image Tag: $IMAGE_TAG"
+echo ""
+
+# Prompt for missing values
+if [ -z "$RESOURCE_GROUP" ]; then
+ read -p "Enter Resource Group name: " RESOURCE_GROUP
+fi
+
+if [ -z "$ACR_NAME" ]; then
+ read -p "Enter Azure Container Registry name: " ACR_NAME
+fi
+
+if [ -z "$APP_SERVICE_NAME" ]; then
+ read -p "Enter App Service name: " APP_SERVICE_NAME
+fi
+
+echo ""
+echo "This deployment will:"
+echo " 1. Build and push the backend container to ACR"
+echo " 2. Update the Azure Container Instance"
+echo " 3. Build and deploy the frontend to App Service"
+echo " 4. Configure RBAC roles for managed identity"
+echo ""
+echo "Required Azure resources (should already exist):"
+echo " - Azure Container Registry (ACR)"
+echo " - Azure Container Instance (ACI) in VNet"
+echo " - Azure App Service (frontend)"
+echo " - Azure Cosmos DB (products, conversations containers)"
+echo " - Azure Blob Storage (product-images, generated-images containers)"
+echo " - Azure OpenAI (GPT model for text generation)"
+echo " - Azure OpenAI (DALL-E 3 for image generation - can be separate resource)"
+echo ""
+
+read -p "Continue with deployment? (y/n) " -n 1 -r
+echo ""
+
+if [[ $REPLY =~ ^[Yy]$ ]]; then
+
+ # Step 1: Build and push container
+ echo ""
+ echo "Step 1: Building and pushing backend container..."
+ echo "=========================================="
+
+ cd "$PROJECT_DIR/src"
+
+ # Login to ACR
+ az acr login --name "$ACR_NAME"
+
+ # Build and push using ACR tasks
+ az acr build \
+ --registry "$ACR_NAME" \
+ --image "contentgen-backend:$IMAGE_TAG" \
+ --file WebApp.Dockerfile \
+ .
+
+ echo "✓ Container built and pushed to $ACR_NAME.azurecr.io/contentgen-backend:$IMAGE_TAG"
+
+ # Step 2: Get the current container's managed identity
+ echo ""
+ echo "Step 2: Updating Azure Container Instance..."
+ echo "=========================================="
+
+ # Get current ACI configuration
+ CURRENT_IP=$(az container show -g "$RESOURCE_GROUP" -n "$CONTAINER_NAME" --query "ipAddress.ip" -o tsv 2>/dev/null || echo "")
+
+ if [ -n "$CURRENT_IP" ]; then
+ echo "Current container IP: $CURRENT_IP"
+ echo "Restarting container with new image..."
+ az container restart -g "$RESOURCE_GROUP" -n "$CONTAINER_NAME"
+ else
+ echo "Warning: Container $CONTAINER_NAME not found. You may need to create it manually."
+ fi
+
+ # Step 3: Build and deploy frontend
+ echo ""
+ echo "Step 3: Building and deploying frontend..."
+ echo "=========================================="
+
+ cd "$PROJECT_DIR/src/frontend"
+ npm install
+ npm run build
+
+ # Copy built files to server directory
+ cp -r "$PROJECT_DIR/src/static/"* "$PROJECT_DIR/src/frontend-server/static/"
+
+ cd "$PROJECT_DIR/src/frontend-server"
+
+ # Create deployment package
+ rm -f frontend-deploy.zip
+ zip -r frontend-deploy.zip static/ server.js package.json package-lock.json
+
+ # Deploy to App Service
+ az webapp deploy \
+ --resource-group "$RESOURCE_GROUP" \
+ --name "$APP_SERVICE_NAME" \
+ --src-path frontend-deploy.zip \
+ --type zip
+
+ echo "✓ Frontend deployed to $APP_SERVICE_NAME"
+
+ # Step 4: Get managed identity and show RBAC requirements
+ echo ""
+ echo "Step 4: RBAC Configuration"
+ echo "=========================================="
+
+ PRINCIPAL_ID=$(az container show -g "$RESOURCE_GROUP" -n "$CONTAINER_NAME" --query "identity.principalId" -o tsv 2>/dev/null || echo "")
+
+ if [ -n "$PRINCIPAL_ID" ]; then
+ echo "Container Managed Identity Principal ID: $PRINCIPAL_ID"
+ echo ""
+ echo "Required RBAC role assignments for the managed identity:"
+ echo ""
+ echo "1. Azure OpenAI (GPT model):"
+ echo " Role: Cognitive Services OpenAI User"
+ echo " az role assignment create --assignee $PRINCIPAL_ID \\"
+ echo " --role 'Cognitive Services OpenAI User' \\"
+ echo " --scope "
+ echo ""
+ echo "2. Azure OpenAI (DALL-E model - if separate resource):"
+ echo " Role: Cognitive Services OpenAI User"
+ echo " az role assignment create --assignee $PRINCIPAL_ID \\"
+ echo " --role 'Cognitive Services OpenAI User' \\"
+ echo " --scope "
+ echo ""
+ echo "3. Azure Cosmos DB:"
+ echo " Role: Cosmos DB Built-in Data Contributor (data plane)"
+ echo " az cosmosdb sql role assignment create \\"
+ echo " --account-name \\"
+ echo " --resource-group $RESOURCE_GROUP \\"
+ echo " --scope '/' \\"
+ echo " --principal-id $PRINCIPAL_ID \\"
+ echo " --role-definition-id 00000000-0000-0000-0000-000000000002"
+ echo ""
+ echo " Role: Cosmos DB Account Reader Role (for metadata)"
+ echo " az role assignment create --assignee $PRINCIPAL_ID \\"
+ echo " --role 'Cosmos DB Account Reader Role' \\"
+ echo " --scope "
+ echo ""
+ echo "4. Azure Blob Storage:"
+ echo " Role: Storage Blob Data Contributor"
+ echo " az role assignment create --assignee $PRINCIPAL_ID \\"
+ echo " --role 'Storage Blob Data Contributor' \\"
+ echo " --scope "
+ else
+ echo "Warning: Could not retrieve managed identity. Configure RBAC manually."
+ fi
+
+ echo ""
+ echo "============================================"
+ echo "Deployment complete!"
+ echo "============================================"
+
+ # Get App Service URL
+ WEBAPP_URL=$(az webapp show -g "$RESOURCE_GROUP" -n "$APP_SERVICE_NAME" --query "defaultHostName" -o tsv 2>/dev/null || echo "")
+ if [ -n "$WEBAPP_URL" ]; then
+ echo ""
+ echo "Application URL: https://$WEBAPP_URL"
+ fi
+
+ echo ""
+ echo "Post-deployment steps:"
+ echo "1. Verify RBAC roles are assigned (see above)"
+ echo "2. Upload product data: python scripts/load_sample_data.py"
+ echo "3. Test the application at the URL above"
+ echo ""
+
+else
+ echo "Deployment cancelled."
+fi
diff --git a/content-gen/scripts/images/BlueAsh.png b/content-gen/scripts/images/BlueAsh.png
new file mode 100644
index 000000000..b266e6c70
Binary files /dev/null and b/content-gen/scripts/images/BlueAsh.png differ
diff --git a/content-gen/scripts/images/CloudDrift.png b/content-gen/scripts/images/CloudDrift.png
new file mode 100644
index 000000000..1611ca7fd
Binary files /dev/null and b/content-gen/scripts/images/CloudDrift.png differ
diff --git a/content-gen/scripts/images/FogHarbor.png b/content-gen/scripts/images/FogHarbor.png
new file mode 100644
index 000000000..44ab1e153
Binary files /dev/null and b/content-gen/scripts/images/FogHarbor.png differ
diff --git a/content-gen/scripts/images/GlacierTint.png b/content-gen/scripts/images/GlacierTint.png
new file mode 100644
index 000000000..d37a3e067
Binary files /dev/null and b/content-gen/scripts/images/GlacierTint.png differ
diff --git a/content-gen/scripts/images/GraphiteFade.png b/content-gen/scripts/images/GraphiteFade.png
new file mode 100644
index 000000000..cb8f4225b
Binary files /dev/null and b/content-gen/scripts/images/GraphiteFade.png differ
diff --git a/content-gen/scripts/images/ObsidianPearl.png b/content-gen/scripts/images/ObsidianPearl.png
new file mode 100644
index 000000000..6b99490ff
Binary files /dev/null and b/content-gen/scripts/images/ObsidianPearl.png differ
diff --git a/content-gen/scripts/images/OliveStone.png b/content-gen/scripts/images/OliveStone.png
new file mode 100644
index 000000000..fd2747039
Binary files /dev/null and b/content-gen/scripts/images/OliveStone.png differ
diff --git a/content-gen/scripts/images/PineShadow.png b/content-gen/scripts/images/PineShadow.png
new file mode 100644
index 000000000..3e84ddc79
Binary files /dev/null and b/content-gen/scripts/images/PineShadow.png differ
diff --git a/content-gen/scripts/images/PorcelainMist.png b/content-gen/scripts/images/PorcelainMist.png
new file mode 100644
index 000000000..e62093276
Binary files /dev/null and b/content-gen/scripts/images/PorcelainMist.png differ
diff --git a/content-gen/scripts/images/QuietMoss.png b/content-gen/scripts/images/QuietMoss.png
new file mode 100644
index 000000000..99ebf1112
Binary files /dev/null and b/content-gen/scripts/images/QuietMoss.png differ
diff --git a/content-gen/scripts/images/SeafoamLight.png b/content-gen/scripts/images/SeafoamLight.png
new file mode 100644
index 000000000..cbcdc5c9b
Binary files /dev/null and b/content-gen/scripts/images/SeafoamLight.png differ
diff --git a/content-gen/scripts/images/SilverShore.png b/content-gen/scripts/images/SilverShore.png
new file mode 100644
index 000000000..c3fe950fa
Binary files /dev/null and b/content-gen/scripts/images/SilverShore.png differ
diff --git a/content-gen/scripts/images/SnowVeil.png b/content-gen/scripts/images/SnowVeil.png
new file mode 100644
index 000000000..0a445a72e
Binary files /dev/null and b/content-gen/scripts/images/SnowVeil.png differ
diff --git a/content-gen/scripts/images/SteelSky.png b/content-gen/scripts/images/SteelSky.png
new file mode 100644
index 000000000..5439a366d
Binary files /dev/null and b/content-gen/scripts/images/SteelSky.png differ
diff --git a/content-gen/scripts/images/StoneDusk.png b/content-gen/scripts/images/StoneDusk.png
new file mode 100644
index 000000000..c629b4043
Binary files /dev/null and b/content-gen/scripts/images/StoneDusk.png differ
diff --git a/content-gen/scripts/images/VerdantHaze.png b/content-gen/scripts/images/VerdantHaze.png
new file mode 100644
index 000000000..b99c1b8ba
Binary files /dev/null and b/content-gen/scripts/images/VerdantHaze.png differ
diff --git a/content-gen/scripts/local_dev.ps1 b/content-gen/scripts/local_dev.ps1
new file mode 100644
index 000000000..6978846ba
--- /dev/null
+++ b/content-gen/scripts/local_dev.ps1
@@ -0,0 +1,634 @@
+# =============================================================================
+# Local Development Script for Content Generation Accelerator (PowerShell)
+# =============================================================================
+#
+# This script sets up and runs the application locally for development.
+#
+# Usage:
+# .\local_dev.ps1 # Start both backend and frontend
+# .\local_dev.ps1 -Command backend # Start only the backend
+# .\local_dev.ps1 -Command frontend # Start only the frontend
+# .\local_dev.ps1 -Command setup # Set up virtual environment and install dependencies
+# .\local_dev.ps1 -Command env # Generate .env file from Azure resources
+#
+# Prerequisites:
+# - Python 3.11+
+# - Node.js 18+
+# - Azure CLI (for fetching environment variables)
+#
+# =============================================================================
+
+param(
+ [Parameter(Position=0)]
+ [ValidateSet("setup", "env", "backend", "frontend", "all", "build", "clean", "help")]
+ [string]$Command = "all"
+)
+
+$ErrorActionPreference = "Stop"
+
+# Get the script directory
+$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
+$ProjectRoot = Split-Path -Parent $ScriptDir
+$SrcDir = Join-Path $ProjectRoot "src"
+$BackendDir = Join-Path $SrcDir "backend"
+$FrontendDir = Join-Path $SrcDir "app\frontend"
+
+# Default ports
+$BackendPort = if ($env:BACKEND_PORT) { $env:BACKEND_PORT } else { "5000" }
+$FrontendPort = if ($env:FRONTEND_PORT) { $env:FRONTEND_PORT } else { "3000" }
+
+# =============================================================================
+# Helper Functions
+# =============================================================================
+
+function Write-Header {
+ param([string]$Message)
+ Write-Host ""
+ Write-Host "============================================" -ForegroundColor Blue
+ Write-Host $Message -ForegroundColor Blue
+ Write-Host "============================================" -ForegroundColor Blue
+ Write-Host ""
+}
+
+function Write-Success {
+ param([string]$Message)
+ Write-Host "✓ $Message" -ForegroundColor Green
+}
+
+function Write-Warning {
+ param([string]$Message)
+ Write-Host "⚠ $Message" -ForegroundColor Yellow
+}
+
+function Write-Error {
+ param([string]$Message)
+ Write-Host "✗ $Message" -ForegroundColor Red
+}
+
+function Write-Info {
+ param([string]$Message)
+ Write-Host "→ $Message" -ForegroundColor Cyan
+}
+
+function Test-Command {
+ param([string]$CommandName)
+ return [bool](Get-Command -Name $CommandName -ErrorAction SilentlyContinue)
+}
+
+# =============================================================================
+# Azure Authentication & Role Functions
+# =============================================================================
+
+function Ensure-AzureLogin {
+ if (-not (Test-Command "az")) {
+ Write-Error "Azure CLI is not installed. Please install it first."
+ exit 1
+ }
+
+ $accountId = az account show --query id -o tsv 2>$null
+ if ($accountId) {
+ $accountName = az account show --query name -o tsv 2>$null
+ Write-Success "Logged in to Azure: $accountName"
+ } else {
+ Write-Info "Not logged in. Running az login..."
+ az login --use-device-code --output none
+ $accountId = az account show --query id -o tsv 2>$null
+ if (-not $accountId) {
+ Write-Error "Azure login failed."
+ exit 1
+ }
+ Write-Success "Azure login successful."
+ }
+}
+
+function Ensure-AzureAIUserRole {
+ Write-Info "Checking Azure AI User role..."
+
+ # Get env vars
+ $existingProjectId = $null
+ $foundryResourceId = $null
+ if (Test-Path ".env") {
+ Get-Content ".env" | ForEach-Object {
+ if ($_ -match "^AZURE_EXISTING_AI_PROJECT_RESOURCE_ID=(.*)$") { $existingProjectId = $matches[1].Trim('"').Trim("'") }
+ if ($_ -match "^AI_FOUNDRY_RESOURCE_ID=(.*)$") { $foundryResourceId = $matches[1].Trim('"').Trim("'") }
+ }
+ }
+
+ # Determine scope
+ $scope = $null
+ if ($existingProjectId) {
+ $scope = $existingProjectId
+ } elseif ($foundryResourceId) {
+ $scope = $foundryResourceId
+ } else {
+ Write-Error "Neither AZURE_EXISTING_AI_PROJECT_RESOURCE_ID nor AI_FOUNDRY_RESOURCE_ID found in .env"
+ exit 1
+ }
+
+ $signedUserId = az ad signed-in-user show --query id -o tsv 2>$null
+ if (-not $signedUserId) {
+ Write-Error "Could not get signed-in user ID."
+ exit 1
+ }
+
+ $roleId = "53ca6127-db72-4b80-b1b0-d745d6d5456d"
+ $existing = az role assignment list --assignee $signedUserId --role $roleId --scope $scope --query "[0].id" -o tsv 2>$null
+
+ if ($existing) {
+ Write-Success "Azure AI User role already assigned."
+ } else {
+ Write-Info "Assigning Azure AI User role..."
+ az role assignment create --assignee $signedUserId --role $roleId --scope $scope --output none 2>$null
+ if ($LASTEXITCODE -ne 0) {
+ Write-Error "Failed to assign Azure AI User role."
+ exit 1
+ }
+ Write-Success "Azure AI User role assigned."
+ }
+}
+
+function Ensure-CosmosDBRole {
+ Write-Info "Checking Cosmos DB Data Contributor role..."
+
+ # Get env vars
+ $cosmosAccount = $null
+ $resourceGroup = $null
+ if (Test-Path ".env") {
+ Get-Content ".env" | ForEach-Object {
+ if ($_ -match "^COSMOSDB_ACCOUNT_NAME=(.*)$") { $cosmosAccount = $matches[1].Trim('"').Trim("'") }
+ if ($_ -match "^RESOURCE_GROUP_NAME=(.*)$") { $resourceGroup = $matches[1].Trim('"').Trim("'") }
+ }
+ }
+
+ if (-not $cosmosAccount -or -not $resourceGroup) {
+ Write-Error "COSMOSDB_ACCOUNT_NAME or RESOURCE_GROUP_NAME not found in .env"
+ exit 1
+ }
+
+ $signedUserId = az ad signed-in-user show --query id -o tsv 2>$null
+ if (-not $signedUserId) {
+ Write-Error "Could not get signed-in user ID."
+ exit 1
+ }
+
+ $roleDefId = "00000000-0000-0000-0000-000000000002"
+
+ # Check if role already assigned
+ $existing = az cosmosdb sql role assignment list --resource-group $resourceGroup --account-name $cosmosAccount --query "[?principalId=='$signedUserId' && contains(roleDefinitionId, '$roleDefId')].id | [0]" -o tsv 2>$null
+
+ if ($existing) {
+ Write-Success "Cosmos DB role already assigned."
+ } else {
+ Write-Info "Assigning Cosmos DB role..."
+ az cosmosdb sql role assignment create --resource-group $resourceGroup --account-name $cosmosAccount --role-definition-id $roleDefId --principal-id $signedUserId --scope "/" --output none 2>$null
+ if ($LASTEXITCODE -ne 0) {
+ Write-Error "Failed to assign Cosmos DB role."
+ exit 1
+ }
+ Write-Success "Cosmos DB role assigned."
+ }
+}
+
+function Ensure-StorageRole {
+ Write-Info "Checking Storage Blob Data Contributor role..."
+
+ # Get env vars
+ $storageAccount = $null
+ $resourceGroup = $null
+ if (Test-Path ".env") {
+ Get-Content ".env" | ForEach-Object {
+ if ($_ -match "^AZURE_BLOB_ACCOUNT_NAME=(.*)$") { $storageAccount = $matches[1].Trim('"').Trim("'") }
+ if ($_ -match "^RESOURCE_GROUP_NAME=(.*)$") { $resourceGroup = $matches[1].Trim('"').Trim("'") }
+ }
+ }
+
+ if (-not $storageAccount -or -not $resourceGroup) {
+ Write-Error "AZURE_BLOB_ACCOUNT_NAME or RESOURCE_GROUP_NAME not found in .env"
+ exit 1
+ }
+
+ $signedUserId = az ad signed-in-user show --query id -o tsv 2>$null
+ if (-not $signedUserId) {
+ Write-Error "Could not get signed-in user ID."
+ exit 1
+ }
+
+ # Get storage account resource ID
+ $storageResourceId = az storage account show --name $storageAccount --resource-group $resourceGroup --query id -o tsv 2>$null
+ if (-not $storageResourceId) {
+ Write-Error "Could not get storage account resource ID."
+ exit 1
+ }
+
+ $roleId = "Storage Blob Data Contributor"
+ $existing = az role assignment list --assignee $signedUserId --role $roleId --scope $storageResourceId --query "[0].id" -o tsv 2>$null
+
+ if ($existing) {
+ Write-Success "Storage Blob Data Contributor role already assigned."
+ } else {
+ Write-Info "Assigning Storage Blob Data Contributor role..."
+ az role assignment create --assignee $signedUserId --role $roleId --scope $storageResourceId --output none 2>$null
+ if ($LASTEXITCODE -ne 0) {
+ Write-Error "Failed to assign Storage Blob Data Contributor role."
+ exit 1
+ }
+ Write-Success "Storage Blob Data Contributor role assigned."
+ }
+}
+
+# =============================================================================
+# Setup Function
+# =============================================================================
+
+function Invoke-Setup {
+ Write-Header "Setting Up Local Development Environment"
+
+ Set-Location $ProjectRoot
+
+ # Check Python
+ Write-Info "Checking Python installation..."
+ if (-not (Test-Command "python")) {
+ Write-Error "Python is not installed. Please install Python 3.11+"
+ exit 1
+ }
+ $pythonVersion = python --version
+ Write-Success "Python $pythonVersion found"
+
+ # Check Node.js
+ Write-Info "Checking Node.js installation..."
+ if (-not (Test-Command "node")) {
+ Write-Error "Node.js is not installed. Please install Node.js 18+"
+ exit 1
+ }
+ $nodeVersion = node --version
+ Write-Success "Node.js $nodeVersion found"
+
+ # Create virtual environment
+ Write-Info "Creating Python virtual environment..."
+ if (-not (Test-Path ".venv")) {
+ python -m venv .venv
+ Write-Success "Virtual environment created"
+ } else {
+ Write-Warning "Virtual environment already exists"
+ }
+
+ # Activate virtual environment
+ $activateScript = Join-Path ".venv" "Scripts" "Activate.ps1"
+ & $activateScript
+
+ # Install Python dependencies
+ Write-Info "Installing Python dependencies..."
+ python -m pip install --upgrade pip | Out-Null
+ python -m pip install -r (Join-Path $BackendDir "requirements.txt") | Out-Null
+ Write-Success "Python dependencies installed"
+
+ # Install frontend dependencies
+ Write-Info "Installing frontend dependencies..."
+ Set-Location $FrontendDir
+ npm install | Out-Null
+ Write-Success "Frontend dependencies installed"
+
+ Set-Location $ProjectRoot
+
+ # Check for .env file
+ if (-not (Test-Path ".env")) {
+ Write-Warning ".env file not found"
+ if (Test-Path ".env.template") {
+ Write-Info "Copying .env.template to .env..."
+ Copy-Item ".env.template" ".env"
+ Write-Warning "Please update .env with your Azure resource values"
+ }
+ } else {
+ Write-Success ".env file found"
+ }
+
+ Write-Header "Setup Complete!"
+ Write-Host "To start development, run: " -NoNewline
+ Write-Host ".\scripts\local_dev.ps1" -ForegroundColor Green
+ Write-Host "Or start individually:"
+ Write-Host " Backend: " -NoNewline
+ Write-Host ".\scripts\local_dev.ps1 -Command backend" -ForegroundColor Green
+ Write-Host " Frontend: " -NoNewline
+ Write-Host ".\scripts\local_dev.ps1 -Command frontend" -ForegroundColor Green
+}
+
+# =============================================================================
+# Environment Generation Function
+# =============================================================================
+
+function Invoke-EnvGeneration {
+ Write-Header "Generating Environment Variables from Azure"
+
+ Set-Location $ProjectRoot
+
+ Ensure-AzureLogin
+
+ # If using azd
+ if ((Test-Command "azd") -and (Test-Path "azure.yaml")) {
+ Write-Info "Azure Developer CLI detected. Generating .env from azd..."
+ try {
+ $envValues = azd env get-values 2>$null
+ if ($envValues) {
+ $envValues | Out-File ".env.azd" -Encoding UTF8
+ Write-Success "Environment variables exported to .env.azd"
+
+ Write-Info "Merging with .env..."
+ if (Test-Path ".env") {
+ Copy-Item ".env" ".env.backup"
+ $existingEnv = Get-Content ".env"
+ $newEnv = Get-Content ".env.azd"
+
+ foreach ($line in $newEnv) {
+ $key = $line.Split('=')[0]
+ if (-not ($existingEnv -match "^$key=")) {
+ Add-Content ".env" $line
+ }
+ }
+ } else {
+ Move-Item ".env.azd" ".env"
+ }
+ Remove-Item ".env.azd" -ErrorAction SilentlyContinue
+ Write-Success "Environment variables merged"
+ }
+ } catch {
+ Write-Warning "Could not export from azd"
+ }
+ } else {
+ Write-Warning "Azure Developer CLI not found or no azure.yaml"
+ Write-Info "Please manually update .env with your Azure resource values"
+
+ if (-not (Test-Path ".env") -and (Test-Path ".env.template")) {
+ Copy-Item ".env.template" ".env"
+ Write-Info "Created .env from template"
+ }
+ }
+
+ Write-Success "Environment setup complete"
+ Write-Warning "Review .env and ensure all required values are set"
+}
+
+# =============================================================================
+# Backend Start Function
+# =============================================================================
+
+function Start-Backend {
+ Write-Header "Starting Backend Server"
+
+ Set-Location $ProjectRoot
+
+ # Check for .env
+ if (-not (Test-Path ".env")) {
+ Write-Error ".env file not found. Run: .\scripts\local_dev.ps1 -Command setup"
+ exit 1
+ }
+
+ # Ensure Azure roles
+ Ensure-AzureLogin
+ Ensure-AzureAIUserRole
+ Ensure-CosmosDBRole
+ Ensure-StorageRole
+
+ # Activate virtual environment
+ if (Test-Path ".venv") {
+ $activateScript = Join-Path ".venv" "Scripts" "Activate.ps1"
+ & $activateScript
+ Write-Success "Virtual environment activated"
+ } else {
+ Write-Error "Virtual environment not found. Run: .\scripts\local_dev.ps1 -Command setup"
+ exit 1
+ }
+
+ # Set environment variables
+ $env:PYTHONPATH = $BackendDir
+ $env:DOTENV_PATH = Join-Path $ProjectRoot ".env"
+
+ # Load .env file
+ Get-Content ".env" | ForEach-Object {
+ if ($_ -match "^([^#][^=]+)=(.*)$") {
+ $name = $matches[1].Trim()
+ $value = $matches[2].Trim()
+ # Strip surrounding quotes (single or double)
+ if (($value.StartsWith('"') -and $value.EndsWith('"')) -or ($value.StartsWith("'") -and $value.EndsWith("'"))) {
+ $value = $value.Substring(1, $value.Length - 2)
+ }
+ Set-Item -Path "env:$name" -Value $value
+ }
+ }
+
+ Write-Info "Starting Quart backend on port $BackendPort..."
+ Write-Info "API will be available at: http://localhost:$BackendPort"
+ Write-Info "Health check: http://localhost:$BackendPort/api/health"
+ Write-Host ""
+
+ Set-Location $BackendDir
+
+ # Use hypercorn for async support
+ if (Test-Command "hypercorn") {
+ hypercorn app:app --bind "0.0.0.0:$BackendPort" --reload
+ } else {
+ python -m quart --app app:app run --host 0.0.0.0 --port $BackendPort --reload
+ }
+}
+
+# =============================================================================
+# Frontend Start Function
+# =============================================================================
+
+function Start-Frontend {
+ Write-Header "Starting Frontend Development Server"
+
+ Set-Location $FrontendDir
+
+ # Check if node_modules exists
+ if (-not (Test-Path "node_modules")) {
+ Write-Error "Node modules not found. Run: .\scripts\local_dev.ps1 -Command setup"
+ exit 1
+ }
+
+ Write-Info "Starting Vite dev server on port $FrontendPort..."
+ Write-Info "Frontend will be available at: http://localhost:$FrontendPort"
+ Write-Info "API requests will proxy to: http://localhost:$BackendPort"
+ Write-Host ""
+
+ npm run dev
+}
+
+# =============================================================================
+# Start Both (using Jobs)
+# =============================================================================
+
+function Start-All {
+ Write-Header "Starting Full Development Environment"
+
+ Set-Location $ProjectRoot
+
+ # Check prerequisites
+ if (-not (Test-Path ".env")) {
+ Write-Error ".env file not found. Run: .\scripts\local_dev.ps1 -Command setup"
+ exit 1
+ }
+
+ if (-not (Test-Path ".venv")) {
+ Write-Error "Virtual environment not found. Run: .\scripts\local_dev.ps1 -Command setup"
+ exit 1
+ }
+
+ if (-not (Test-Path (Join-Path $FrontendDir "node_modules"))) {
+ Write-Error "Frontend dependencies not found. Run: .\scripts\local_dev.ps1 -Command setup"
+ exit 1
+ }
+
+ # Ensure Azure roles
+ Ensure-AzureLogin
+ Ensure-AzureAIUserRole
+ Ensure-CosmosDBRole
+ Ensure-StorageRole
+
+ Write-Info "Starting backend and frontend in parallel..."
+ Write-Info ""
+ Write-Info "Services:"
+ Write-Info " Backend API: http://localhost:$BackendPort"
+ Write-Info " Frontend: http://localhost:$FrontendPort"
+ Write-Info ""
+ Write-Info "Press Ctrl+C to stop all services"
+ Write-Host ""
+
+ # Start backend as a job
+ $backendJob = Start-Job -ScriptBlock {
+ param($ProjectRoot, $BackendDir, $BackendPort)
+ Set-Location $ProjectRoot
+
+ # Activate venv
+ $activateScript = Join-Path ".venv" "Scripts" "Activate.ps1"
+ & $activateScript
+
+ $env:PYTHONPATH = $BackendDir
+ $env:DOTENV_PATH = Join-Path $ProjectRoot ".env"
+
+ # Load .env
+ Get-Content ".env" | ForEach-Object {
+ if ($_ -match "^([^#][^=]+)=(.*)$") {
+ $name = $matches[1].Trim()
+ $value = $matches[2].Trim()
+ # Strip surrounding quotes (single or double)
+ if (($value.StartsWith('"') -and $value.EndsWith('"')) -or ($value.StartsWith("'") -and $value.EndsWith("'"))) {
+ $value = $value.Substring(1, $value.Length - 2)
+ }
+ Set-Item -Path "env:$name" -Value $value
+ }
+ }
+
+ Set-Location $BackendDir
+ python -m quart --app app:app run --host 0.0.0.0 --port $BackendPort --reload
+ } -ArgumentList $ProjectRoot, $BackendDir, $BackendPort
+
+ # Give backend time to start
+ Start-Sleep -Seconds 2
+
+ # Start frontend as a job
+ $frontendJob = Start-Job -ScriptBlock {
+ param($FrontendDir)
+ Set-Location $FrontendDir
+ npm run dev
+ } -ArgumentList $FrontendDir
+
+ try {
+ # Monitor jobs and output their results
+ while ($true) {
+ Receive-Job -Job $backendJob -ErrorAction SilentlyContinue
+ Receive-Job -Job $frontendJob -ErrorAction SilentlyContinue
+
+ if ($backendJob.State -eq 'Failed' -or $frontendJob.State -eq 'Failed') {
+ Write-Error "One of the services failed"
+ break
+ }
+
+ Start-Sleep -Milliseconds 500
+ }
+ } finally {
+ # Cleanup jobs on exit
+ Write-Info "Stopping all services..."
+ Stop-Job -Job $backendJob -ErrorAction SilentlyContinue
+ Stop-Job -Job $frontendJob -ErrorAction SilentlyContinue
+ Remove-Job -Job $backendJob -ErrorAction SilentlyContinue
+ Remove-Job -Job $frontendJob -ErrorAction SilentlyContinue
+ }
+}
+
+# =============================================================================
+# Build Function
+# =============================================================================
+
+function Invoke-Build {
+ Write-Header "Building for Production"
+
+ Set-Location $FrontendDir
+
+ Write-Info "Building frontend..."
+ npm run build
+
+ Write-Success "Build complete!"
+ Write-Info "Static files are in: $SrcDir\static"
+}
+
+# =============================================================================
+# Clean Function
+# =============================================================================
+
+function Invoke-Clean {
+ Write-Header "Cleaning Development Environment"
+
+ Set-Location $ProjectRoot
+
+ Write-Info "Removing Python cache..."
+ Get-ChildItem -Path . -Include "__pycache__" -Recurse -Directory | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
+ Get-ChildItem -Path . -Include "*.pyc" -Recurse -File | Remove-Item -Force -ErrorAction SilentlyContinue
+
+ Write-Info "Removing node_modules..."
+ Remove-Item -Path (Join-Path $FrontendDir "node_modules") -Recurse -Force -ErrorAction SilentlyContinue
+
+ Write-Info "Removing build artifacts..."
+ Remove-Item -Path (Join-Path $SrcDir "static") -Recurse -Force -ErrorAction SilentlyContinue
+
+ Write-Success "Clean complete!"
+}
+
+# =============================================================================
+# Help Function
+# =============================================================================
+
+function Show-Help {
+ Write-Host "Content Generation Accelerator - Local Development" -ForegroundColor Cyan
+ Write-Host ""
+ Write-Host "Usage: .\local_dev.ps1 [-Command ]"
+ Write-Host ""
+ Write-Host "Commands:"
+ Write-Host " setup Set up virtual environment and install dependencies"
+ Write-Host " env Generate .env file from Azure resources"
+ Write-Host " backend Start only the backend server"
+ Write-Host " frontend Start only the frontend dev server"
+ Write-Host " all Start both backend and frontend (default)"
+ Write-Host " build Build frontend for production"
+ Write-Host " clean Remove cache and build artifacts"
+ Write-Host " help Show this help message"
+ Write-Host ""
+ Write-Host "Environment Variables:"
+ Write-Host " BACKEND_PORT Backend port (default: 5000)"
+ Write-Host " FRONTEND_PORT Frontend port (default: 3000)"
+}
+
+# =============================================================================
+# Main Script
+# =============================================================================
+
+switch ($Command) {
+ "setup" { Invoke-Setup }
+ "env" { Invoke-EnvGeneration }
+ "backend" { Start-Backend }
+ "frontend" { Start-Frontend }
+ "all" { Start-All }
+ "build" { Invoke-Build }
+ "clean" { Invoke-Clean }
+ "help" { Show-Help }
+ default { Start-All }
+}
diff --git a/content-gen/scripts/local_dev.sh b/content-gen/scripts/local_dev.sh
new file mode 100755
index 000000000..8bf811d92
--- /dev/null
+++ b/content-gen/scripts/local_dev.sh
@@ -0,0 +1,587 @@
+#!/bin/bash
+# =============================================================================
+# Local Development Script for Content Generation Accelerator
+# =============================================================================
+#
+# This script sets up and runs the application locally for development.
+#
+# Usage:
+# ./local_dev.sh # Start both backend and frontend
+# ./local_dev.sh backend # Start only the backend
+# ./local_dev.sh frontend # Start only the frontend
+# ./local_dev.sh setup # Set up virtual environment and install dependencies
+# ./local_dev.sh env # Generate .env file from Azure resources
+#
+# Prerequisites:
+# - Python 3.11+
+# - Node.js 18+
+# - Azure CLI (for fetching environment variables)
+# - Azure Developer CLI (azd) - optional, for env generation
+#
+# =============================================================================
+
+set -e
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m' # No Color
+
+# Get the script directory
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
+SRC_DIR="$PROJECT_ROOT/src"
+BACKEND_DIR="$SRC_DIR/backend"
+FRONTEND_DIR="$SRC_DIR/app/frontend"
+
+# Default ports
+BACKEND_PORT=${BACKEND_PORT:-5000}
+FRONTEND_PORT=${FRONTEND_PORT:-3000}
+
+# =============================================================================
+# Helper Functions
+# =============================================================================
+
+print_header() {
+ echo -e "\n${BLUE}============================================${NC}"
+ echo -e "${BLUE}$1${NC}"
+ echo -e "${BLUE}============================================${NC}\n"
+}
+
+print_success() {
+ echo -e "${GREEN}✓ $1${NC}"
+}
+
+print_warning() {
+ echo -e "${YELLOW}⚠ $1${NC}"
+}
+
+print_error() {
+ echo -e "${RED}✗ $1${NC}"
+}
+
+print_info() {
+ echo -e "${BLUE}→ $1${NC}"
+}
+
+check_command() {
+ if ! command -v "$1" &> /dev/null; then
+ print_error "$1 is not installed. Please install it first."
+ return 1
+ fi
+ return 0
+}
+
+# =============================================================================
+# Azure Authentication & Role Functions
+# =============================================================================
+
+ensure_azure_login() {
+ if ! command -v az &> /dev/null; then
+ print_error "Azure CLI is not installed. Please install it first."
+ exit 1
+ fi
+
+ if az account show --query id -o tsv &> /dev/null; then
+ local account_name
+ account_name=$(az account show --query name -o tsv)
+ print_success "Logged in to Azure: $account_name"
+ else
+ print_info "Not logged in. Running az login..."
+ az login --use-device-code --output none
+ if ! az account show --query id -o tsv &> /dev/null; then
+ print_error "Azure login failed."
+ exit 1
+ fi
+ print_success "Azure login successful."
+ fi
+}
+
+ensure_azure_ai_user_role() {
+ print_info "Checking Azure AI User role..."
+
+ local existing_project_id=""
+ local foundry_resource_id=""
+ if [ -f ".env" ]; then
+ existing_project_id=$(grep "^AZURE_EXISTING_AI_PROJECT_RESOURCE_ID=" .env | cut -d'=' -f2- | tr -d '"' | tr -d "'" || echo "")
+ foundry_resource_id=$(grep "^AI_FOUNDRY_RESOURCE_ID=" .env | cut -d'=' -f2- | tr -d '"' | tr -d "'" || echo "")
+ fi
+
+ local scope=""
+ if [ -n "$existing_project_id" ]; then
+ scope="$existing_project_id"
+ elif [ -n "$foundry_resource_id" ]; then
+ scope="$foundry_resource_id"
+ else
+ print_error "Neither AZURE_EXISTING_AI_PROJECT_RESOURCE_ID nor AI_FOUNDRY_RESOURCE_ID found in .env"
+ exit 1
+ fi
+
+ local signed_user_id
+ signed_user_id=$(az ad signed-in-user show --query id -o tsv 2>/dev/null)
+ if [ -z "$signed_user_id" ]; then
+ print_error "Could not get signed-in user ID."
+ exit 1
+ fi
+
+ local role_id="53ca6127-db72-4b80-b1b0-d745d6d5456d"
+ local existing
+ existing=$(MSYS_NO_PATHCONV=1 az role assignment list --assignee "$signed_user_id" --role "$role_id" --scope "$scope" --query "[0].id" -o tsv 2>/dev/null)
+
+ if [ -n "$existing" ]; then
+ print_success "Azure AI User role already assigned."
+ else
+ print_info "Assigning Azure AI User role..."
+ if ! MSYS_NO_PATHCONV=1 az role assignment create --assignee "$signed_user_id" --role "$role_id" --scope "$scope" --output none 2>/dev/null; then
+ print_error "Failed to assign Azure AI User role."
+ exit 1
+ fi
+ print_success "Azure AI User role assigned."
+ fi
+}
+
+ensure_cosmosdb_role() {
+ print_info "Checking Cosmos DB Data Contributor role..."
+
+ local cosmos_account=""
+ local resource_group=""
+ if [ -f ".env" ]; then
+ cosmos_account=$(grep "^COSMOSDB_ACCOUNT_NAME=" .env | cut -d'=' -f2- | tr -d '"' | tr -d "'" || echo "")
+ resource_group=$(grep "^RESOURCE_GROUP_NAME=" .env | cut -d'=' -f2- | tr -d '"' | tr -d "'" || echo "")
+ fi
+
+ if [ -z "$cosmos_account" ] || [ -z "$resource_group" ]; then
+ print_error "COSMOSDB_ACCOUNT_NAME or RESOURCE_GROUP_NAME not found in .env"
+ exit 1
+ fi
+
+ local signed_user_id
+ signed_user_id=$(az ad signed-in-user show --query id -o tsv 2>/dev/null)
+ if [ -z "$signed_user_id" ]; then
+ print_error "Could not get signed-in user ID."
+ exit 1
+ fi
+
+ local role_def_id="00000000-0000-0000-0000-000000000002"
+
+ # Check if role already assigned
+ local existing
+ existing=$(az cosmosdb sql role assignment list --resource-group "$resource_group" --account-name "$cosmos_account" --query "[?principalId=='$signed_user_id' && contains(roleDefinitionId, '$role_def_id')].id | [0]" -o tsv 2>/dev/null)
+
+ if [ -n "$existing" ]; then
+ print_success "Cosmos DB role already assigned."
+ else
+ print_info "Assigning Cosmos DB role..."
+ MSYS_NO_PATHCONV=1 az cosmosdb sql role assignment create --resource-group "$resource_group" --account-name "$cosmos_account" --role-definition-id "$role_def_id" --principal-id "$signed_user_id" --scope "/" --output none 2>/dev/null
+ if [ $? -ne 0 ]; then
+ print_error "Failed to assign Cosmos DB role."
+ exit 1
+ fi
+ print_success "Cosmos DB role assigned."
+ fi
+}
+
+ensure_storage_role() {
+ print_info "Checking Storage Blob Data Contributor role..."
+
+ local storage_account=""
+ local resource_group=""
+ if [ -f ".env" ]; then
+ storage_account=$(grep "^AZURE_BLOB_ACCOUNT_NAME=" .env | cut -d'=' -f2- | tr -d '"' | tr -d "'" || echo "")
+ resource_group=$(grep "^RESOURCE_GROUP_NAME=" .env | cut -d'=' -f2- | tr -d '"' | tr -d "'" || echo "")
+ fi
+
+ if [ -z "$storage_account" ] || [ -z "$resource_group" ]; then
+ print_error "AZURE_BLOB_ACCOUNT_NAME or RESOURCE_GROUP_NAME not found in .env"
+ exit 1
+ fi
+
+ local signed_user_id
+ signed_user_id=$(az ad signed-in-user show --query id -o tsv 2>/dev/null)
+ if [ -z "$signed_user_id" ]; then
+ print_error "Could not get signed-in user ID."
+ exit 1
+ fi
+
+ # Get storage account resource ID
+ local storage_resource_id
+ storage_resource_id=$(az storage account show --name "$storage_account" --resource-group "$resource_group" --query id -o tsv 2>/dev/null)
+ if [ -z "$storage_resource_id" ]; then
+ print_error "Could not get storage account resource ID."
+ exit 1
+ fi
+
+ local role_name="Storage Blob Data Contributor"
+ local existing
+ existing=$(MSYS_NO_PATHCONV=1 az role assignment list --assignee "$signed_user_id" --role "$role_name" --scope "$storage_resource_id" --query "[0].id" -o tsv 2>/dev/null)
+
+ if [ -n "$existing" ]; then
+ print_success "Storage Blob Data Contributor role already assigned."
+ else
+ print_info "Assigning Storage Blob Data Contributor role..."
+ if ! MSYS_NO_PATHCONV=1 az role assignment create --assignee "$signed_user_id" --role "$role_name" --scope "$storage_resource_id" --output none 2>/dev/null; then
+ print_error "Failed to assign Storage Blob Data Contributor role."
+ exit 1
+ fi
+ print_success "Storage Blob Data Contributor role assigned."
+ fi
+}
+
+# =============================================================================
+# Setup Function
+# =============================================================================
+
+setup() {
+ print_header "Setting Up Local Development Environment"
+
+ cd "$PROJECT_ROOT"
+
+ # Check Python
+ print_info "Checking Python installation..."
+ if ! check_command python3; then
+ exit 1
+ fi
+ python_version=$(python3 --version 2>&1 | cut -d' ' -f2)
+ print_success "Python $python_version found"
+
+ # Check Node.js
+ print_info "Checking Node.js installation..."
+ if ! check_command node; then
+ exit 1
+ fi
+ node_version=$(node --version)
+ print_success "Node.js $node_version found"
+
+ # Create virtual environment
+ print_info "Creating Python virtual environment..."
+ if [ ! -d ".venv" ]; then
+ python3 -m venv .venv
+ print_success "Virtual environment created"
+ else
+ print_warning "Virtual environment already exists"
+ fi
+
+ # Activate virtual environment
+ source .venv/bin/activate
+
+ # Install Python dependencies
+ print_info "Installing Python dependencies..."
+ pip install --upgrade pip > /dev/null
+ pip install -r "$BACKEND_DIR/requirements.txt" > /dev/null 2>&1
+ print_success "Python dependencies installed"
+
+ # Install frontend dependencies
+ print_info "Installing frontend dependencies..."
+ cd "$FRONTEND_DIR"
+ npm install > /dev/null 2>&1
+ print_success "Frontend dependencies installed"
+
+ cd "$PROJECT_ROOT"
+
+ # Check for .env file
+ if [ ! -f ".env" ]; then
+ print_warning ".env file not found"
+ if [ -f ".env.template" ]; then
+ print_info "Copying .env.template to .env..."
+ cp .env.template .env
+ print_warning "Please update .env with your Azure resource values"
+ fi
+ else
+ print_success ".env file found"
+ fi
+
+ print_header "Setup Complete!"
+ echo -e "To start development, run: ${GREEN}./scripts/local_dev.sh${NC}"
+ echo -e "Or start individually:"
+ echo -e " Backend: ${GREEN}./scripts/local_dev.sh backend${NC}"
+ echo -e " Frontend: ${GREEN}./scripts/local_dev.sh frontend${NC}"
+}
+
+# =============================================================================
+# Environment Generation Function
+# =============================================================================
+
+generate_env() {
+ print_header "Generating Environment Variables from Azure"
+
+ cd "$PROJECT_ROOT"
+
+ ensure_azure_login
+
+ # If using azd
+ if command -v azd &> /dev/null && [ -f "azure.yaml" ]; then
+ print_info "Azure Developer CLI detected. Generating .env from azd..."
+ azd env get-values > .env.azd 2>/dev/null || true
+
+ if [ -s ".env.azd" ]; then
+ print_success "Environment variables exported to .env.azd"
+ print_info "Merging with .env..."
+
+ # Merge with existing .env
+ if [ -f ".env" ]; then
+ # Backup existing
+ cp .env .env.backup
+ # Append new values (avoiding duplicates)
+ while IFS= read -r line; do
+ key=$(echo "$line" | cut -d'=' -f1)
+ if ! grep -q "^$key=" .env 2>/dev/null; then
+ echo "$line" >> .env
+ fi
+ done < .env.azd
+ else
+ mv .env.azd .env
+ fi
+ rm -f .env.azd
+ print_success "Environment variables merged"
+ fi
+ else
+ print_warning "Azure Developer CLI not found or no azure.yaml"
+ print_info "Please manually update .env with your Azure resource values"
+
+ if [ ! -f ".env" ] && [ -f ".env.template" ]; then
+ cp .env.template .env
+ print_info "Created .env from template"
+ fi
+ fi
+
+ print_success "Environment setup complete"
+ print_warning "Review .env and ensure all required values are set"
+}
+
+# =============================================================================
+# Backend Start Function
+# =============================================================================
+
+start_backend() {
+ print_header "Starting Backend Server"
+
+ cd "$PROJECT_ROOT"
+
+ # Check for .env
+ if [ ! -f ".env" ]; then
+ print_error ".env file not found. Run: ./scripts/local_dev.sh setup"
+ exit 1
+ fi
+
+ # Ensure Azure roles
+ ensure_azure_login
+ ensure_azure_ai_user_role
+ ensure_cosmosdb_role
+ ensure_storage_role
+
+ # Activate virtual environment
+ if [ -d ".venv" ]; then
+ source .venv/bin/activate
+ print_success "Virtual environment activated"
+ else
+ print_error "Virtual environment not found. Run: ./scripts/local_dev.sh setup"
+ exit 1
+ fi
+
+ # Set environment variables
+ export PYTHONPATH="$BACKEND_DIR"
+ export DOTENV_PATH="$PROJECT_ROOT/.env"
+
+ # Load .env file
+ set -a
+ source "$PROJECT_ROOT/.env"
+ set +a
+
+ print_info "Starting Quart backend on port $BACKEND_PORT..."
+ print_info "API will be available at: http://localhost:$BACKEND_PORT"
+ print_info "Health check: http://localhost:$BACKEND_PORT/api/health"
+ echo ""
+
+ cd "$BACKEND_DIR"
+
+ # Use hypercorn for async support (same as production)
+ if command -v hypercorn &> /dev/null; then
+ hypercorn app:app --bind "0.0.0.0:$BACKEND_PORT" --reload
+ else
+ # Fallback to quart run
+ python -m quart --app app:app run --host 0.0.0.0 --port "$BACKEND_PORT" --reload
+ fi
+}
+
+# =============================================================================
+# Frontend Start Function
+# =============================================================================
+
+start_frontend() {
+ print_header "Starting Frontend Development Server"
+
+ cd "$FRONTEND_DIR"
+
+ # Check if node_modules exists
+ if [ ! -d "node_modules" ]; then
+ print_error "Node modules not found. Run: ./scripts/local_dev.sh setup"
+ exit 1
+ fi
+
+ print_info "Starting Vite dev server on port $FRONTEND_PORT..."
+ print_info "Frontend will be available at: http://localhost:$FRONTEND_PORT"
+ print_info "API requests will proxy to: http://localhost:$BACKEND_PORT"
+ echo ""
+
+ npm run dev
+}
+
+# =============================================================================
+# Start Both (using background processes)
+# =============================================================================
+
+start_all() {
+ print_header "Starting Full Development Environment"
+
+ cd "$PROJECT_ROOT"
+
+ # Check prerequisites
+ if [ ! -f ".env" ]; then
+ print_error ".env file not found. Run: ./scripts/local_dev.sh setup"
+ exit 1
+ fi
+
+ if [ ! -d ".venv" ]; then
+ print_error "Virtual environment not found. Run: ./scripts/local_dev.sh setup"
+ exit 1
+ fi
+
+ if [ ! -d "$FRONTEND_DIR/node_modules" ]; then
+ print_error "Frontend dependencies not found. Run: ./scripts/local_dev.sh setup"
+ exit 1
+ fi
+
+ # Ensure Azure authentication and role assignments
+ ensure_azure_login
+ ensure_azure_ai_user_role
+ ensure_cosmosdb_role
+ ensure_storage_role
+
+ print_info "Starting backend and frontend in parallel..."
+ print_info ""
+ print_info "Services:"
+ print_info " Backend API: http://localhost:$BACKEND_PORT"
+ print_info " Frontend: http://localhost:$FRONTEND_PORT"
+ print_info ""
+ print_info "Press Ctrl+C to stop all services"
+ echo ""
+
+ # Trap Ctrl+C to kill all background jobs
+ trap 'echo ""; print_info "Stopping all services..."; kill $(jobs -p) 2>/dev/null; exit 0' INT TERM
+
+ # Start backend in background
+ (
+ cd "$PROJECT_ROOT"
+ source .venv/bin/activate
+ export PYTHONPATH="$SRC_DIR"
+ export DOTENV_PATH="$PROJECT_ROOT/.env"
+ set -a
+ source "$PROJECT_ROOT/.env"
+ set +a
+ cd "$BACKEND_DIR"
+
+ if command -v hypercorn &> /dev/null; then
+ hypercorn app:app --bind "0.0.0.0:$BACKEND_PORT" --reload 2>&1 | sed 's/^/[Backend] /'
+ else
+ python -m quart --app app:app run --host 0.0.0.0 --port "$BACKEND_PORT" --reload 2>&1 | sed 's/^/[Backend] /'
+ fi
+ ) &
+
+ # Give backend a moment to start
+ sleep 2
+
+ # Start frontend in background
+ (
+ cd "$FRONTEND_DIR"
+ npm run dev 2>&1 | sed 's/^/[Frontend] /'
+ ) &
+
+ # Wait for all background jobs
+ wait
+}
+
+# =============================================================================
+# Build Function
+# =============================================================================
+
+build() {
+ print_header "Building for Production"
+
+ cd "$FRONTEND_DIR"
+
+ print_info "Building frontend..."
+ npm run build
+
+ print_success "Build complete!"
+ print_info "Static files are in: $SRC_DIR/static"
+}
+
+# =============================================================================
+# Clean Function
+# =============================================================================
+
+clean() {
+ print_header "Cleaning Development Environment"
+
+ cd "$PROJECT_ROOT"
+
+ print_info "Removing Python cache..."
+ find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
+ find . -type f -name "*.pyc" -delete 2>/dev/null || true
+
+ print_info "Removing node_modules..."
+ rm -rf "$FRONTEND_DIR/node_modules" 2>/dev/null || true
+
+ print_info "Removing build artifacts..."
+ rm -rf "$SRC_DIR/static" 2>/dev/null || true
+
+ print_success "Clean complete!"
+}
+
+# =============================================================================
+# Main Script
+# =============================================================================
+
+case "${1:-}" in
+ setup)
+ setup
+ ;;
+ env)
+ generate_env
+ ;;
+ backend)
+ start_backend
+ ;;
+ frontend)
+ start_frontend
+ ;;
+ build)
+ build
+ ;;
+ clean)
+ clean
+ ;;
+ ""|all)
+ start_all
+ ;;
+ *)
+ echo "Content Generation Accelerator - Local Development"
+ echo ""
+ echo "Usage: $0 [command]"
+ echo ""
+ echo "Commands:"
+ echo " setup Set up virtual environment and install dependencies"
+ echo " env Generate .env file from Azure resources"
+ echo " backend Start only the backend server"
+ echo " frontend Start only the frontend dev server"
+ echo " all Start both backend and frontend (default)"
+ echo " build Build frontend for production"
+ echo " clean Remove cache and build artifacts"
+ echo ""
+ echo "Environment Variables:"
+ echo " BACKEND_PORT Backend port (default: 5000)"
+ echo " FRONTEND_PORT Frontend port (default: 3000)"
+ ;;
+esac
diff --git a/content-gen/scripts/post_deploy.py b/content-gen/scripts/post_deploy.py
new file mode 100644
index 000000000..47ace7d6a
--- /dev/null
+++ b/content-gen/scripts/post_deploy.py
@@ -0,0 +1,570 @@
+#!/usr/bin/env python3
+"""
+Post-Deployment Script for Content Generation Solution Accelerator.
+
+This unified script handles all post-deployment tasks by calling admin APIs
+that run inside the VNet (bypassing firewall restrictions):
+1. Upload product images via /api/admin/upload-images
+2. Load sample product data via /api/admin/load-sample-data
+3. Create and populate Azure AI Search index via /api/admin/create-search-index
+4. Run application health tests
+
+The admin APIs run inside the ACI container which has private endpoint access
+to Blob Storage, Cosmos DB, and Azure AI Search - eliminating the need to
+modify firewall rules.
+
+Usage:
+ python post_deploy.py [options]
+
+ Reads resource names from azd environment variables by default.
+ Can override with explicit arguments if needed.
+
+Options:
+ --resource-group, -g Resource group name (or use RESOURCE_GROUP_NAME env var)
+ --app-name App Service name (or use APP_SERVICE_NAME env var)
+ --storage-account Storage account name (or use AZURE_BLOB_ACCOUNT_NAME env var)
+ --cosmos-account Cosmos DB account name (or use COSMOSDB_ACCOUNT_NAME env var)
+ --search-service AI Search service name (or use AI_SEARCH_SERVICE_NAME env var)
+ --api-key Admin API key (or use ADMIN_API_KEY env var)
+ --skip-images Skip uploading images
+ --skip-data Skip loading sample data
+ --skip-index Skip creating search index
+ --skip-tests Skip application tests
+ --dry-run Show what would be done without executing
+"""
+
+import argparse
+import asyncio
+import base64
+import json
+import os
+import sys
+import time
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Optional, List, Dict, Any
+
+try:
+ import httpx
+except ModuleNotFoundError as exc:
+ missing = getattr(exc, "name", "")
+ print("\nERROR: Missing Python dependency: %s\n" % missing)
+ print("Install post-deploy dependencies first, e.g.:")
+ print(" python -m pip install httpx")
+ sys.exit(2)
+
+
+@dataclass
+class ResourceConfig:
+ """Configuration for Azure resources."""
+ resource_group: str
+ app_service: str
+ app_url: str
+ api_key: str = ""
+ storage_account: str = "" # Only needed for reference, not direct access
+ cosmos_account: str = "" # Only needed for reference, not direct access
+ search_service: str = "" # Only needed for reference, not direct access
+ container_name: str = "product-images"
+
+
+class Colors:
+ """ANSI color codes for terminal output."""
+ GREEN = '\033[92m'
+ RED = '\033[91m'
+ YELLOW = '\033[93m'
+ BLUE = '\033[94m'
+ CYAN = '\033[96m'
+ BOLD = '\033[1m'
+ END = '\033[0m'
+
+
+def print_header(text: str):
+ """Print a section header."""
+ print(f"\n{Colors.BOLD}{Colors.CYAN}{'=' * 70}{Colors.END}")
+ print(f"{Colors.BOLD}{Colors.CYAN}{text}{Colors.END}")
+ print(f"{Colors.BOLD}{Colors.CYAN}{'=' * 70}{Colors.END}\n")
+
+
+def print_step(text: str):
+ """Print a step indicator."""
+ print(f"{Colors.BLUE}→ {text}{Colors.END}")
+
+
+def print_success(text: str):
+ """Print a success message."""
+ print(f"{Colors.GREEN}✓ {text}{Colors.END}")
+
+
+def print_error(text: str):
+ """Print an error message."""
+ print(f"{Colors.RED}✗ {text}{Colors.END}")
+
+
+def print_warning(text: str):
+ """Print a warning message."""
+ print(f"{Colors.YELLOW}⚠ {text}{Colors.END}")
+
+
+def discover_resources(resource_group: str, app_name: str, storage_account: str, cosmos_account: str, search_service: str, api_key: str = "") -> ResourceConfig:
+ """Build resource configuration from provided values (no Azure CLI required)."""
+ print_step("Configuring Azure resources...")
+
+ # Build App URL from app name
+ app_url = f"https://{app_name}.azurewebsites.net"
+
+ config = ResourceConfig(
+ resource_group=resource_group,
+ app_service=app_name,
+ app_url=app_url,
+ api_key=api_key,
+ storage_account=storage_account,
+ cosmos_account=cosmos_account,
+ search_service=search_service
+ )
+
+ print(f" App Service: {config.app_service}")
+ print(f" App URL: {config.app_url}")
+ print(f" Storage Account: {config.storage_account}")
+ print(f" Cosmos DB: {config.cosmos_account}")
+ print(f" AI Search: {config.search_service}")
+ print(f" API Key: {'***' if config.api_key else '(not set - development mode)'}")
+
+ return config
+
+
+def get_api_headers(config: ResourceConfig) -> Dict[str, str]:
+ """Get headers for admin API requests."""
+ headers = {"Content-Type": "application/json"}
+ if config.api_key:
+ headers["X-Admin-API-Key"] = config.api_key
+ return headers
+
+
+async def check_admin_api_health(config: ResourceConfig) -> bool:
+ """Check if the admin API is available."""
+ print_step("Checking admin API health...")
+
+ async with httpx.AsyncClient(timeout=30.0) as client:
+ try:
+ response = await client.get(f"{config.app_url}/api/admin/health")
+ if response.status_code == 200:
+ data = response.json()
+ print_success(f"Admin API healthy (API key required: {data.get('api_key_required', False)})")
+ return True
+ else:
+ print_error(f"Admin API returned {response.status_code}")
+ return False
+ except Exception as e:
+ print_error(f"Failed to reach admin API: {e}")
+ return False
+
+
+async def upload_images(config: ResourceConfig, dry_run: bool = False) -> int:
+ """Upload product images via admin API."""
+ print_header("Uploading Product Images via API")
+
+ images_folder = Path(__file__).parent / "images"
+ if not images_folder.exists():
+ print_error(f"Images folder not found: {images_folder}")
+ return 0
+
+ image_files = (
+ list(images_folder.glob("*.jpg")) +
+ list(images_folder.glob("*.JPG")) +
+ list(images_folder.glob("*.png")) +
+ list(images_folder.glob("*.PNG"))
+ )
+
+ if not image_files:
+ print_warning("No image files found")
+ return 0
+
+ print(f"Found {len(image_files)} image files")
+
+ if dry_run:
+ print_warning("DRY RUN: Would upload images via API")
+ for img in sorted(image_files):
+ print(f" - {img.name}")
+ return len(image_files)
+
+ # Upload images one at a time
+ uploaded_count = 0
+
+ async with httpx.AsyncClient(timeout=120.0) as client: # 2 minute timeout per image
+ for image_path in sorted(image_files):
+ content_type = "image/png" if image_path.suffix.lower() == ".png" else "image/jpeg"
+
+ with open(image_path, "rb") as f:
+ image_bytes = f.read()
+
+ image_data = {
+ "filename": image_path.name,
+ "content_type": content_type,
+ "data": base64.b64encode(image_bytes).decode("utf-8")
+ }
+
+ try:
+ response = await client.post(
+ f"{config.app_url}/api/admin/upload-images",
+ headers=get_api_headers(config),
+ json={"images": [image_data]}
+ )
+
+ if response.status_code == 401:
+ print_error("Unauthorized - check your ADMIN_API_KEY")
+ return uploaded_count
+
+ if response.status_code == 200:
+ uploaded_count += 1
+ else:
+ print_error(f"{image_path.name}: API returned {response.status_code}")
+
+ except Exception as e:
+ print_error(f"{image_path.name}: {e}")
+
+ print(f"\nUploaded {uploaded_count}/{len(image_files)} images")
+ return uploaded_count
+
+
+async def load_sample_data(config: ResourceConfig, dry_run: bool = False) -> int:
+ """Load sample product data via admin API."""
+ print_header("Loading Sample Product Data via API")
+
+ # Sample products (Contoso Paints) - image URLs use proxy path
+ sample_products = [
+ {"product_name": "Snow Veil", "description": "A crisp white with a hint of warmth — perfect for open, modern interiors.", "tags": "soft white, airy, minimal, fresh", "price": 59.95, "sku": "CP-0001", "image_url": f"https://{config.storage_account}.blob.core.windows.net/product-images/SnowVeil.png", "category": "Paint"},
+ {"product_name": "Porcelain Mist", "description": "A gentle off-white that softens spaces with a cozy, inviting glow.", "tags": "warm neutral, beige, cozy, calm", "price": 59.95, "sku": "CP-0002", "image_url": f"https://{config.storage_account}.blob.core.windows.net/product-images/PorcelainMist.png", "category": "Paint"},
+ {"product_name": "Stone Dusk", "description": "A balanced mix of gray and beige, ideal for grounding a room without heaviness.", "tags": "greige, muted, balanced, modern", "price": 59.95, "sku": "CP-0003", "image_url": f"https://{config.storage_account}.blob.core.windows.net/product-images/StoneDusk.png", "category": "Paint"},
+ {"product_name": "Fog Harbor", "description": "A moody gray with blue undertones that feels sleek and contemporary.", "tags": "cool gray, stormy, industrial, sleek", "price": 59.95, "sku": "CP-0004", "image_url": f"https://{config.storage_account}.blob.core.windows.net/product-images/FogHarbor.png", "category": "Paint"},
+ {"product_name": "Graphite Fade", "description": "A dark graphite shade that adds weight and sophistication to feature walls.", "tags": "charcoal, deep gray, moody, bold", "price": 59.95, "sku": "CP-0005", "image_url": f"https://{config.storage_account}.blob.core.windows.net/product-images/GraphiteFade.png", "category": "Paint"},
+ {"product_name": "Obsidian Pearl", "description": "A rich black that creates contrast and drama while staying refined.", "tags": "black, matte, dramatic, luxe", "price": 59.95, "sku": "CP-0006", "image_url": f"https://{config.storage_account}.blob.core.windows.net/product-images/ObsidianPearl.png", "category": "Paint"},
+ {"product_name": "Steel Sky", "description": "A mid-tone slate blue that feels steady, grounded, and architectural.", "tags": "slate, bluish gray, urban, cool", "price": 59.95, "sku": "CP-0007", "image_url": f"https://{config.storage_account}.blob.core.windows.net/product-images/SteelSky.png", "category": "Paint"},
+ {"product_name": "Blue Ash", "description": "A softened navy with gray undertones — stylish but not overpowering.", "tags": "midnight, muted navy, grounding, refined", "price": 59.95, "sku": "CP-0008", "image_url": f"https://{config.storage_account}.blob.core.windows.net/product-images/BlueAsh.png", "category": "Paint"},
+ {"product_name": "Cloud Drift", "description": "An ethereal off-white with subtle gray undertones that evokes soft, drifting clouds.", "tags": "cloud white, soft gray, peaceful, ethereal, airy", "price": 59.95, "sku": "CP-0009", "image_url": f"https://{config.storage_account}.blob.core.windows.net/product-images/CloudDrift.png", "category": "Paint"},
+ {"product_name": "Silver Shore", "description": "A frosty gray with subtle silver hints — sharp, bright, and clean.", "tags": "cool gray, icy, clean, modern", "price": 59.95, "sku": "CP-0010", "image_url": f"https://{config.storage_account}.blob.core.windows.net/product-images/SilverShore.png", "category": "Paint"},
+ {"product_name": "Seafoam Light", "description": "A soft seafoam tone that feels breezy and coastal without being too bold.", "tags": "pale green, misty, fresh, coastal", "price": 59.95, "sku": "CP-0011", "image_url": f"https://{config.storage_account}.blob.core.windows.net/product-images/SeafoamLight.png", "category": "Paint"},
+ {"product_name": "Quiet Moss", "description": "A soft moss green with sage undertones that adds organic calm to any interior palette.", "tags": "moss green, sage, organic, muted, calming", "price": 59.95, "sku": "CP-0012", "image_url": f"https://{config.storage_account}.blob.core.windows.net/product-images/QuietMoss.png", "category": "Paint"},
+ {"product_name": "Olive Stone", "description": "A grounded olive shade that pairs well with natural textures like wood and linen.", "tags": "earthy, muted green, natural, rustic", "price": 59.95, "sku": "CP-0013", "image_url": f"https://{config.storage_account}.blob.core.windows.net/product-images/OliveStone.png", "category": "Paint"},
+ {"product_name": "Verdant Haze", "description": "A muted teal that blends serenity with just enough depth for modern accents.", "tags": "soft teal, subdued, calming, serene", "price": 59.95, "sku": "CP-0014", "image_url": f"https://{config.storage_account}.blob.core.windows.net/product-images/VerdantHaze.png", "category": "Paint"},
+ {"product_name": "Glacier Tint", "description": "A barely-there aqua that brings a refreshing, clean lift to light spaces.", "tags": "pale aqua, refreshing, crisp, airy", "price": 59.95, "sku": "CP-0015", "image_url": f"https://{config.storage_account}.blob.core.windows.net/product-images/GlacierTint.png", "category": "Paint"},
+ {"product_name": "Pine Shadow", "description": "A deep forest green with pine undertones that anchors a room with natural richness.", "tags": "dark green, forest, pine, earthy, grounding, natural", "price": 59.95, "sku": "CP-0016", "image_url": f"https://{config.storage_account}.blob.core.windows.net/product-images/PineShadow.png", "category": "Paint"},
+ ]
+
+ print(f"Sample products: {len(sample_products)} Contoso Paints items")
+
+ if dry_run:
+ print_warning("DRY RUN: Would load products via API")
+ for p in sample_products:
+ print(f" - {p['product_name']} ({p['sku']})")
+ return len(sample_products)
+
+ # Call admin API
+ print_step("Calling /api/admin/load-sample-data...")
+
+ async with httpx.AsyncClient(timeout=120.0) as client:
+ try:
+ response = await client.post(
+ f"{config.app_url}/api/admin/load-sample-data",
+ headers=get_api_headers(config),
+ json={
+ "products": sample_products,
+ "clear_existing": True
+ }
+ )
+
+ if response.status_code == 401:
+ print_error("Unauthorized - check your ADMIN_API_KEY")
+ return 0
+
+ if response.status_code != 200:
+ print_error(f"API returned {response.status_code}: {response.text[:500]}")
+ return 0
+
+ result = response.json()
+ loaded = result.get("loaded", 0)
+ failed = result.get("failed", 0)
+ deleted = result.get("deleted", 0)
+
+ if deleted > 0:
+ print(f" Deleted {deleted} existing products")
+
+ for r in result.get("results", []):
+ if r.get("status") == "loaded":
+ print_success(f"{r['product_name']} ({r['sku']})")
+ else:
+ print_error(f"{r['product_name']}: {r.get('error', 'Unknown error')}")
+
+ print(f"\nLoaded {loaded}/{len(sample_products)} products ({failed} failed)")
+ return loaded
+
+ except Exception as e:
+ print_error(f"API call failed: {e}")
+ return 0
+
+
+async def create_search_index(config: ResourceConfig, dry_run: bool = False) -> int:
+ """Create and populate the search index via admin API."""
+ print_header("Creating Search Index via API")
+
+ if dry_run:
+ print_warning("DRY RUN: Would create search index via API")
+ return 0
+
+ # Call admin API
+ print_step("Calling /api/admin/create-search-index...")
+
+ async with httpx.AsyncClient(timeout=180.0) as client: # 3 minute timeout
+ try:
+ response = await client.post(
+ f"{config.app_url}/api/admin/create-search-index",
+ headers=get_api_headers(config),
+ json={"reindex_all": True}
+ )
+
+ if response.status_code == 401:
+ print_error("Unauthorized - check your ADMIN_API_KEY")
+ return 0
+
+ if response.status_code != 200:
+ print_error(f"API returned {response.status_code}: {response.text[:500]}")
+ return 0
+
+ result = response.json()
+ indexed = result.get("indexed", 0)
+ failed = result.get("failed", 0)
+ index_name = result.get("index_name", "products")
+
+ print(f" Index name: {index_name}")
+
+ for r in result.get("results", []):
+ if r.get("status") == "indexed":
+ print_success(f"{r['product_name']} ({r['sku']})")
+ else:
+ print_error(f"{r['product_name']}: {r.get('error', 'Unknown error')}")
+
+ print(f"\nIndexed {indexed} products ({failed} failed)")
+ return indexed
+
+ except Exception as e:
+ print_error(f"API call failed: {e}")
+ return 0
+
+
+async def run_application_tests(config: ResourceConfig, dry_run: bool = False) -> Dict[str, bool]:
+ """Run application health tests."""
+ print_header("Running Application Tests")
+
+ if dry_run:
+ print_warning("DRY RUN: Would run application tests")
+ return {}
+
+ app_url = config.app_url
+ print(f"App URL: {app_url}")
+ print()
+
+ results = {}
+
+ async with httpx.AsyncClient(timeout=30.0) as client:
+ # Test 1: Frontend
+ print_step("Testing Frontend (GET /)")
+ try:
+ response = await client.get(f"{app_url}/")
+ if response.status_code == 200 and "" in response.text:
+ print_success("Frontend serving HTML")
+ results["frontend"] = True
+ else:
+ print_error(f"Unexpected response: {response.status_code}")
+ results["frontend"] = False
+ except Exception as e:
+ print_error(f"Failed: {e}")
+ results["frontend"] = False
+
+ # Test 2: Health endpoint
+ print_step("Testing Health (GET /api/health)")
+ try:
+ response = await client.get(f"{app_url}/api/health")
+ if response.status_code == 200:
+ print_success(f"Health OK: {response.json()}")
+ results["health"] = True
+ else:
+ print_warning(f"Health returned {response.status_code}")
+ results["health"] = False
+ except Exception as e:
+ print_warning(f"Health check failed: {e}")
+ results["health"] = False
+
+ # Test 3: Brief Parsing (POST /api/brief/parse)
+ print_step("Testing Brief Parsing (POST /api/brief/parse)")
+ try:
+ response = await client.post(
+ f"{app_url}/api/brief/parse",
+ json={"brief_text": "Create an ad for calm interior paint for homeowners."},
+ headers={"Content-Type": "application/json"}
+ )
+ if response.status_code == 200:
+ data = response.json()
+ if "brief" in data:
+ print_success(f"Brief parsed: {data['brief'].get('overview', '')[:60]}...")
+ results["brief_parse"] = True
+ else:
+ print_error(f"Unexpected response: {data}")
+ results["brief_parse"] = False
+ else:
+ print_error(f"Failed: {response.status_code} - {response.text[:200]}")
+ results["brief_parse"] = False
+ except Exception as e:
+ print_error(f"Failed: {e}")
+ results["brief_parse"] = False
+
+ # Test 4: Product Search (GET /api/products)
+ print_step("Testing Product Search (GET /api/products?search=blue)")
+ try:
+ response = await client.get(f"{app_url}/api/products?search=blue&limit=3")
+ if response.status_code == 200:
+ data = response.json()
+ count = data.get("count", 0)
+ products = data.get("products", [])
+ if count > 0:
+ print_success(f"Found {count} products: {[p['product_name'] for p in products]}")
+ results["product_search"] = True
+ else:
+ print_warning("No products found (search index may need time)")
+ results["product_search"] = False
+ else:
+ print_error(f"Failed: {response.status_code}")
+ results["product_search"] = False
+ except Exception as e:
+ print_error(f"Failed: {e}")
+ results["product_search"] = False
+
+ # Test 5: Product List (GET /api/products)
+ print_step("Testing Product List (GET /api/products)")
+ try:
+ response = await client.get(f"{app_url}/api/products?limit=5")
+ if response.status_code == 200:
+ data = response.json()
+ count = data.get("count", 0)
+ print_success(f"Listed {count} products")
+ results["product_list"] = True
+ else:
+ print_error(f"Failed: {response.status_code}")
+ results["product_list"] = False
+ except Exception as e:
+ print_error(f"Failed: {e}")
+ results["product_list"] = False
+
+ # Summary
+ print()
+ passed = sum(1 for v in results.values() if v)
+ total = len(results)
+
+ if passed == total:
+ print_success(f"All {total} tests passed!")
+ else:
+ print_warning(f"{passed}/{total} tests passed")
+
+ return results
+
+
+def print_summary(
+ images_uploaded: int,
+ products_loaded: int,
+ products_indexed: int,
+ test_results: Dict[str, bool]
+):
+ """Print final summary."""
+ print_header("Post-Deployment Summary")
+
+ print(f" Images Uploaded: {images_uploaded}")
+ print(f" Products Loaded: {products_loaded}")
+ print(f" Products Indexed: {products_indexed}")
+
+ if test_results:
+ print()
+ print(" Application Tests:")
+ for test, passed in test_results.items():
+ status = f"{Colors.GREEN}PASS{Colors.END}" if passed else f"{Colors.RED}FAIL{Colors.END}"
+ print(f" {test}: {status}")
+
+ print()
+
+
+async def main():
+ parser = argparse.ArgumentParser(
+ description="Post-deployment script for Content Generation Solution Accelerator",
+ formatter_class=argparse.RawDescriptionHelpFormatter
+ )
+ parser.add_argument("-g", "--resource-group", help="Azure resource group name (reads from RESOURCE_GROUP_NAME if not provided)")
+ parser.add_argument("--app-name", help="App Service name (reads from APP_SERVICE_NAME if not provided)")
+ parser.add_argument("--storage-account", help="Storage account name (reads from AZURE_BLOB_ACCOUNT_NAME if not provided)")
+ parser.add_argument("--cosmos-account", help="Cosmos DB account name (reads from COSMOSDB_ACCOUNT_NAME if not provided)")
+ parser.add_argument("--search-service", help="AI Search service name (reads from AI_SEARCH_SERVICE_NAME if not provided)")
+ parser.add_argument("--api-key", help="Admin API key (or set ADMIN_API_KEY env var)")
+ parser.add_argument("--skip-images", action="store_true", help="Skip uploading images")
+ parser.add_argument("--skip-data", action="store_true", help="Skip loading sample data")
+ parser.add_argument("--skip-index", action="store_true", help="Skip creating search index")
+ parser.add_argument("--skip-tests", action="store_true", help="Skip application tests")
+ parser.add_argument("--dry-run", action="store_true", help="Show what would be done")
+
+ args = parser.parse_args()
+
+ # Get values from args or environment variables (args take precedence)
+ resource_group = args.resource_group or os.environ.get("RESOURCE_GROUP_NAME", "")
+ app_name = args.app_name or os.environ.get("APP_SERVICE_NAME", "")
+ storage_account = args.storage_account or os.environ.get("AZURE_BLOB_ACCOUNT_NAME", "")
+ cosmos_account = args.cosmos_account or os.environ.get("COSMOSDB_ACCOUNT_NAME", "")
+ search_service = args.search_service or os.environ.get("AI_SEARCH_SERVICE_NAME", "")
+ api_key = args.api_key or os.environ.get("ADMIN_API_KEY", "")
+
+ # Validate required values are present
+ if not resource_group or not app_name or not storage_account or not cosmos_account or not search_service:
+ print_error("Missing required resource names. Provide via arguments or set environment variables: RESOURCE_GROUP_NAME (--resource-group), APP_SERVICE_NAME (--app-name), AZURE_BLOB_ACCOUNT_NAME (--storage-account), COSMOSDB_ACCOUNT_NAME (--cosmos-account), AI_SEARCH_SERVICE_NAME (--search-service)")
+ sys.exit(1)
+
+ print_header("Content Generation Solution Accelerator - Post Deployment")
+ print(f"Resource Group: {resource_group}")
+ print(f"Dry Run: {args.dry_run}")
+ print()
+
+ # Configure resources
+ config = discover_resources(resource_group, app_name, storage_account, cosmos_account, search_service, api_key)
+
+ # Check admin API health first
+ if not args.dry_run:
+ if not await check_admin_api_health(config):
+ print_error("Admin API not available. Make sure the app is deployed and running.")
+ print("You can check the app at: " + config.app_url)
+ sys.exit(1)
+
+ images_uploaded = 0
+ products_loaded = 0
+ products_indexed = 0
+ test_results = {}
+
+ try:
+ # Upload images via API
+ if not args.skip_images:
+ images_uploaded = await upload_images(config, dry_run=args.dry_run)
+
+ # Load sample data via API
+ if not args.skip_data:
+ products_loaded = await load_sample_data(config, dry_run=args.dry_run)
+
+ # Create search index via API
+ if not args.skip_index:
+ products_indexed = await create_search_index(config, dry_run=args.dry_run)
+
+ # Run application tests
+ if not args.skip_tests:
+ test_results = await run_application_tests(config, dry_run=args.dry_run)
+
+ except Exception as e:
+ print_error(f"Error during post-deployment: {e}")
+ raise
+
+ # Print summary
+ print_summary(images_uploaded, products_loaded, products_indexed, test_results)
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/content-gen/scripts/product_ingestion.py b/content-gen/scripts/product_ingestion.py
new file mode 100644
index 000000000..671d16505
--- /dev/null
+++ b/content-gen/scripts/product_ingestion.py
@@ -0,0 +1,467 @@
+#!/usr/bin/env python3
+"""
+Product Data Ingestion Script for Intelligent Content Generation Accelerator
+
+This script handles:
+1. Loading product data from CSV/JSON files
+2. Uploading product images to Azure Blob Storage
+3. Indexing product data in Azure AI Search
+4. Storing product metadata in Azure Cosmos DB
+
+Usage:
+ python product_ingestion.py --data-path ./sample_data --env-file .env
+
+Requirements:
+ - Azure credentials configured (via environment or managed identity)
+ - Required environment variables set in .env or Azure Key Vault
+"""
+
+import argparse
+import asyncio
+import json
+import logging
+import os
+import sys
+from dataclasses import dataclass, field
+from pathlib import Path
+from typing import Any
+
+# Configure logging
+logging.basicConfig(
+ level=logging.INFO,
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
+)
+logger = logging.getLogger(__name__)
+
+
+@dataclass
+class ProductData:
+ """Product data model."""
+ id: str
+ name: str
+ description: str
+ category: str
+ price: float
+ image_url: str = ""
+ attributes: dict = field(default_factory=dict)
+ tags: list = field(default_factory=list)
+
+
+@dataclass
+class IngestionConfig:
+ """Configuration for product ingestion."""
+ storage_account_name: str
+ storage_container_name: str
+ cosmos_endpoint: str
+ cosmos_database: str
+ cosmos_container: str
+ search_service_name: str
+ search_index_name: str
+ data_path: Path
+ batch_size: int = 100
+
+
+def load_environment(env_file: str | None = None) -> IngestionConfig:
+ """Load configuration from environment variables or .env file."""
+ if env_file and Path(env_file).exists():
+ from dotenv import load_dotenv
+ load_dotenv(env_file)
+ logger.info(f"Loaded environment from {env_file}")
+
+ return IngestionConfig(
+ storage_account_name=os.getenv("AZURE_BLOB_ACCOUNT_NAME", ""),
+ storage_container_name=os.getenv("AZURE_BLOB_PRODUCT_IMAGES_CONTAINER", "product-images"),
+ cosmos_endpoint=os.getenv("AZURE_COSMOS_ENDPOINT", ""),
+ cosmos_database=os.getenv("AZURE_COSMOS_DATABASE_NAME", "content_generation_db"),
+ cosmos_container=os.getenv("AZURE_COSMOS_PRODUCTS_CONTAINER", "products"),
+ search_service_name=os.getenv("AI_SEARCH_SERVICE_NAME", ""),
+ search_index_name=os.getenv("AZURE_AI_SEARCH_PRODUCTS_INDEX", "product_index"),
+ data_path=Path(os.getenv("DATA_PATH", "./sample_data")),
+ batch_size=int(os.getenv("BATCH_SIZE", "100"))
+ )
+
+
+def validate_config(config: IngestionConfig) -> bool:
+ """Validate configuration has required values."""
+ required_fields = [
+ ("storage_account_name", config.storage_account_name),
+ ("cosmos_endpoint", config.cosmos_endpoint),
+ ("search_service_name", config.search_service_name),
+ ]
+
+ missing = [name for name, value in required_fields if not value]
+
+ if missing:
+ logger.error(f"Missing required configuration: {', '.join(missing)}")
+ return False
+
+ if not config.data_path.exists():
+ logger.error(f"Data path does not exist: {config.data_path}")
+ return False
+
+ return True
+
+
+def load_products_from_json(file_path: Path) -> list[ProductData]:
+ """Load products from a JSON file."""
+ logger.info(f"Loading products from {file_path}")
+
+ with open(file_path, "r", encoding="utf-8") as f:
+ data = json.load(f)
+
+ products = []
+ items = data if isinstance(data, list) else data.get("products", [])
+
+ for item in items:
+ products.append(ProductData(
+ id=str(item.get("id", "")),
+ name=item.get("name", ""),
+ description=item.get("description", ""),
+ category=item.get("category", ""),
+ price=float(item.get("price", 0.0)),
+ image_url=item.get("image_url", ""),
+ attributes=item.get("attributes", {}),
+ tags=item.get("tags", [])
+ ))
+
+ logger.info(f"Loaded {len(products)} products from {file_path.name}")
+ return products
+
+
+def load_products_from_csv(file_path: Path) -> list[ProductData]:
+ """Load products from a CSV file."""
+ import csv
+
+ logger.info(f"Loading products from {file_path}")
+ products = []
+
+ with open(file_path, "r", encoding="utf-8") as f:
+ reader = csv.DictReader(f)
+ for row in reader:
+ products.append(ProductData(
+ id=str(row.get("id", "")),
+ name=row.get("name", ""),
+ description=row.get("description", ""),
+ category=row.get("category", ""),
+ price=float(row.get("price", 0.0)),
+ image_url=row.get("image_url", ""),
+ attributes=json.loads(row.get("attributes", "{}")),
+ tags=row.get("tags", "").split(",") if row.get("tags") else []
+ ))
+
+ logger.info(f"Loaded {len(products)} products from {file_path.name}")
+ return products
+
+
+def load_all_products(data_path: Path) -> list[ProductData]:
+ """Load all products from JSON and CSV files in the data directory."""
+ products = []
+
+ # Load JSON files
+ for json_file in data_path.glob("*.json"):
+ try:
+ products.extend(load_products_from_json(json_file))
+ except Exception as e:
+ logger.error(f"Error loading {json_file}: {e}")
+
+ # Load CSV files
+ for csv_file in data_path.glob("*.csv"):
+ try:
+ products.extend(load_products_from_csv(csv_file))
+ except Exception as e:
+ logger.error(f"Error loading {csv_file}: {e}")
+
+ logger.info(f"Total products loaded: {len(products)}")
+ return products
+
+
+async def upload_images_to_blob(
+ config: IngestionConfig,
+ products: list[ProductData],
+ images_path: Path | None = None
+) -> list[ProductData]:
+ """Upload product images to Azure Blob Storage."""
+ try:
+ from azure.identity import DefaultAzureCredential
+ from azure.storage.blob.aio import BlobServiceClient
+ except ImportError:
+ logger.warning("Azure storage SDK not installed. Skipping image upload.")
+ return products
+
+ if images_path is None:
+ images_path = config.data_path / "images"
+
+ if not images_path.exists():
+ logger.info(f"Images path {images_path} does not exist. Skipping image upload.")
+ return products
+
+ credential = DefaultAzureCredential()
+ account_url = f"https://{config.storage_account_name}.blob.core.windows.net"
+
+ async with BlobServiceClient(account_url, credential) as blob_service:
+ container_client = blob_service.get_container_client(config.storage_container_name)
+
+ for product in products:
+ # Check for local image file
+ image_extensions = [".jpg", ".jpeg", ".png", ".gif", ".webp"]
+ for ext in image_extensions:
+ local_image = images_path / f"{product.id}{ext}"
+ if local_image.exists():
+ blob_name = f"{product.id}{ext}"
+ blob_client = container_client.get_blob_client(blob_name)
+
+ with open(local_image, "rb") as data:
+ await blob_client.upload_blob(data, overwrite=True)
+
+ product.image_url = f"{account_url}/{config.storage_container_name}/{blob_name}"
+ logger.debug(f"Uploaded image for product {product.id}")
+ break
+
+ logger.info(f"Image upload completed for {len(products)} products")
+ return products
+
+
+async def index_products_in_search(
+ config: IngestionConfig,
+ products: list[ProductData]
+) -> int:
+ """Index products in Azure AI Search."""
+ try:
+ from azure.identity import DefaultAzureCredential
+ from azure.search.documents import SearchClient
+ from azure.search.documents.indexes import SearchIndexClient
+ from azure.search.documents.indexes.models import (
+ SearchIndex,
+ SearchableField,
+ SimpleField,
+ SearchFieldDataType,
+ SemanticConfiguration,
+ SemanticField,
+ SemanticPrioritizedFields,
+ SemanticSearch,
+ )
+ except ImportError:
+ logger.error("Azure search SDK not installed. Cannot index products.")
+ return 0
+
+ credential = DefaultAzureCredential()
+ endpoint = f"https://{config.search_service_name}.search.windows.net"
+
+ # Create or update the search index
+ index_client = SearchIndexClient(endpoint, credential)
+
+ fields = [
+ SimpleField(name="id", type=SearchFieldDataType.String, key=True),
+ SearchableField(name="name", type=SearchFieldDataType.String),
+ SearchableField(name="description", type=SearchFieldDataType.String),
+ SearchableField(name="category", type=SearchFieldDataType.String, filterable=True, facetable=True),
+ SimpleField(name="price", type=SearchFieldDataType.Double, filterable=True, sortable=True),
+ SimpleField(name="image_url", type=SearchFieldDataType.String),
+ SearchableField(name="tags", type=SearchFieldDataType.Collection(SearchFieldDataType.String), filterable=True),
+ ]
+
+ semantic_config = SemanticConfiguration(
+ name="my-semantic-config",
+ prioritized_fields=SemanticPrioritizedFields(
+ title_field=SemanticField(field_name="name"),
+ content_fields=[SemanticField(field_name="description")],
+ keywords_fields=[SemanticField(field_name="tags")]
+ )
+ )
+
+ semantic_search = SemanticSearch(configurations=[semantic_config])
+
+ index = SearchIndex(
+ name=config.search_index_name,
+ fields=fields,
+ semantic_search=semantic_search
+ )
+
+ index_client.create_or_update_index(index)
+ logger.info(f"Created/updated search index: {config.search_index_name}")
+
+ # Upload documents in batches
+ search_client = SearchClient(endpoint, config.search_index_name, credential)
+
+ documents = [
+ {
+ "id": p.id,
+ "name": p.name,
+ "description": p.description,
+ "category": p.category,
+ "price": p.price,
+ "image_url": p.image_url,
+ "tags": p.tags
+ }
+ for p in products
+ ]
+
+ indexed_count = 0
+ for i in range(0, len(documents), config.batch_size):
+ batch = documents[i:i + config.batch_size]
+ result = search_client.upload_documents(batch)
+ indexed_count += len([r for r in result if r.succeeded])
+ logger.info(f"Indexed batch {i // config.batch_size + 1}: {len(batch)} documents")
+
+ logger.info(f"Successfully indexed {indexed_count} products in Azure AI Search")
+ return indexed_count
+
+
+async def store_products_in_cosmos(
+ config: IngestionConfig,
+ products: list[ProductData]
+) -> int:
+ """Store product metadata in Azure Cosmos DB."""
+ try:
+ from azure.identity import DefaultAzureCredential
+ from azure.cosmos.aio import CosmosClient
+ except ImportError:
+ logger.error("Azure Cosmos SDK not installed. Cannot store products.")
+ return 0
+
+ credential = DefaultAzureCredential()
+ endpoint = f"https://{config.cosmos_endpoint}.documents.azure.com:443/"
+
+ async with CosmosClient(endpoint, credential) as client:
+ database = client.get_database_client(config.cosmos_database)
+ container = database.get_container_client(config.cosmos_container)
+
+ stored_count = 0
+ for product in products:
+ item = {
+ "id": product.id,
+ "name": product.name,
+ "description": product.description,
+ "category": product.category,
+ "price": product.price,
+ "image_url": product.image_url,
+ "attributes": product.attributes,
+ "tags": product.tags
+ }
+
+ try:
+ await container.upsert_item(item)
+ stored_count += 1
+ except Exception as e:
+ logger.error(f"Failed to store product {product.id}: {e}")
+
+ logger.info(f"Successfully stored {stored_count} products in Cosmos DB")
+ return stored_count
+
+
+async def run_ingestion(config: IngestionConfig) -> dict[str, Any]:
+ """Run the full product ingestion pipeline."""
+ results = {
+ "products_loaded": 0,
+ "images_uploaded": 0,
+ "products_indexed": 0,
+ "products_stored": 0,
+ "errors": []
+ }
+
+ # Step 1: Load products
+ products = load_all_products(config.data_path)
+ results["products_loaded"] = len(products)
+
+ if not products:
+ logger.warning("No products found to ingest")
+ return results
+
+ # Step 2: Upload images
+ try:
+ products = await upload_images_to_blob(config, products)
+ results["images_uploaded"] = len([p for p in products if p.image_url])
+ except Exception as e:
+ logger.error(f"Image upload failed: {e}")
+ results["errors"].append(f"Image upload: {str(e)}")
+
+ # Step 3: Index in AI Search
+ try:
+ results["products_indexed"] = await index_products_in_search(config, products)
+ except Exception as e:
+ logger.error(f"Search indexing failed: {e}")
+ results["errors"].append(f"Search indexing: {str(e)}")
+
+ # Step 4: Store in Cosmos DB
+ try:
+ results["products_stored"] = await store_products_in_cosmos(config, products)
+ except Exception as e:
+ logger.error(f"Cosmos DB storage failed: {e}")
+ results["errors"].append(f"Cosmos DB: {str(e)}")
+
+ return results
+
+
+def main():
+ """Main entry point."""
+ parser = argparse.ArgumentParser(
+ description="Product Data Ingestion for Content Generation Accelerator"
+ )
+ parser.add_argument(
+ "--data-path",
+ type=str,
+ help="Path to the directory containing product data files"
+ )
+ parser.add_argument(
+ "--env-file",
+ type=str,
+ default=".env",
+ help="Path to .env file with configuration"
+ )
+ parser.add_argument(
+ "--batch-size",
+ type=int,
+ default=100,
+ help="Batch size for indexing operations"
+ )
+ parser.add_argument(
+ "-v", "--verbose",
+ action="store_true",
+ help="Enable verbose logging"
+ )
+
+ args = parser.parse_args()
+
+ if args.verbose:
+ logging.getLogger().setLevel(logging.DEBUG)
+
+ # Load configuration
+ config = load_environment(args.env_file)
+
+ # Override with command line arguments
+ if args.data_path:
+ config.data_path = Path(args.data_path)
+ if args.batch_size:
+ config.batch_size = args.batch_size
+
+ # Validate configuration
+ if not validate_config(config):
+ logger.error("Configuration validation failed")
+ sys.exit(1)
+
+ # Run ingestion
+ logger.info("Starting product ingestion...")
+ results = asyncio.run(run_ingestion(config))
+
+ # Print summary
+ logger.info("=" * 50)
+ logger.info("Ingestion Summary:")
+ logger.info(f" Products loaded: {results['products_loaded']}")
+ logger.info(f" Images uploaded: {results['images_uploaded']}")
+ logger.info(f" Products indexed: {results['products_indexed']}")
+ logger.info(f" Products stored: {results['products_stored']}")
+
+ if results["errors"]:
+ logger.warning(f" Errors: {len(results['errors'])}")
+ for error in results["errors"]:
+ logger.warning(f" - {error}")
+
+ logger.info("=" * 50)
+
+ # Exit with error code if there were failures
+ if results["errors"]:
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/content-gen/scripts/requirements-post-deploy.txt b/content-gen/scripts/requirements-post-deploy.txt
new file mode 100644
index 000000000..66386f3bc
--- /dev/null
+++ b/content-gen/scripts/requirements-post-deploy.txt
@@ -0,0 +1,9 @@
+# Dependencies needed to run scripts/post_deploy.py locally
+#
+# Installs the core application dependencies first, then adds post-deploy-only deps.
+
+-r ../src/backend/requirements.txt
+
+# Post-deploy script deps
+azure-search-documents>=11.6.0
+httpx>=0.28.0
diff --git a/content-gen/scripts/sample_content_generation.py b/content-gen/scripts/sample_content_generation.py
new file mode 100644
index 000000000..05b02569c
--- /dev/null
+++ b/content-gen/scripts/sample_content_generation.py
@@ -0,0 +1,361 @@
+#!/usr/bin/env python3
+"""
+Sample Content Generation Script
+
+This script demonstrates how to use the ContentOrchestrator to generate
+complete marketing content packages including text and images.
+
+Prerequisites:
+1. Set up environment variables (or use a .env file):
+ - AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint
+ - AZURE_OPENAI_GPT_MODEL: GPT model deployment name
+ - AZURE_OPENAI_GPT_IMAGE_ENDPOINT: (Optional) Endpoint for images
+ - AZURE_OPENAI_IMAGE_MODEL: Image model name (e.g., gpt-image-1)
+ - AZURE_COSMOS_ENDPOINT: Your CosmosDB endpoint
+ - AZURE_COSMOS_DATABASE_NAME: content-generation
+ - AZURE_COSMOS_CONVERSATIONS_CONTAINER: conversations
+
+2. Ensure you have RBAC access:
+ - "Cognitive Services OpenAI User" role on the Azure OpenAI resource
+ - "Cosmos DB Built-in Data Contributor" on CosmosDB (if using products)
+
+Usage:
+ python sample_content_generation.py
+ python sample_content_generation.py --no-images
+ python sample_content_generation.py --output results.json
+"""
+
+import asyncio
+import argparse
+import json
+import os
+import sys
+from datetime import datetime
+from pathlib import Path
+
+# Add the backend directory to the path
+backend_path = Path(__file__).parent.parent / "src" / "backend"
+sys.path.insert(0, str(backend_path))
+
+# Now import the orchestrator and models
+from orchestrator import ContentOrchestrator
+from models import CreativeBrief
+
+
+def create_sample_brief() -> CreativeBrief:
+ """Create a sample creative brief for testing."""
+ return CreativeBrief(
+ overview="Spring home refresh campaign promoting interior paint colors",
+ objectives="Drive awareness and consideration for spring paint collection, increase website traffic by 20%",
+ target_audience="Homeowners aged 30-55, interested in DIY home improvement and interior design",
+ key_message="Transform your home this spring with our fresh, on-trend paint colors that bring warmth and style to any room",
+ tone_and_style="Inspiring, approachable, and aspirational. Use warm, inviting language that speaks to the joy of home improvement",
+ deliverable="Social media carousel post (3-5 images) with captions for social media",
+ timelines="Launch by March 15th for spring campaign",
+ visual_guidelines="Bright, airy rooms with natural lighting. Show before/after transformations. Feature paint swatches in context. Modern, clean aesthetic",
+ cta="Shop the Spring Collection - Visit our website for color inspiration",
+ raw_input="Sample brief for testing content generation"
+ )
+
+
+def create_sample_products() -> list:
+ """Create sample product data for testing."""
+ return [
+ {
+ "id": "sample-1",
+ "product_name": "Morning Mist",
+ "description": "A soft, ethereal blue-gray that evokes early morning calm. Perfect for bedrooms and living spaces.",
+ "tags": "blue, gray, soft, calming, bedroom, living room",
+ "price": 45.99,
+ "image_url": "https://example.com/morning-mist.jpg"
+ },
+ {
+ "id": "sample-2",
+ "product_name": "Sunlit Meadow",
+ "description": "A warm, golden yellow that brings sunshine indoors. Ideal for kitchens and breakfast nooks.",
+ "tags": "yellow, warm, sunny, kitchen, cheerful",
+ "price": 42.99,
+ "image_url": "https://example.com/sunlit-meadow.jpg"
+ },
+ {
+ "id": "sample-3",
+ "product_name": "Forest Haven",
+ "description": "A rich, deep green inspired by lush forest canopies. Creates a sophisticated, grounding atmosphere.",
+ "tags": "green, deep, sophisticated, nature, accent wall",
+ "price": 48.99,
+ "image_url": "https://example.com/forest-haven.jpg"
+ }
+ ]
+
+
+async def generate_content_sample(
+ brief: CreativeBrief = None,
+ products: list = None,
+ generate_images: bool = True,
+ output_path: str = None
+) -> dict:
+ """
+ Generate a complete content package using the orchestrator.
+
+ Args:
+ brief: Creative brief (uses sample if not provided)
+ products: Products to feature (uses samples if not provided)
+ generate_images: Whether to generate images
+ output_path: Path to save results JSON (optional)
+
+ Returns:
+ Dictionary with generation results
+ """
+ # Use defaults if not provided
+ brief = brief or create_sample_brief()
+ products = products or create_sample_products()
+
+ print(f"\n{'='*70}")
+ print("CONTENT GENERATION SAMPLE")
+ print(f"{'='*70}")
+ print(f"\nCreative Brief Overview: {brief.overview}")
+ print(f"Target Audience: {brief.target_audience}")
+ print(f"Deliverable: {brief.deliverable}")
+ print(f"Products: {len(products)} items")
+ print(f"Generate Images: {generate_images}")
+
+ print(f"\n{'='*70}")
+ print("Initializing Content Orchestrator...")
+ print(f"{'='*70}\n")
+
+ # Create and initialize the orchestrator
+ orchestrator = ContentOrchestrator()
+ orchestrator.initialize()
+
+ print("Orchestrator initialized successfully!")
+ print("\nGenerating content...\n")
+
+ # Generate the content
+ start_time = datetime.now()
+
+ results = await orchestrator.generate_content(
+ brief=brief,
+ products=products,
+ generate_images=generate_images
+ )
+
+ elapsed = (datetime.now() - start_time).total_seconds()
+
+ # Display results
+ print(f"\n{'='*70}")
+ print("GENERATION RESULTS")
+ print(f"{'='*70}")
+ print(f"\nTime elapsed: {elapsed:.1f} seconds")
+
+ # Text content
+ if results.get("text_content"):
+ print(f"\n{'─'*40}")
+ print("TEXT CONTENT:")
+ print(f"{'─'*40}")
+ text_content = results["text_content"]
+ # Show first 1000 chars
+ if len(text_content) > 1000:
+ print(text_content[:1000] + "\n\n[...truncated...]")
+ else:
+ print(text_content)
+
+ # Image prompt
+ if results.get("image_prompt"):
+ print(f"\n{'─'*40}")
+ print("IMAGE PROMPT:")
+ print(f"{'─'*40}")
+ print(results["image_prompt"][:500] + "..." if len(results.get("image_prompt", "")) > 500 else results["image_prompt"])
+
+ # Generated image
+ if results.get("generated_image"):
+ print(f"\n{'─'*40}")
+ print("GENERATED IMAGE:")
+ print(f"{'─'*40}")
+ img = results["generated_image"]
+ if img.get("success"):
+ print(f"✅ Image generated successfully")
+ print(f" Model: {img.get('model', 'unknown')}")
+ if img.get("image_base64"):
+ print(f" Data size: {len(img['image_base64']) / 1024:.1f} KB (base64)")
+ else:
+ print(f"❌ Image generation failed: {img.get('error', 'unknown error')}")
+
+ # Compliance
+ if results.get("compliance"):
+ print(f"\n{'─'*40}")
+ print("COMPLIANCE CHECK:")
+ print(f"{'─'*40}")
+ print(results["compliance"])
+
+ if results.get("violations"):
+ print(f"\n⚠️ Violations found: {len(results['violations'])}")
+ for v in results["violations"]:
+ print(f" - {v}")
+
+ if results.get("requires_modification"):
+ print("\n⚠️ Content requires modification before publishing")
+
+ # Save results if output path provided
+ if output_path:
+ # Prepare results for JSON serialization
+ output_data = {
+ "timestamp": datetime.now().isoformat(),
+ "elapsed_seconds": elapsed,
+ "brief": brief.model_dump(),
+ "products": products,
+ "generate_images": generate_images,
+ "results": {
+ "text_content": results.get("text_content"),
+ "image_prompt": results.get("image_prompt"),
+ "compliance": results.get("compliance"),
+ "violations": results.get("violations"),
+ "requires_modification": results.get("requires_modification"),
+ }
+ }
+
+ # Handle generated image separately (base64 can be large)
+ if results.get("generated_image"):
+ img = results["generated_image"]
+ output_data["results"]["generated_image"] = {
+ "success": img.get("success"),
+ "model": img.get("model"),
+ "error": img.get("error"),
+ "revised_prompt": img.get("revised_prompt"),
+ # Store base64 in separate file to keep JSON readable
+ "has_image_data": bool(img.get("image_base64"))
+ }
+
+ # Save image to separate file if successful
+ if img.get("success") and img.get("image_base64"):
+ import base64
+ image_path = output_path.replace(".json", "_image.png")
+ image_data = base64.b64decode(img["image_base64"])
+ with open(image_path, "wb") as f:
+ f.write(image_data)
+ output_data["results"]["generated_image"]["image_file"] = image_path
+ print(f"\n📁 Image saved to: {image_path}")
+
+ with open(output_path, "w") as f:
+ json.dump(output_data, f, indent=2)
+ print(f"📁 Results saved to: {output_path}")
+
+ return results
+
+
+async def main():
+ """Main entry point for the sample script."""
+ parser = argparse.ArgumentParser(
+ description="Generate marketing content using the Content Orchestrator"
+ )
+ parser.add_argument(
+ "--no-images",
+ action="store_true",
+ help="Skip image generation"
+ )
+ parser.add_argument(
+ "--output", "-o",
+ type=str,
+ default=None,
+ help="Output file path for results JSON"
+ )
+ parser.add_argument(
+ "--brief-file",
+ type=str,
+ default=None,
+ help="Path to JSON file containing creative brief"
+ )
+ parser.add_argument(
+ "--products-file",
+ type=str,
+ default=None,
+ help="Path to JSON file containing products list"
+ )
+
+ args = parser.parse_args()
+
+ # Load brief from file if provided
+ brief = None
+ if args.brief_file:
+ with open(args.brief_file, "r") as f:
+ brief_data = json.load(f)
+ brief = CreativeBrief(**brief_data)
+
+ # Load products from file if provided
+ products = None
+ if args.products_file:
+ with open(args.products_file, "r") as f:
+ products = json.load(f)
+
+ # Generate content
+ try:
+ result = await generate_content_sample(
+ brief=brief,
+ products=products,
+ generate_images=not args.no_images,
+ output_path=args.output
+ )
+
+ print(f"\n{'='*70}")
+ print("✅ Content generation completed!")
+ print(f"{'='*70}\n")
+
+ sys.exit(0)
+
+ except Exception as e:
+ print(f"\n❌ Error during content generation: {e}")
+ import traceback
+ traceback.print_exc()
+ sys.exit(1)
+
+
+# Example: Custom brief and products
+async def custom_example():
+ """Example with custom brief and products."""
+
+ # Custom brief for a summer promotion
+ brief = CreativeBrief(
+ overview="Summer outdoor living promotion for patio furniture",
+ objectives="Increase summer patio sales by 30%, drive foot traffic to showrooms",
+ target_audience="Suburban homeowners, 35-60, with outdoor entertaining spaces",
+ key_message="Create your perfect outdoor oasis with our premium patio collection",
+ tone_and_style="Relaxed, aspirational, lifestyle-focused. Evoke summer gatherings and outdoor relaxation",
+ deliverable="Email banner and 2 social media posts",
+ timelines="Launch Memorial Day weekend",
+ visual_guidelines="Outdoor settings at golden hour, families enjoying patios, lush greenery, modern outdoor furniture",
+ cta="Explore the Summer Collection - 20% off this weekend only"
+ )
+
+ # Custom products
+ products = [
+ {
+ "id": "patio-1",
+ "product_name": "Sunset Lounger",
+ "description": "Premium outdoor chaise lounge with weather-resistant cushions in coastal blue",
+ "tags": "patio, lounge, outdoor, blue, relaxation",
+ "price": 599.99
+ },
+ {
+ "id": "patio-2",
+ "product_name": "Garden Dining Set",
+ "description": "6-piece aluminum dining set with tempered glass table, perfect for outdoor entertaining",
+ "tags": "patio, dining, aluminum, entertaining, family",
+ "price": 1299.99
+ }
+ ]
+
+ results = await generate_content_sample(
+ brief=brief,
+ products=products,
+ generate_images=True,
+ output_path="custom_content_results.json"
+ )
+
+ return results
+
+
+if __name__ == "__main__":
+ # Run the main function
+ asyncio.run(main())
+
+ # Uncomment below to run custom example instead:
+ # asyncio.run(custom_example())
diff --git a/content-gen/scripts/sample_image_generation.py b/content-gen/scripts/sample_image_generation.py
new file mode 100644
index 000000000..aa4bc9363
--- /dev/null
+++ b/content-gen/scripts/sample_image_generation.py
@@ -0,0 +1,253 @@
+#!/usr/bin/env python3
+"""
+Sample Image Generation Script
+
+This script demonstrates how to generate marketing images using the
+content-gen image generation capabilities (DALL-E 3 or gpt-image-1).
+
+Prerequisites:
+1. Set up environment variables (or use a .env file):
+ - AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint
+ - AZURE_OPENAI_DALLE_ENDPOINT: (Optional) Dedicated DALL-E endpoint
+ - AZURE_OPENAI_DALLE_MODEL: Model name (default: dall-e-3)
+ - AZURE_OPENAI_IMAGE_MODEL: (Optional) Use "gpt-image-1" for GPT Image model
+
+2. Ensure you have RBAC access:
+ - "Cognitive Services OpenAI User" role on the Azure OpenAI resource
+
+Usage:
+ python sample_image_generation.py
+ python sample_image_generation.py --prompt "A modern kitchen with stainless steel appliances"
+ python sample_image_generation.py --size 1024x1792 --quality hd
+"""
+
+import asyncio
+import argparse
+import base64
+import os
+import sys
+from datetime import datetime
+from pathlib import Path
+
+# Add the backend directory to the path
+backend_path = Path(__file__).parent.parent / "src" / "backend"
+sys.path.insert(0, str(backend_path))
+
+# Now import the image generation function
+from agents.image_content_agent import generate_dalle_image
+from settings import app_settings
+
+
+async def generate_sample_image(
+ prompt: str,
+ product_description: str = "",
+ scene_description: str = "",
+ size: str = None,
+ quality: str = None,
+ output_path: str = None
+) -> dict:
+ """
+ Generate a sample marketing image.
+
+ Args:
+ prompt: The main image generation prompt
+ product_description: Optional product context for the image
+ scene_description: Optional scene/setting description
+ size: Image size (default from settings)
+ quality: Image quality (default from settings)
+ output_path: Path to save the generated image (optional)
+
+ Returns:
+ Dictionary with generation results
+ """
+ print(f"\n{'='*60}")
+ print("IMAGE GENERATION SAMPLE")
+ print(f"{'='*60}")
+ print(f"\nModel: {app_settings.azure_openai.effective_image_model}")
+ print(f"Endpoint: {app_settings.azure_openai.dalle_endpoint or app_settings.azure_openai.endpoint}")
+ print(f"Size: {size or app_settings.azure_openai.image_size}")
+ print(f"Quality: {quality or app_settings.azure_openai.image_quality}")
+ print(f"\nPrompt: {prompt[:200]}{'...' if len(prompt) > 200 else ''}")
+
+ if product_description:
+ print(f"Product context: {product_description[:100]}...")
+ if scene_description:
+ print(f"Scene: {scene_description[:100]}...")
+
+ print(f"\n{'='*60}")
+ print("Generating image...")
+ print(f"{'='*60}\n")
+
+ # Call the image generation function
+ result = await generate_dalle_image(
+ prompt=prompt,
+ product_description=product_description,
+ scene_description=scene_description,
+ size=size,
+ quality=quality
+ )
+
+ if result.get("success"):
+ print("✅ Image generated successfully!")
+ print(f" Model used: {result.get('model')}")
+
+ if result.get("revised_prompt"):
+ print(f" Revised prompt: {result['revised_prompt'][:150]}...")
+
+ # Save the image if we have base64 data
+ if result.get("image_base64") and output_path:
+ # Decode and save the image
+ image_data = base64.b64decode(result["image_base64"])
+
+ # Ensure output directory exists
+ output_dir = os.path.dirname(output_path)
+ if output_dir:
+ os.makedirs(output_dir, exist_ok=True)
+
+ with open(output_path, "wb") as f:
+ f.write(image_data)
+
+ print(f" Saved to: {output_path}")
+ print(f" File size: {len(image_data) / 1024:.1f} KB")
+ elif result.get("image_base64"):
+ # Generate default output path
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+ default_path = f"generated_image_{timestamp}.png"
+
+ image_data = base64.b64decode(result["image_base64"])
+ with open(default_path, "wb") as f:
+ f.write(image_data)
+
+ print(f" Saved to: {default_path}")
+ print(f" File size: {len(image_data) / 1024:.1f} KB")
+ else:
+ print(f"❌ Image generation failed: {result.get('error')}")
+
+ return result
+
+
+async def main():
+ """Main entry point for the sample script."""
+ parser = argparse.ArgumentParser(
+ description="Generate marketing images using DALL-E 3 or gpt-image-1"
+ )
+ parser.add_argument(
+ "--prompt", "-p",
+ type=str,
+ default="A modern, minimalist living room with comfortable furniture, soft natural lighting, and plants. Professional marketing photography style.",
+ help="The image generation prompt"
+ )
+ parser.add_argument(
+ "--product", "-d",
+ type=str,
+ default="",
+ help="Product description for context"
+ )
+ parser.add_argument(
+ "--scene", "-s",
+ type=str,
+ default="",
+ help="Scene/setting description"
+ )
+ parser.add_argument(
+ "--size",
+ type=str,
+ choices=["1024x1024", "1024x1792", "1792x1024", "1536x1024", "1024x1536"],
+ default=None,
+ help="Image size (default from settings)"
+ )
+ parser.add_argument(
+ "--quality", "-q",
+ type=str,
+ choices=["standard", "hd", "low", "medium", "high"],
+ default=None,
+ help="Image quality (default from settings)"
+ )
+ parser.add_argument(
+ "--output", "-o",
+ type=str,
+ default=None,
+ help="Output file path for the generated image"
+ )
+
+ args = parser.parse_args()
+
+ # Check if image generation is enabled
+ if not app_settings.azure_openai.image_generation_enabled:
+ print("❌ Image generation is not configured.")
+ print(" Please set AZURE_OPENAI_DALLE_ENDPOINT or AZURE_OPENAI_ENDPOINT")
+ print(" and ensure you have access to a DALL-E 3 or gpt-image-1 model.")
+ sys.exit(1)
+
+ # Generate the image
+ result = await generate_sample_image(
+ prompt=args.prompt,
+ product_description=args.product,
+ scene_description=args.scene,
+ size=args.size,
+ quality=args.quality,
+ output_path=args.output
+ )
+
+ # Exit with appropriate code
+ sys.exit(0 if result.get("success") else 1)
+
+
+# Example: Generate multiple themed images
+async def generate_themed_examples():
+ """Generate a set of example marketing images with different themes."""
+
+ themes = [
+ {
+ "name": "Modern Kitchen",
+ "prompt": "A sleek modern kitchen with marble countertops, stainless steel appliances, and pendant lighting. Professional real estate photography.",
+ "scene": "Bright, airy kitchen in a contemporary home",
+ },
+ {
+ "name": "Outdoor Living",
+ "prompt": "A beautiful outdoor patio with comfortable seating, string lights, and a fire pit at sunset. Lifestyle marketing photography.",
+ "scene": "Warm evening atmosphere in a backyard setting",
+ },
+ {
+ "name": "Home Office",
+ "prompt": "A minimalist home office with a clean desk, ergonomic chair, natural wood accents, and large windows. Professional interior design photography.",
+ "scene": "Productive workspace with natural lighting",
+ },
+ ]
+
+ print("\n" + "="*60)
+ print("GENERATING THEMED MARKETING IMAGES")
+ print("="*60)
+
+ results = []
+ for i, theme in enumerate(themes, 1):
+ print(f"\n[{i}/{len(themes)}] Generating: {theme['name']}")
+
+ result = await generate_sample_image(
+ prompt=theme["prompt"],
+ scene_description=theme["scene"],
+ output_path=f"sample_{theme['name'].lower().replace(' ', '_')}.png"
+ )
+ results.append({"theme": theme["name"], "result": result})
+
+ # Summary
+ print("\n" + "="*60)
+ print("GENERATION SUMMARY")
+ print("="*60)
+
+ successful = sum(1 for r in results if r["result"].get("success"))
+ print(f"\nSuccessfully generated: {successful}/{len(results)} images")
+
+ for r in results:
+ status = "✅" if r["result"].get("success") else "❌"
+ print(f" {status} {r['theme']}")
+
+ return results
+
+
+if __name__ == "__main__":
+ # Run the main function
+ asyncio.run(main())
+
+ # Uncomment below to run themed examples instead:
+ # asyncio.run(generate_themed_examples())
diff --git a/content-gen/scripts/test_content_generation.py b/content-gen/scripts/test_content_generation.py
new file mode 100644
index 000000000..3896a2465
--- /dev/null
+++ b/content-gen/scripts/test_content_generation.py
@@ -0,0 +1,314 @@
+"""
+Test script to simulate marketing content creation workflow.
+
+This script tests the end-to-end content generation flow:
+1. Research Agent searches for products and visual styles
+2. Planning Agent creates content strategy
+3. Text Content Agent generates marketing copy
+4. Image Content Agent generates image prompts
+5. Compliance Agent validates content
+
+Run with: python scripts/test_content_generation.py
+"""
+
+import asyncio
+import json
+import sys
+import os
+from typing import Dict, Any
+
+# Add src to path
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
+
+from dotenv import load_dotenv
+load_dotenv(os.path.join(os.path.dirname(__file__), '..', '.env'))
+
+
+# Sample creative briefs for testing
+SAMPLE_BRIEFS = [
+ {
+ "name": "Holiday Audio Campaign",
+ "brief": """
+Create a holiday marketing campaign for our premium wireless headphones.
+
+Campaign Details:
+- Target Audience: Tech-savvy professionals, 25-45 years old
+- Campaign Theme: "Gift of Sound" - perfect holiday gift for music lovers
+- Tone: Warm, festive, but sophisticated
+- Channels: Social media, Email newsletter
+- Key Messages: Premium sound quality, noise cancellation, perfect gift
+
+Deliverables Needed:
+1. Social media post with headline and body copy
+2. Email subject line and preview text
+3. Product hero image concept description
+"""
+ },
+ {
+ "name": "Fitness Product Launch",
+ "brief": """
+Launch campaign for our new Performance Yoga Mat.
+
+Campaign Details:
+- Target Audience: Health-conscious individuals, yoga enthusiasts, 20-40 years old
+- Campaign Theme: "Elevate Your Practice" - premium eco-friendly yoga experience
+- Tone: Calm, inspiring, wellness-focused
+- Channels: Social media, Wellness blogs
+- Key Messages: Eco-friendly materials, superior grip, alignment markers
+
+Deliverables Needed:
+1. Social media carousel post copy (3 slides)
+2. Social media pin description
+3. Lifestyle image concept for the mat in use
+"""
+ },
+ {
+ "name": "Luxury Watch Campaign",
+ "brief": """
+Create a sophisticated campaign for our Titanium Travel Watch.
+
+Campaign Details:
+- Target Audience: Affluent travelers, adventure seekers, 35-55 years old
+- Campaign Theme: "Time for Adventure" - precision meets exploration
+- Tone: Sophisticated, aspirational, adventurous
+- Channels: Print magazine, LinkedIn, Premium digital ads
+- Key Messages: Swiss precision, titanium durability, dual timezone
+
+Deliverables Needed:
+1. Magazine ad headline and body copy
+2. LinkedIn post for business travelers
+3. Hero image concept showing the watch in travel context
+"""
+ }
+]
+
+
+async def test_search_service():
+ """Test the search service for grounding data."""
+ print("\n" + "=" * 60)
+ print("STEP 1: Testing Search Service (Grounding Data)")
+ print("=" * 60)
+
+ from backend.services.search_service import get_search_service
+
+ service = await get_search_service()
+
+ # Test product search
+ print("\n📦 Searching for products: 'wireless audio'")
+ products = await service.search_products("wireless audio", top=3)
+ print(f" Found {len(products)} products:")
+ for p in products:
+ print(f" • {p['product_name']} ({p['category']})")
+ print(f" {p['marketing_description'][:80]}...")
+
+ # Test visual style search
+ print("\n🎨 Searching for visual styles: 'professional modern'")
+ images = await service.search_images("professional modern", top=3)
+ print(f" Found {len(images)} visual styles:")
+ for img in images:
+ print(f" • {img['name']}: {img['primary_color']} / {img['mood']}")
+
+ # Test combined grounding context
+ print("\n📋 Getting grounding context for 'holiday gift electronics'")
+ context = await service.get_grounding_context(
+ product_query="holiday gift electronics",
+ image_query="warm festive",
+ mood="Warm"
+ )
+ print(f" Products: {context['product_count']}, Images: {context['image_count']}")
+
+ return context
+
+
+async def test_research_agent_tools():
+ """Test the research agent's tool functions directly."""
+ print("\n" + "=" * 60)
+ print("STEP 2: Testing Research Agent Tools")
+ print("=" * 60)
+
+ from backend.agents.research_agent import (
+ search_products,
+ search_visual_styles,
+ get_grounding_context
+ )
+
+ # Test search_products tool
+ print("\n🔍 Testing search_products tool...")
+ result = await search_products(
+ query="yoga fitness",
+ category="Sports & Fitness",
+ limit=2
+ )
+ print(f" Query: 'yoga fitness', Category: 'Sports & Fitness'")
+ print(f" Found: {result['total_count']} products")
+ for p in result['products']:
+ print(f" • {p['product_name']}")
+
+ # Test search_visual_styles tool
+ print("\n🎨 Testing search_visual_styles tool...")
+ result = await search_visual_styles(
+ query="calm natural green",
+ color_family="Nature",
+ limit=2
+ )
+ print(f" Query: 'calm natural green', Color Family: 'Nature'")
+ print(f" Found: {result['total_count']} visual styles")
+ for v in result['visual_styles']:
+ print(f" • {v['name']}: {v['style']}")
+
+ # Test get_grounding_context tool
+ print("\n📦 Testing get_grounding_context tool...")
+ result = await get_grounding_context(
+ product_query="luxury accessories",
+ visual_query="sophisticated elegant",
+ mood="Elegant"
+ )
+ print(f" Products: {result['product_count']}, Images: {result['image_count']}")
+
+ return result
+
+
+async def simulate_content_generation(brief: Dict[str, Any]):
+ """Simulate the content generation workflow for a brief."""
+ print("\n" + "=" * 60)
+ print(f"STEP 3: Simulating Content Generation")
+ print(f"Campaign: {brief['name']}")
+ print("=" * 60)
+
+ from backend.services.search_service import get_search_service
+ from backend.settings import app_settings
+
+ # Extract key terms from brief (simplified - real implementation would use AI)
+ brief_text = brief['brief'].lower()
+
+ # Determine product category
+ if 'headphones' in brief_text or 'audio' in brief_text:
+ product_query = "wireless headphones audio"
+ category = "Electronics"
+ elif 'yoga' in brief_text or 'fitness' in brief_text:
+ product_query = "yoga mat fitness"
+ category = "Sports & Fitness"
+ elif 'watch' in brief_text:
+ product_query = "titanium watch travel"
+ category = "Accessories"
+ else:
+ product_query = "product"
+ category = None
+
+ # Determine visual mood
+ if 'holiday' in brief_text or 'festive' in brief_text:
+ visual_query = "warm inviting"
+ mood = "Warm"
+ elif 'calm' in brief_text or 'wellness' in brief_text:
+ visual_query = "calm peaceful natural"
+ mood = "Tranquil"
+ elif 'sophisticated' in brief_text or 'luxury' in brief_text:
+ visual_query = "sophisticated elegant premium"
+ mood = "Sophisticated"
+ else:
+ visual_query = "modern professional"
+ mood = None
+
+ # Get grounding context
+ print(f"\n🔍 Researching products: '{product_query}'")
+ print(f"🎨 Finding visual styles: '{visual_query}'")
+
+ service = await get_search_service()
+ context = await service.get_grounding_context(
+ product_query=product_query,
+ image_query=visual_query,
+ category=category,
+ mood=mood
+ )
+
+ print(f"\n📊 Grounding Context Retrieved:")
+ print(f" • {context['product_count']} matching products")
+ print(f" • {context['image_count']} matching visual styles")
+
+ # Display matched products
+ if context['products']:
+ print("\n📦 Matched Products:")
+ for p in context['products'][:2]:
+ print(f" • {p['product_name']} ({p['sku']})")
+ print(f" {p['marketing_description']}")
+
+ # Display matched visual styles
+ if context['images']:
+ print("\n🎨 Matched Visual Styles:")
+ for img in context['images'][:2]:
+ print(f" • {img['name']}")
+ print(f" Colors: {img['primary_color']} / {img['secondary_color']}")
+ print(f" Mood: {img['mood']}, Style: {img['style']}")
+
+ # Simulate content planning
+ print("\n📝 Content Plan (Simulated):")
+ print(" Based on the grounding context, the content would include:")
+
+ if context['products']:
+ product = context['products'][0]
+ print(f"\n HEADLINE CONCEPT:")
+ print(f" \"Experience {product['product_name']} - {product['marketing_description'][:50]}...\"")
+
+ print(f"\n KEY MESSAGING POINTS:")
+ specs = product.get('detailed_spec_description', '')[:200]
+ print(f" • Product highlights: {specs}...")
+
+ if product.get('image_description'):
+ print(f"\n IMAGE CONCEPT:")
+ print(f" Based on product visual: {product['image_description'][:150]}...")
+
+ if context['images']:
+ style = context['images'][0]
+ print(f"\n VISUAL DIRECTION:")
+ print(f" • Color palette: {style['primary_color']} with {style['secondary_color']} accents")
+ print(f" • Mood: {style['mood']}")
+ print(f" • Style: {style['style']}")
+ print(f" • Best for: {style.get('use_cases', 'General marketing')[:100]}")
+
+ # Show brand compliance context
+ print("\n✅ Brand Compliance Check:")
+ print(f" • Tone: {app_settings.brand_guidelines.tone}")
+ print(f" • Max headline: {app_settings.brand_guidelines.max_headline_length} chars")
+ print(f" • Prohibited words: {', '.join(app_settings.brand_guidelines.prohibited_words[:3])}...")
+
+ return context
+
+
+async def run_full_test():
+ """Run the complete test suite."""
+ print("\n" + "=" * 70)
+ print(" MARKETING CONTENT GENERATION - TEST SIMULATION")
+ print("=" * 70)
+
+ try:
+ # Step 1: Test search service
+ await test_search_service()
+
+ # Step 2: Test research agent tools
+ await test_research_agent_tools()
+
+ # Step 3: Simulate content generation for each brief
+ for brief in SAMPLE_BRIEFS:
+ await simulate_content_generation(brief)
+ print("\n" + "-" * 60)
+
+ print("\n" + "=" * 70)
+ print(" ✅ ALL TESTS COMPLETED SUCCESSFULLY")
+ print("=" * 70)
+ print("\nThe content generation system is configured and ready.")
+ print("Products and visual styles are being retrieved from Azure AI Search.")
+ print("\nNext steps:")
+ print(" 1. Start the backend: python app.py")
+ print(" 2. Start the frontend: npm run dev")
+ print(" 3. Submit a creative brief through the chat interface")
+ print("=" * 70 + "\n")
+
+ except Exception as e:
+ print(f"\n❌ Test failed: {e}")
+ import traceback
+ traceback.print_exc()
+ raise
+
+
+if __name__ == "__main__":
+ asyncio.run(run_full_test())
diff --git a/content-gen/scripts/update_backend_dns.sh b/content-gen/scripts/update_backend_dns.sh
new file mode 100755
index 000000000..c928c161a
--- /dev/null
+++ b/content-gen/scripts/update_backend_dns.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+# Script to update the backend DNS record after container restart
+# This keeps the BACKEND_URL stable (backend.contentgen.internal)
+# while updating the underlying IP when the container gets a new one.
+
+set -e
+
+RESOURCE_GROUP="${RESOURCE_GROUP:-rg-contentgen-jahunte}"
+CONTAINER_NAME="${CONTAINER_NAME:-aci-contentgen-backend}"
+DNS_ZONE="contentgen.internal"
+RECORD_NAME="backend"
+
+echo "Fetching current container IP..."
+NEW_IP=$(az container show -g "$RESOURCE_GROUP" -n "$CONTAINER_NAME" --query "ipAddress.ip" -o tsv)
+echo "Current container IP: $NEW_IP"
+
+echo "Fetching current DNS record IP..."
+CURRENT_IP=$(az network private-dns record-set a show -g "$RESOURCE_GROUP" -z "$DNS_ZONE" -n "$RECORD_NAME" --query "aRecords[0].ipv4Address" -o tsv 2>/dev/null || echo "")
+
+if [ "$CURRENT_IP" == "$NEW_IP" ]; then
+ echo "✓ DNS record is already up to date ($NEW_IP)"
+ exit 0
+fi
+
+if [ -n "$CURRENT_IP" ]; then
+ echo "Removing old DNS record ($CURRENT_IP)..."
+ az network private-dns record-set a remove-record \
+ -g "$RESOURCE_GROUP" \
+ -z "$DNS_ZONE" \
+ -n "$RECORD_NAME" \
+ -a "$CURRENT_IP" \
+ --keep-empty-record-set
+fi
+
+echo "Adding new DNS record ($NEW_IP)..."
+az network private-dns record-set a add-record \
+ -g "$RESOURCE_GROUP" \
+ -z "$DNS_ZONE" \
+ -n "$RECORD_NAME" \
+ -a "$NEW_IP"
+
+echo "✓ DNS record updated: $RECORD_NAME.$DNS_ZONE -> $NEW_IP"
+echo ""
+echo "The App Service will automatically use the new IP via:"
+echo " BACKEND_URL=http://backend.contentgen.internal:8000"
diff --git a/content-gen/src/app/.dockerignore b/content-gen/src/app/.dockerignore
new file mode 100644
index 000000000..01b21e59f
--- /dev/null
+++ b/content-gen/src/app/.dockerignore
@@ -0,0 +1,42 @@
+# Docker ignore file for src/ build context
+# Excludes unnecessary files from Docker build
+
+# Dependencies (will be installed fresh in container)
+**/node_modules/
+**/__pycache__/
+*.pyc
+
+# Build outputs (will be built in container)
+static/
+**/dist/
+frontend-server/static/
+
+# Development files
+*.log
+*.md
+.git/
+.gitignore
+.vscode/
+.env
+.env.*
+!.env.template
+
+# Test files
+**/*.test.*
+**/*.spec.*
+__tests__/
+coverage/
+tests/
+
+# IDE and OS files
+.DS_Store
+Thumbs.db
+*.swp
+*.swo
+
+# Deployment artifacts
+*.zip
+frontend-deploy.zip
+
+# Backend not needed for frontend build (and vice versa)
+# Comment out if building full-stack image
diff --git a/content-gen/src/app/WebApp.Dockerfile b/content-gen/src/app/WebApp.Dockerfile
new file mode 100644
index 000000000..fca82196e
--- /dev/null
+++ b/content-gen/src/app/WebApp.Dockerfile
@@ -0,0 +1,58 @@
+# ============================================
+# Frontend Dockerfile
+# Multi-stage build for Content Generation Frontend
+# Combines: frontend (React/Vite) + frontend-server (Node.js proxy)
+# ============================================
+
+# ============================================
+# Stage 1: Build the React frontend with Vite
+# ============================================
+FROM node:20-alpine AS frontend-build
+
+WORKDIR /app
+
+# Copy frontend package files
+COPY frontend/package*.json ./
+
+# Install dependencies
+RUN npm ci
+
+# Copy frontend source code
+COPY frontend/ ./
+
+# Build the frontend (outputs to ../static, but we're in /app so it goes to /static)
+# Override outDir to keep it in the container context
+RUN npm run build -- --outDir ./dist
+
+# ============================================
+# Stage 2: Production Node.js server
+# ============================================
+FROM node:20-alpine AS production
+
+WORKDIR /app
+
+# Copy frontend-server package files
+COPY frontend-server/package*.json ./
+
+# Install only production dependencies
+RUN npm ci --only=production
+
+# Copy the server code
+COPY frontend-server/server.js ./
+
+# Copy built frontend assets from stage 1
+COPY --from=frontend-build /app/dist ./static
+
+# Environment variables (can be overridden at runtime)
+ENV PORT=8080
+ENV NODE_ENV=production
+
+# Expose the port
+EXPOSE 8080
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD node -e "require('http').get('http://localhost:' + (process.env.PORT || 8080) + '/', (r) => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"
+
+# Start the server
+CMD ["node", "server.js"]
diff --git a/content-gen/src/app/frontend-server/package-lock.json b/content-gen/src/app/frontend-server/package-lock.json
new file mode 100644
index 000000000..6ac06cdef
--- /dev/null
+++ b/content-gen/src/app/frontend-server/package-lock.json
@@ -0,0 +1,1117 @@
+{
+ "name": "contentgen-frontend",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "contentgen-frontend",
+ "version": "1.0.0",
+ "dependencies": {
+ "express": "^4.18.2",
+ "http-proxy-middleware": "^2.0.6"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@types/http-proxy": {
+ "version": "1.17.17",
+ "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz",
+ "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "24.10.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
+ "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "license": "MIT"
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.4",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
+ "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "~1.2.0",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "on-finished": "~2.4.1",
+ "qs": "~6.14.0",
+ "raw-body": "~2.5.3",
+ "type-is": "~1.6.18",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/eventemitter3": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
+ "license": "MIT"
+ },
+ "node_modules/express": {
+ "version": "4.22.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
+ "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "~1.20.3",
+ "content-disposition": "~0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "~0.7.1",
+ "cookie-signature": "~1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "~1.3.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "~0.1.12",
+ "proxy-addr": "~2.0.7",
+ "qs": "~6.14.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "~0.19.0",
+ "serve-static": "~1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "~2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "~2.0.2",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/http-proxy": {
+ "version": "1.18.1",
+ "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
+ "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
+ "license": "MIT",
+ "dependencies": {
+ "eventemitter3": "^4.0.0",
+ "follow-redirects": "^1.0.0",
+ "requires-port": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/http-proxy-middleware": {
+ "version": "2.0.9",
+ "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz",
+ "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-proxy": "^1.17.8",
+ "http-proxy": "^1.18.1",
+ "is-glob": "^4.0.1",
+ "is-plain-obj": "^3.0.0",
+ "micromatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "@types/express": "^4.17.13"
+ },
+ "peerDependenciesMeta": {
+ "@types/express": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz",
+ "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+ "license": "MIT"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+ "license": "MIT"
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/send": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.1.tgz",
+ "integrity": "sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/send/node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.2",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+ "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.19.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/serve-static/node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/serve-static/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/serve-static/node_modules/send": {
+ "version": "0.19.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+ "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/serve-static/node_modules/send/node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/serve-static/node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "license": "MIT"
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ }
+ }
+}
diff --git a/content-gen/src/app/frontend-server/package.json b/content-gen/src/app/frontend-server/package.json
new file mode 100644
index 000000000..262373682
--- /dev/null
+++ b/content-gen/src/app/frontend-server/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "contentgen-frontend",
+ "version": "1.0.0",
+ "description": "Frontend server for Content Generation Accelerator",
+ "main": "server.js",
+ "scripts": {
+ "start": "node server.js"
+ },
+ "dependencies": {
+ "express": "^4.18.2",
+ "http-proxy-middleware": "^2.0.6"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+}
diff --git a/content-gen/src/app/frontend-server/server.js b/content-gen/src/app/frontend-server/server.js
new file mode 100644
index 000000000..cb61a1dee
--- /dev/null
+++ b/content-gen/src/app/frontend-server/server.js
@@ -0,0 +1,74 @@
+const express = require('express');
+const { createProxyMiddleware } = require('http-proxy-middleware');
+const path = require('path');
+const http = require('http');
+
+const app = express();
+const PORT = process.env.PORT || 8080;
+
+// Backend API URL (ACI private IP in VNet)
+const BACKEND_URL = process.env.BACKEND_URL || 'http://10.0.4.5:8000';
+
+// Create HTTP agent with extended keep-alive timeout for long-running SSE connections
+const httpAgent = new http.Agent({
+ keepAlive: true,
+ keepAliveMsecs: 300000, // 5 minutes keep-alive
+ maxSockets: 100,
+ timeout: 600000 // 10 minutes socket timeout
+});
+
+// Proxy API requests to backend
+app.use('/api', createProxyMiddleware({
+ target: BACKEND_URL,
+ changeOrigin: true,
+ pathRewrite: {
+ '^/api': '/api'
+ },
+ agent: httpAgent,
+ // Increase timeout for long-running requests (10 minutes)
+ proxyTimeout: 600000,
+ timeout: 600000,
+ // Support streaming responses (SSE)
+ onProxyRes: (proxyRes, req, res) => {
+ // Disable buffering for streaming responses
+ if (proxyRes.headers['content-type']?.includes('text/event-stream')) {
+ res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
+ res.setHeader('X-Accel-Buffering', 'no');
+ res.setHeader('Connection', 'keep-alive');
+ res.flushHeaders();
+ }
+ // Log response for debugging
+ console.log(`Proxy response: ${req.method} ${req.path} -> ${proxyRes.statusCode}`);
+ },
+ onProxyReq: (proxyReq, req, res) => {
+ // Log request for debugging
+ console.log(`Proxy request: ${req.method} ${req.path}`);
+ },
+ onError: (err, req, res) => {
+ console.error('Proxy error:', err.message);
+ if (!res.headersSent) {
+ res.status(502).json({ error: 'Backend service unavailable', details: err.message });
+ }
+ }
+}));
+
+// Serve static files from the build directory
+app.use(express.static(path.join(__dirname, 'static')));
+
+// Serve index.html for all other routes (SPA support)
+app.get('*', (req, res) => {
+ res.sendFile(path.join(__dirname, 'static', 'index.html'));
+});
+
+// Create server with extended timeouts for SSE
+const server = app.listen(PORT, () => {
+ console.log(`Frontend server running on port ${PORT}`);
+ console.log(`Proxying API requests to ${BACKEND_URL}`);
+});
+
+// Extend server timeouts for long-running SSE connections
+server.keepAliveTimeout = 620000; // 10 minutes + buffer
+server.headersTimeout = 630000; // Slightly higher than keepAliveTimeout
+server.timeout = 0; // Disable request timeout (handled by proxy)
+
+console.log('Server timeouts configured for SSE streaming');
diff --git a/content-gen/src/app/frontend/index.html b/content-gen/src/app/frontend/index.html
new file mode 100644
index 000000000..bb3b84e73
--- /dev/null
+++ b/content-gen/src/app/frontend/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Content Generation Accelerator
+
+
+
+
+
+
diff --git a/content-gen/src/app/frontend/microsoft.svg b/content-gen/src/app/frontend/microsoft.svg
new file mode 100644
index 000000000..a5b9a5c52
--- /dev/null
+++ b/content-gen/src/app/frontend/microsoft.svg
@@ -0,0 +1,6 @@
+
diff --git a/content-gen/src/app/frontend/package-lock.json b/content-gen/src/app/frontend/package-lock.json
new file mode 100644
index 000000000..854be9bbf
--- /dev/null
+++ b/content-gen/src/app/frontend/package-lock.json
@@ -0,0 +1,6365 @@
+{
+ "name": "content-generation-frontend",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "content-generation-frontend",
+ "version": "1.0.0",
+ "dependencies": {
+ "@fluentui/react-components": "^9.54.0",
+ "@fluentui/react-icons": "^2.0.245",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-markdown": "^9.0.1",
+ "uuid": "^10.0.0"
+ },
+ "devDependencies": {
+ "@types/node": "^24.10.1",
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
+ "@types/uuid": "^10.0.0",
+ "@typescript-eslint/eslint-plugin": "^7.15.0",
+ "@typescript-eslint/parser": "^7.15.0",
+ "@vitejs/plugin-react": "^4.3.1",
+ "eslint": "^8.57.0",
+ "eslint-plugin-react-hooks": "^4.6.2",
+ "eslint-plugin-react-refresh": "^0.4.7",
+ "typescript": "^5.5.2",
+ "vite": "^5.3.2"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
+ "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
+ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.28.3",
+ "@babel/helpers": "^7.28.4",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/core/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
+ "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
+ "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.28.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
+ "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
+ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.5"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
+ "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.5",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
+ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@ctrl/tinycolor": {
+ "version": "3.6.1",
+ "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
+ "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@emotion/hash": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
+ "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
+ "license": "MIT"
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
+ "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
+ "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
+ "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/devtools": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@floating-ui/devtools/-/devtools-0.2.3.tgz",
+ "integrity": "sha512-ZTcxTvgo9CRlP7vJV62yCxdqmahHTGpSTi5QaTDgGoyQq0OyjaVZhUhXv/qdkQFOI3Sxlfmz0XGG4HaZMsDf8Q==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@floating-ui/dom": "^1.0.0"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz",
+ "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.3",
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.10",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
+ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
+ "license": "MIT"
+ },
+ "node_modules/@fluentui/keyboard-keys": {
+ "version": "9.0.8",
+ "resolved": "https://registry.npmjs.org/@fluentui/keyboard-keys/-/keyboard-keys-9.0.8.tgz",
+ "integrity": "sha512-iUSJUUHAyTosnXK8O2Ilbfxma+ZyZPMua5vB028Ys96z80v+LFwntoehlFsdH3rMuPsA8GaC1RE7LMezwPBPdw==",
+ "license": "MIT",
+ "dependencies": {
+ "@swc/helpers": "^0.5.1"
+ }
+ },
+ "node_modules/@fluentui/priority-overflow": {
+ "version": "9.2.1",
+ "resolved": "https://registry.npmjs.org/@fluentui/priority-overflow/-/priority-overflow-9.2.1.tgz",
+ "integrity": "sha512-WH5dv54aEqWo/kKQuADAwjv66W6OUMFllQMjpdkrktQp7pu4JXtmF60iYcp9+iuIX9iCeW01j8gNTU08MQlfIQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@swc/helpers": "^0.5.1"
+ }
+ },
+ "node_modules/@fluentui/react-accordion": {
+ "version": "9.8.14",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-accordion/-/react-accordion-9.8.14.tgz",
+ "integrity": "sha512-jTcfYDRUotRhUEjE1LeG1Qm10515CQUKxHWQhppBYhq7yAZcS5jOms5tMZHtHs0EQsWv3nMgUYYqoOqAsU0jDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-aria": "^9.17.6",
+ "@fluentui/react-context-selector": "^9.2.12",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-motion": "^9.11.4",
+ "@fluentui/react-motion-components-preview": "^0.14.1",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-alert": {
+ "version": "9.0.0-beta.129",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-alert/-/react-alert-9.0.0-beta.129.tgz",
+ "integrity": "sha512-afS5Mvf9EH5je3ZOnF96GNaXL5nA/eI69AhO4nsbsvc1RaO/CkEt9+6iVyGy2zeqbQgpsP9UkNwEYyToQ1CrzA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-avatar": "^9.9.12",
+ "@fluentui/react-button": "^9.6.12",
+ "@fluentui/react-icons": "^2.0.239",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-aria": {
+ "version": "9.17.6",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-aria/-/react-aria-9.17.6.tgz",
+ "integrity": "sha512-O421keKMgf9BkHH15kTnKGFuCFKN3ukydJLEfSQJmOfdAHyJMzAul8/zMvkd4vmMr84+PtZUD1+Tylk4NvpN4g==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/keyboard-keys": "^9.0.8",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-avatar": {
+ "version": "9.9.12",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-avatar/-/react-avatar-9.9.12.tgz",
+ "integrity": "sha512-dlJ5mOKCDChMAECFhpcPHoQicA28ATWQXLtz26hAuVJH2/gC/6mZ0j7drIVl9YECqT/ZbZ3/hpVeZu/S/FVrOA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-badge": "^9.4.11",
+ "@fluentui/react-context-selector": "^9.2.12",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-popover": "^9.12.12",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-tooltip": "^9.8.11",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-badge": {
+ "version": "9.4.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-badge/-/react-badge-9.4.11.tgz",
+ "integrity": "sha512-u2gTg+QeD5uaieAwE89n8MLg2MyZN/kGMx3hJewFKtq3SzvU4xcgcna2Gp4UgpaA3pnGZsJjjjDIHwsv4EyO9Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-breadcrumb": {
+ "version": "9.3.12",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-breadcrumb/-/react-breadcrumb-9.3.12.tgz",
+ "integrity": "sha512-cT5xmYQbAYH7HslJu6O5WvSYzsBvaQ54Q6yIPgV5kCo5n3M6OSrJ0Ga6Zbfqid/GnY4G60FfjOvbfHNNhmx2Sw==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-aria": "^9.17.6",
+ "@fluentui/react-button": "^9.6.12",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-link": "^9.7.0",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-button": {
+ "version": "9.6.12",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-button/-/react-button-9.6.12.tgz",
+ "integrity": "sha512-seI9L9O0fCHzlfKD/via1qqzaLFeiFKQeR1/97nXL06reC3DqLSCeiZP3UTxFljFE1CYZQRJfk1wH/D6j0ZCTA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/keyboard-keys": "^9.0.8",
+ "@fluentui/react-aria": "^9.17.6",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-card": {
+ "version": "9.5.6",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-card/-/react-card-9.5.6.tgz",
+ "integrity": "sha512-hCY6VWrKqq+y0yqUkqgkpTN5TVJSU5ZlKtZU+Sed+TlnKlojkS6cYRvsnWdAKwyFLJF9ZYTn+uos9Vi0wQyjtg==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/keyboard-keys": "^9.0.8",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-text": "^9.6.11",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-carousel": {
+ "version": "9.8.12",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-carousel/-/react-carousel-9.8.12.tgz",
+ "integrity": "sha512-Gjn6cd67FodcjfU2MQTBI2xjijzgy54TdQA8vxObZ27I6y9OHeDR07PWTqaCkX8mcBR8ilTxVD5bQ+zuqfb66g==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-aria": "^9.17.6",
+ "@fluentui/react-button": "^9.6.12",
+ "@fluentui/react-context-selector": "^9.2.12",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-tooltip": "^9.8.11",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1",
+ "embla-carousel": "^8.5.1",
+ "embla-carousel-autoplay": "^8.5.1",
+ "embla-carousel-fade": "^8.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-checkbox": {
+ "version": "9.5.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-checkbox/-/react-checkbox-9.5.11.tgz",
+ "integrity": "sha512-M8DTBQK0Z7+HKfRx4mjypH0fEagKK7YMNhGMy18aW3iYWeooA0ut81MzsRM5feqhl+Q8v4VJ0aN9qHNqshkD5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-field": "^9.4.11",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-label": "^9.3.11",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-color-picker": {
+ "version": "9.2.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-color-picker/-/react-color-picker-9.2.11.tgz",
+ "integrity": "sha512-L1ZKJAyioey3glmzMrpawUrzsdu/Nz0m6nVMOznJVuw0vu0BfQuMh/1/0QOoGYXFEbsc4+gSGSCnah4X0EJIsQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@ctrl/tinycolor": "^3.3.4",
+ "@fluentui/react-context-selector": "^9.2.12",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-combobox": {
+ "version": "9.16.12",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-combobox/-/react-combobox-9.16.12.tgz",
+ "integrity": "sha512-SimZpXzTGyDAGHQZmzUl9AsrIOlLDinTbvEwELEYh9X+yE33SZatcPwdpCmBXldBOs/eh+xOuNSOwgerJ3T3qQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/keyboard-keys": "^9.0.8",
+ "@fluentui/react-aria": "^9.17.6",
+ "@fluentui/react-context-selector": "^9.2.12",
+ "@fluentui/react-field": "^9.4.11",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-portal": "^9.8.8",
+ "@fluentui/react-positioning": "^9.20.10",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-components": {
+ "version": "9.72.7",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-components/-/react-components-9.72.7.tgz",
+ "integrity": "sha512-tuC8ZMBQicF4p+f9MJv9cVYZUSktQVreAGJq/YJxQ0Ts1mO2rnAuIBkBFlgjnjyebDiAO1FoAAz/wW99hrIh6A==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-accordion": "^9.8.14",
+ "@fluentui/react-alert": "9.0.0-beta.129",
+ "@fluentui/react-aria": "^9.17.6",
+ "@fluentui/react-avatar": "^9.9.12",
+ "@fluentui/react-badge": "^9.4.11",
+ "@fluentui/react-breadcrumb": "^9.3.12",
+ "@fluentui/react-button": "^9.6.12",
+ "@fluentui/react-card": "^9.5.6",
+ "@fluentui/react-carousel": "^9.8.12",
+ "@fluentui/react-checkbox": "^9.5.11",
+ "@fluentui/react-color-picker": "^9.2.11",
+ "@fluentui/react-combobox": "^9.16.12",
+ "@fluentui/react-dialog": "^9.16.3",
+ "@fluentui/react-divider": "^9.4.11",
+ "@fluentui/react-drawer": "^9.10.9",
+ "@fluentui/react-field": "^9.4.11",
+ "@fluentui/react-image": "^9.3.11",
+ "@fluentui/react-infobutton": "9.0.0-beta.107",
+ "@fluentui/react-infolabel": "^9.4.12",
+ "@fluentui/react-input": "^9.7.11",
+ "@fluentui/react-label": "^9.3.11",
+ "@fluentui/react-link": "^9.7.0",
+ "@fluentui/react-list": "^9.6.6",
+ "@fluentui/react-menu": "^9.20.5",
+ "@fluentui/react-message-bar": "^9.6.14",
+ "@fluentui/react-motion": "^9.11.4",
+ "@fluentui/react-nav": "^9.3.14",
+ "@fluentui/react-overflow": "^9.6.5",
+ "@fluentui/react-persona": "^9.5.12",
+ "@fluentui/react-popover": "^9.12.12",
+ "@fluentui/react-portal": "^9.8.8",
+ "@fluentui/react-positioning": "^9.20.10",
+ "@fluentui/react-progress": "^9.4.11",
+ "@fluentui/react-provider": "^9.22.11",
+ "@fluentui/react-radio": "^9.5.11",
+ "@fluentui/react-rating": "^9.3.11",
+ "@fluentui/react-search": "^9.3.11",
+ "@fluentui/react-select": "^9.4.11",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-skeleton": "^9.4.11",
+ "@fluentui/react-slider": "^9.5.11",
+ "@fluentui/react-spinbutton": "^9.5.11",
+ "@fluentui/react-spinner": "^9.7.11",
+ "@fluentui/react-swatch-picker": "^9.4.11",
+ "@fluentui/react-switch": "^9.4.11",
+ "@fluentui/react-table": "^9.19.5",
+ "@fluentui/react-tabs": "^9.10.7",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-tag-picker": "^9.7.12",
+ "@fluentui/react-tags": "^9.7.12",
+ "@fluentui/react-teaching-popover": "^9.6.12",
+ "@fluentui/react-text": "^9.6.11",
+ "@fluentui/react-textarea": "^9.6.11",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-toast": "^9.7.9",
+ "@fluentui/react-toolbar": "^9.6.12",
+ "@fluentui/react-tooltip": "^9.8.11",
+ "@fluentui/react-tree": "^9.15.6",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@fluentui/react-virtualizer": "9.0.0-alpha.107",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-context-selector": {
+ "version": "9.2.12",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-context-selector/-/react-context-selector-9.2.12.tgz",
+ "integrity": "sha512-4hj+rv+4Uwn9EeDyXD1YCEpVkm0iMLG403QAGd5vZZhcgB2tg/iazewKeTff+HMRkusx+lWBYzBEGcRohY/FiA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-utilities": "^9.25.4",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0",
+ "scheduler": ">=0.19.0"
+ }
+ },
+ "node_modules/@fluentui/react-dialog": {
+ "version": "9.16.3",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-dialog/-/react-dialog-9.16.3.tgz",
+ "integrity": "sha512-aUnErTbSf2oqrqbQOCrjXp/12qHVfnxCR71/5hXJLME7BtYZ/m2lvs5r9MTjQSXBy8ar4G5jobS/+XJ0Lq3XqA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/keyboard-keys": "^9.0.8",
+ "@fluentui/react-aria": "^9.17.6",
+ "@fluentui/react-context-selector": "^9.2.12",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-motion": "^9.11.4",
+ "@fluentui/react-motion-components-preview": "^0.14.1",
+ "@fluentui/react-portal": "^9.8.8",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-divider": {
+ "version": "9.4.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-divider/-/react-divider-9.4.11.tgz",
+ "integrity": "sha512-aESagOX6l7Ja9lb+3zJa6V5m1mjFnI4NEu8TccAu1VUlMZxX6flbMBJplgjN76dJjcHgs8uoa5xxxD74WNZBXg==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-drawer": {
+ "version": "9.10.9",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-drawer/-/react-drawer-9.10.9.tgz",
+ "integrity": "sha512-tlHZBkILOHnA7Lg2v/vzmOvTNrPYJnPJAqiceuFlUZWncIWWAUfpw4Teh5V0wGNr6/yC/HjUD5xnynvIhr/ZuA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-dialog": "^9.16.3",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-motion": "^9.11.4",
+ "@fluentui/react-motion-components-preview": "^0.14.1",
+ "@fluentui/react-portal": "^9.8.8",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-field": {
+ "version": "9.4.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-field/-/react-field-9.4.11.tgz",
+ "integrity": "sha512-kF93G+LGEKaFJcEAUHJKZUc1xeV/q+JTygYVnEDkPbQ/4j+l+J3rVuHL8U7bhE+8cJG3wDP8jt4jqHsDgKyn5w==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-context-selector": "^9.2.12",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-label": "^9.3.11",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-icons": {
+ "version": "2.0.315",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-icons/-/react-icons-2.0.315.tgz",
+ "integrity": "sha512-IITWAQGgU7I32eHPDHi+TUCUF6malP27wZLUV3bqjGVF/x/lfxvTIx8yqv/cxuwF3+ITGFDpl+278ZYJtOI7ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@griffel/react": "^1.0.0",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-image": {
+ "version": "9.3.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-image/-/react-image-9.3.11.tgz",
+ "integrity": "sha512-aLpz0/C6T0Uit6SmyhOJjYBvndZzfvmKv1vg+JRnE0aHS5jSUPoCLI6apxyMC6/LcqqTBklpqK3AD9kYpUxfqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-infobutton": {
+ "version": "9.0.0-beta.107",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-infobutton/-/react-infobutton-9.0.0-beta.107.tgz",
+ "integrity": "sha512-BcI4e+Oj1B/Qk4CMd0O9H0YF+IL4nhK8xuzI5bsZ5mdCaXiwIBgy5RyP8HVSq3y+Ml4XD2IRwufplcxF2cgTOA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-icons": "^2.0.237",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-label": "^9.3.11",
+ "@fluentui/react-popover": "^9.12.12",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-infolabel": {
+ "version": "9.4.12",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-infolabel/-/react-infolabel-9.4.12.tgz",
+ "integrity": "sha512-inXlz5EAwQHKsGyB3wc5WmgQ1F9zc18x0HRd/otc2R7Oo1yRW5hXQCG5K5A9/wUge2pRiQVcBCsIurggmCNUhA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-label": "^9.3.11",
+ "@fluentui/react-popover": "^9.12.12",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8.0 <20.0.0",
+ "@types/react-dom": ">=16.8.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.8.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-input": {
+ "version": "9.7.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-input/-/react-input-9.7.11.tgz",
+ "integrity": "sha512-ae/5ttJf25+J8akeEXpXRFqUAePPt2Moyfx4Tj0u7ZgG1U9IFbcBsshKEHAmIaygueXf6KdRyOduh1CF6a/D2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-field": "^9.4.11",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-jsx-runtime": {
+ "version": "9.3.3",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-jsx-runtime/-/react-jsx-runtime-9.3.3.tgz",
+ "integrity": "sha512-KOy85JqR6MSmp7OKUk/IPleaRlUSWF247AM2Ksu9uEKzDBQ2MO3sYUt8X9457GZjIuKLn5J2Vk127W/Khe+/Bg==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-utilities": "^9.25.4",
+ "@swc/helpers": "^0.5.1",
+ "react-is": "^17.0.2"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-label": {
+ "version": "9.3.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-label/-/react-label-9.3.11.tgz",
+ "integrity": "sha512-9LORj4JQJCbp2J5ftW7ZjDxzD3Y4BkszX3Y7L1mK8DPRVAKOuGiakbH7U0q7ClGOMhCinWIQJjKAwzPZLo7xgA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-link": {
+ "version": "9.7.0",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-link/-/react-link-9.7.0.tgz",
+ "integrity": "sha512-NQ5Jhe5WBYfANSmIcl6fE/oBeh7G4iAq1FU9L/hyva5dxQ9OtiOpU5wxqVFLKEID/r144rhdtOZPL5AcAuJKdg==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/keyboard-keys": "^9.0.8",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-list": {
+ "version": "9.6.6",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-list/-/react-list-9.6.6.tgz",
+ "integrity": "sha512-t0ret56WXP86rDfnhuRrWg/DuS2zZkomB/Nu444rVygE8hsjPUTm5DXx7JKy+sGKVLyFbtsbXNMICkbxhGSSRA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/keyboard-keys": "^9.0.8",
+ "@fluentui/react-checkbox": "^9.5.11",
+ "@fluentui/react-context-selector": "^9.2.12",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8.0 <20.0.0",
+ "@types/react-dom": ">=16.8.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.8.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-menu": {
+ "version": "9.20.5",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-menu/-/react-menu-9.20.5.tgz",
+ "integrity": "sha512-vshb/OXBZxvk+ghdmdVb2mJ/LJBYjlwpZRhWGJ8ZU0hmPTh74m5jTFWditSk8aL9oMvVuIo0MYLQyUJyJsFoqg==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/keyboard-keys": "^9.0.8",
+ "@fluentui/react-aria": "^9.17.6",
+ "@fluentui/react-context-selector": "^9.2.12",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-portal": "^9.8.8",
+ "@fluentui/react-positioning": "^9.20.10",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-message-bar": {
+ "version": "9.6.14",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-message-bar/-/react-message-bar-9.6.14.tgz",
+ "integrity": "sha512-UR4Uvkx4VHQyS04T5ikf9gYOH52dloo1vjmK+pFKiqRzZhflHEXID9R1AZFuuZ572KUMXnxRlyEevpXnWqE70w==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-button": "^9.6.12",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-link": "^9.7.0",
+ "@fluentui/react-motion": "^9.11.4",
+ "@fluentui/react-motion-components-preview": "^0.14.1",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8.0 <20.0.0",
+ "@types/react-dom": ">=16.8.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.8.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-motion": {
+ "version": "9.11.4",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-motion/-/react-motion-9.11.4.tgz",
+ "integrity": "sha512-rLxz6DSAtp3O+W+mJnov2qXtvZkIgcC1BQOAyUH6tl6u2YmsC1/zRKWhVsf/WUgZwqu3G4jlq15ptyuCITAcDA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8.0 <20.0.0",
+ "@types/react-dom": ">=16.8.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.8.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-motion-components-preview": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-motion-components-preview/-/react-motion-components-preview-0.14.1.tgz",
+ "integrity": "sha512-+2MK7d2g3mD+6Z3o9/fitO+V4u5OKGeRUoUjwlU1v56JHP43hj+NCJynoe4Cym8FeSwTyipks6hvdqBF4W+jtw==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-motion": "*",
+ "@fluentui/react-utilities": "*",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-nav": {
+ "version": "9.3.14",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-nav/-/react-nav-9.3.14.tgz",
+ "integrity": "sha512-0Lylul5g/9y3Cay5qHLtzW4SB9kdkTmvjHSffPJZDKE/Wv7GBbDypBxoB+f2L1K4f0qzRJ1NvIiwatH4hAsUDA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-aria": "^9.17.6",
+ "@fluentui/react-button": "^9.6.12",
+ "@fluentui/react-context-selector": "^9.2.12",
+ "@fluentui/react-divider": "^9.4.11",
+ "@fluentui/react-drawer": "^9.10.9",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-motion": "^9.11.4",
+ "@fluentui/react-motion-components-preview": "^0.14.1",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-tooltip": "^9.8.11",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-overflow": {
+ "version": "9.6.5",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-overflow/-/react-overflow-9.6.5.tgz",
+ "integrity": "sha512-4MlXASDodkwk4QWhUPLgMbUPwDYAOAWDnQGJz4q6Hs9eZvx83dSpWdWjkmQ6mwjYf2HwooMkqsjR/kAFvg+ipg==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/priority-overflow": "^9.2.1",
+ "@fluentui/react-context-selector": "^9.2.12",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-persona": {
+ "version": "9.5.12",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-persona/-/react-persona-9.5.12.tgz",
+ "integrity": "sha512-ja3t1o6XDJWCJnOVDTM48G7bFPAbNxcsQKwAPfiuROVu8ODbTQefutCHl0Hno40AsftQk6N4zGbKcn7BYSZ09Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-avatar": "^9.9.12",
+ "@fluentui/react-badge": "^9.4.11",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-popover": {
+ "version": "9.12.12",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-popover/-/react-popover-9.12.12.tgz",
+ "integrity": "sha512-IytuasB4b4lCnEhFC0OC66a3mzBSePLpg78/BceKYepuG7IC6iGuCwYartqSQCSUlSU12rT02/V0rqCO81f4Nw==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/keyboard-keys": "^9.0.8",
+ "@fluentui/react-aria": "^9.17.6",
+ "@fluentui/react-context-selector": "^9.2.12",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-portal": "^9.8.8",
+ "@fluentui/react-positioning": "^9.20.10",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-portal": {
+ "version": "9.8.8",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-portal/-/react-portal-9.8.8.tgz",
+ "integrity": "sha512-RVvhWYfcwIUYXiokgFw3oxb7Q6xox2e7jcsgFtheDm2X/BHT6WJigW4OaCjOkvugkBEYQkwgIpL9iS2QG3HMFA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-positioning": {
+ "version": "9.20.10",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-positioning/-/react-positioning-9.20.10.tgz",
+ "integrity": "sha512-mjuiqh+urV5SzAP2NfzUzsvtWut0aNcO9m/jIuz374iTVGRfDNeVIl7aPI4yK5sdCDR6dGALiNMTFHpjz1YXyw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/devtools": "^0.2.3",
+ "@floating-ui/dom": "^1.6.12",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1",
+ "use-sync-external-store": "^1.2.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-progress": {
+ "version": "9.4.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-progress/-/react-progress-9.4.11.tgz",
+ "integrity": "sha512-L0Yh2D0vLPJX0jYfc9VHf8c/idW+e/oRxYNXfrTrvtW1bX80bAmrXWgdRPr/VEtvbJh//2ol2TRmTTQsn2ECNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-field": "^9.4.11",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-provider": {
+ "version": "9.22.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-provider/-/react-provider-9.22.11.tgz",
+ "integrity": "sha512-XrinA7DVEqsPHeN9XljwTENiIQypiF9cmDYXHN9Emsz6Od4hnmsbt4pnR4Xsf+GcSxVtxkIImfgwtS0aENzYbQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/core": "^1.16.0",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-radio": {
+ "version": "9.5.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-radio/-/react-radio-9.5.11.tgz",
+ "integrity": "sha512-tMxCcqRSSYqYr6hy1dKkzS6LymRc8wM089vr4eBLPQCGCvi3OCd6P7XH8aIcXnzxE3+v03Gs7E/wbzi2CXN6gA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-field": "^9.4.11",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-label": "^9.3.11",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-rating": {
+ "version": "9.3.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-rating/-/react-rating-9.3.11.tgz",
+ "integrity": "sha512-9Bl/sESNbFTbz8peGt9vxLxHDO0AWvS12oMiQ80S1GQOt1ua4S9/SKC83OvyVLEdpBDpBkXTelNz5whczcWexQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8.0 <20.0.0",
+ "@types/react-dom": ">=16.8.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.8.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-search": {
+ "version": "9.3.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-search/-/react-search-9.3.11.tgz",
+ "integrity": "sha512-inLoPgbGnupfwhBxFS59mF/ThsntusfYp9TaaTB3SJmqfEEx6YXi5soxszzrXsNvrqpgEoCGIduRpEICuUz5pw==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-input": "^9.7.11",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-select": {
+ "version": "9.4.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-select/-/react-select-9.4.11.tgz",
+ "integrity": "sha512-/mcdl/lkKccT+GKXu22y2/ANeLhFNUdjkOX+0rvBdl3u49xkqS9Y4Bi0zM1EhhTV2jE8+yjMjzPDzfzJaXVK1A==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-field": "^9.4.11",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-shared-contexts": {
+ "version": "9.26.0",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-shared-contexts/-/react-shared-contexts-9.26.0.tgz",
+ "integrity": "sha512-r52B+LUevs930pe45pFsppM9XNvY+ojgRgnDE+T/6aiwR/Mo4YoGrtjhLEzlQBeTGuySICTeaAiXfuH6Keo5Dg==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-theme": "^9.2.0",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-skeleton": {
+ "version": "9.4.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-skeleton/-/react-skeleton-9.4.11.tgz",
+ "integrity": "sha512-nw6NlTBXS7lNSxsebLuADYQi9gJ83jFBFsFq+AGIpAoZLBOCHOhk8/XwV3vYtPwVrKcZtOtXqh9NdCqTR3OAIA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-field": "^9.4.11",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-slider": {
+ "version": "9.5.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-slider/-/react-slider-9.5.11.tgz",
+ "integrity": "sha512-kxZKklJbcG/521muQaIDMdcftoClbwV7yMOcu8PMG+VXsaIuoandoBleBYdzM2XdpY62iK6vUPAMZWBZh3B5Ng==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-field": "^9.4.11",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-spinbutton": {
+ "version": "9.5.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-spinbutton/-/react-spinbutton-9.5.11.tgz",
+ "integrity": "sha512-pYR3RkJfks+0WV47KoDKD04D0pTHtT+lu3AeOpBlIswxtsb1gZEDmTrEHHNeLDKKVhWMWNoEPlxfXuX9tOh7yA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/keyboard-keys": "^9.0.8",
+ "@fluentui/react-field": "^9.4.11",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-spinner": {
+ "version": "9.7.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-spinner/-/react-spinner-9.7.11.tgz",
+ "integrity": "sha512-MhmAisICa3BzBNQH9CnLI5NVPTTXFo1Yaey8kbQPU+gVVF4vIGORB7M1MXSHFxZvojtFpBixiVHqRwh9mowJww==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-label": "^9.3.11",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-swatch-picker": {
+ "version": "9.4.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-swatch-picker/-/react-swatch-picker-9.4.11.tgz",
+ "integrity": "sha512-M/ZfHqo63F69y2ymEQDDN/BZuI3afeW3U+omyGZZoHts3rVCjPk6sKFemTRpUhGgGfxBdexWEPithZx3dk0IPA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-context-selector": "^9.2.12",
+ "@fluentui/react-field": "^9.4.11",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8.0 <20.0.0",
+ "@types/react-dom": ">=16.8.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.8.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-switch": {
+ "version": "9.4.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-switch/-/react-switch-9.4.11.tgz",
+ "integrity": "sha512-/WDcoVFQ3I2fe5FTINfyVTIW6wuTgM5QkJgcwbU7HTANq/+wJ2f8wzywoI4x16cJOckBdy+ByDpW7uJ/Uvs8RA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-field": "^9.4.11",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-label": "^9.3.11",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-table": {
+ "version": "9.19.5",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-table/-/react-table-9.19.5.tgz",
+ "integrity": "sha512-In9egEdytjFd6N1RBZd5+3UgdXvEVDP7rz+/I79J10ui2+Nb7r9ah68m5CQB15AKA8F5XFDWPEGvGG3Tmuq4Jg==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/keyboard-keys": "^9.0.8",
+ "@fluentui/react-aria": "^9.17.6",
+ "@fluentui/react-avatar": "^9.9.12",
+ "@fluentui/react-checkbox": "^9.5.11",
+ "@fluentui/react-context-selector": "^9.2.12",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-radio": "^9.5.11",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-tabs": {
+ "version": "9.10.7",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-tabs/-/react-tabs-9.10.7.tgz",
+ "integrity": "sha512-Kfq6GxZXEKsMdGKmHWNMcEYOYHxl5+fXJOH6ZRgeR2FkHUsPUUe2BHaFnOMRSvCwzECvhOMYs+Ekqt7JzW3BWQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-context-selector": "^9.2.12",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-tabster": {
+ "version": "9.26.10",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-tabster/-/react-tabster-9.26.10.tgz",
+ "integrity": "sha512-KrddtwbnbgYVAnOkx1pQsMMgq7Kfi+lMRrUrDDJ9Y5X6wiXiajbWRRxYgKiOJc3MpeDCaTCEtjOWNG92vcinMw==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1",
+ "keyborg": "^2.6.0",
+ "tabster": "^8.5.5"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-tag-picker": {
+ "version": "9.7.12",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-tag-picker/-/react-tag-picker-9.7.12.tgz",
+ "integrity": "sha512-OJucCDub6b3ceGL6v2UXL+SD3x6nJMbmJ70v38BmrA9t3fNcDvn6RnsfHhF2O0pRGGUOrXbK7vDwVhUAG4Py8w==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/keyboard-keys": "^9.0.8",
+ "@fluentui/react-aria": "^9.17.6",
+ "@fluentui/react-combobox": "^9.16.12",
+ "@fluentui/react-context-selector": "^9.2.12",
+ "@fluentui/react-field": "^9.4.11",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-portal": "^9.8.8",
+ "@fluentui/react-positioning": "^9.20.10",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-tags": "^9.7.12",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-tags": {
+ "version": "9.7.12",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-tags/-/react-tags-9.7.12.tgz",
+ "integrity": "sha512-G7pxP0GGa6J/7mYvB9ycOmD9Jpm6ByUz6JsJI4OBL9UnhenUVTtE7ZKJ9GJ0SiG0GVxS152aSlOR7NLHV7mCqw==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/keyboard-keys": "^9.0.8",
+ "@fluentui/react-aria": "^9.17.6",
+ "@fluentui/react-avatar": "^9.9.12",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-teaching-popover": {
+ "version": "9.6.12",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-teaching-popover/-/react-teaching-popover-9.6.12.tgz",
+ "integrity": "sha512-Ugo5SQ3yzSlxUWkeeEdumTWTw662KDh3UPc6RGhU0Jq13skpmsClSJL678BZwsYdAaJXvvG9Bi4PjPeezeB/SA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-aria": "^9.17.6",
+ "@fluentui/react-button": "^9.6.12",
+ "@fluentui/react-context-selector": "^9.2.12",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-popover": "^9.12.12",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1",
+ "use-sync-external-store": "^1.2.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8.0 <20.0.0",
+ "@types/react-dom": ">=16.8.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.8.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-text": {
+ "version": "9.6.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-text/-/react-text-9.6.11.tgz",
+ "integrity": "sha512-U7EiCesOWjkALf7LM6sy+yvE59Px3c6f27jg4aa21UMo61HCVNbjKV8Lz6GzEftEvv++/EZ25yZBiQcKgh/5iA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-textarea": {
+ "version": "9.6.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-textarea/-/react-textarea-9.6.11.tgz",
+ "integrity": "sha512-5ds8u8hzSqj8cOy0e7HJWjUMq1aO0MIJiaNt/SyIxoZFvsklj/2yaMRVXpWxr3GvX5bzScvFoBY53gPdLKtE/g==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-field": "^9.4.11",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-theme": {
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-theme/-/react-theme-9.2.0.tgz",
+ "integrity": "sha512-Q0zp/MY1m5RjlkcwMcjn/PQRT2T+q3bgxuxWbhgaD07V+tLzBhGROvuqbsdg4YWF/IK21zPfLhmGyifhEu0DnQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/tokens": "1.0.0-alpha.22",
+ "@swc/helpers": "^0.5.1"
+ }
+ },
+ "node_modules/@fluentui/react-toast": {
+ "version": "9.7.9",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-toast/-/react-toast-9.7.9.tgz",
+ "integrity": "sha512-PaFh2CwVK4tgvRzBMb46ODHsB+ZYSYE8mx735vqgIG8Oj1AL3wZ5Y9TrjJGxn/lppZgtnwLgt4GQ+GI7MM+e+g==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/keyboard-keys": "^9.0.8",
+ "@fluentui/react-aria": "^9.17.6",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-motion": "^9.11.4",
+ "@fluentui/react-motion-components-preview": "^0.14.1",
+ "@fluentui/react-portal": "^9.8.8",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-toolbar": {
+ "version": "9.6.12",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-toolbar/-/react-toolbar-9.6.12.tgz",
+ "integrity": "sha512-AuOZvp6Jcc/Sngk0OddTsHlJVU/u9mVEw6JDhsCYiwKeq04kdgfco1sjSTGjDhJbf1SnkhmyR6YN16SrpVQWtA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-button": "^9.6.12",
+ "@fluentui/react-context-selector": "^9.2.12",
+ "@fluentui/react-divider": "^9.4.11",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-radio": "^9.5.11",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-tooltip": {
+ "version": "9.8.11",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-tooltip/-/react-tooltip-9.8.11.tgz",
+ "integrity": "sha512-ke7Hbom3dtC3f9QjJG/F7QfNfukwTtAhoYLmwwQnXYTh/CIVxoC2rVh4c/V8jUD0lnjNPBZZ5ttVUopWljHuFg==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/keyboard-keys": "^9.0.8",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-portal": "^9.8.8",
+ "@fluentui/react-positioning": "^9.20.10",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-tree": {
+ "version": "9.15.6",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-tree/-/react-tree-9.15.6.tgz",
+ "integrity": "sha512-L/uc+SgwXW8DXgSZsyIg5tQkixfrGllANg0I2578WRlfOkERehkg1eSW8Uib/Mbk+W3tB0I8CL20ifoSTL7Ztw==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/keyboard-keys": "^9.0.8",
+ "@fluentui/react-aria": "^9.17.6",
+ "@fluentui/react-avatar": "^9.9.12",
+ "@fluentui/react-button": "^9.6.12",
+ "@fluentui/react-checkbox": "^9.5.11",
+ "@fluentui/react-context-selector": "^9.2.12",
+ "@fluentui/react-icons": "^2.0.245",
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-motion": "^9.11.4",
+ "@fluentui/react-motion-components-preview": "^0.14.1",
+ "@fluentui/react-radio": "^9.5.11",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-tabster": "^9.26.10",
+ "@fluentui/react-theme": "^9.2.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-utilities": {
+ "version": "9.25.4",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-utilities/-/react-utilities-9.25.4.tgz",
+ "integrity": "sha512-vvEIFTfqkcBnKNJhlm8csdGNtOWDWDkqAM4tGlW7jLlFrhNkOfDsqdNuBElENPNJ1foHyVTF5ZSr20kVoKWPjQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/keyboard-keys": "^9.0.8",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/react-virtualizer": {
+ "version": "9.0.0-alpha.107",
+ "resolved": "https://registry.npmjs.org/@fluentui/react-virtualizer/-/react-virtualizer-9.0.0-alpha.107.tgz",
+ "integrity": "sha512-zpTVzJB2BUNv7QdTUlLSBMCbt/EfALRuls/u/8FYaO4PGOFVeS3equytyxSOizz9zJZVhm8sjdp326DEQNiaPA==",
+ "license": "MIT",
+ "dependencies": {
+ "@fluentui/react-jsx-runtime": "^9.3.3",
+ "@fluentui/react-shared-contexts": "^9.26.0",
+ "@fluentui/react-utilities": "^9.25.4",
+ "@griffel/react": "^1.5.32",
+ "@swc/helpers": "^0.5.1"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.0 <20.0.0",
+ "@types/react-dom": ">=16.9.0 <20.0.0",
+ "react": ">=16.14.0 <20.0.0",
+ "react-dom": ">=16.14.0 <20.0.0"
+ }
+ },
+ "node_modules/@fluentui/tokens": {
+ "version": "1.0.0-alpha.22",
+ "resolved": "https://registry.npmjs.org/@fluentui/tokens/-/tokens-1.0.0-alpha.22.tgz",
+ "integrity": "sha512-i9fgYyyCWFRdUi+vQwnV6hp7wpLGK4p09B+O/f2u71GBXzPuniubPYvrIJYtl444DD6shLjYToJhQ1S6XTFwLg==",
+ "license": "MIT",
+ "dependencies": {
+ "@swc/helpers": "^0.5.1"
+ }
+ },
+ "node_modules/@griffel/core": {
+ "version": "1.19.2",
+ "resolved": "https://registry.npmjs.org/@griffel/core/-/core-1.19.2.tgz",
+ "integrity": "sha512-WkB/QQkjy9dE4vrNYGhQvRRUHFkYVOuaznVOMNTDT4pS9aTJ9XPrMTXXlkpcwaf0D3vNKoerj4zAwnU2lBzbOg==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/hash": "^0.9.0",
+ "@griffel/style-types": "^1.3.0",
+ "csstype": "^3.1.3",
+ "rtl-css-js": "^1.16.1",
+ "stylis": "^4.2.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@griffel/react": {
+ "version": "1.5.32",
+ "resolved": "https://registry.npmjs.org/@griffel/react/-/react-1.5.32.tgz",
+ "integrity": "sha512-jN3SmSwAUcWFUQuQ9jlhqZ5ELtKY21foaUR0q1mJtiAeSErVgjkpKJyMLRYpvaFGWrDql0Uz23nXUogXbsS2wQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@griffel/core": "^1.19.2",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0 <20.0.0"
+ }
+ },
+ "node_modules/@griffel/style-types": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@griffel/style-types/-/style-types-1.3.0.tgz",
+ "integrity": "sha512-bHwD3sUE84Xwv4dH011gOKe1jul77M1S6ZFN9Tnq8pvZ48UMdY//vtES6fv7GRS5wXYT4iqxQPBluAiYAfkpmw==",
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.1.3"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
+ "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
+ "deprecated": "Use @eslint/config-array instead",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^2.0.3",
+ "debug": "^4.3.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+ "deprecated": "Use @eslint/object-schema instead",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz",
+ "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz",
+ "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz",
+ "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz",
+ "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz",
+ "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz",
+ "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz",
+ "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz",
+ "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz",
+ "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz",
+ "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz",
+ "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz",
+ "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz",
+ "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz",
+ "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz",
+ "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz",
+ "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz",
+ "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz",
+ "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz",
+ "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz",
+ "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz",
+ "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz",
+ "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.17",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
+ "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
+ "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "license": "MIT"
+ },
+ "node_modules/@types/estree-jsx": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz",
+ "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "*"
+ }
+ },
+ "node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/mdast": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
+ "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "24.10.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
+ "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.27",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
+ "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "license": "MIT"
+ },
+ "node_modules/@types/uuid": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
+ "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz",
+ "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "7.18.0",
+ "@typescript-eslint/type-utils": "7.18.0",
+ "@typescript-eslint/utils": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.3.1",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^1.3.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^7.0.0",
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz",
+ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "7.18.0",
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/typescript-estree": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz",
+ "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz",
+ "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "7.18.0",
+ "@typescript-eslint/utils": "7.18.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^1.3.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz",
+ "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz",
+ "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^1.3.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz",
+ "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "@typescript-eslint/scope-manager": "7.18.0",
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/typescript-estree": "7.18.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz",
+ "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "7.18.0",
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "license": "ISC"
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/bail": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
+ "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.8.31",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz",
+ "integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz",
+ "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.8.25",
+ "caniuse-lite": "^1.0.30001754",
+ "electron-to-chromium": "^1.5.249",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.1.4"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001757",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz",
+ "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/ccount": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/character-entities": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-html4": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
+ "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-legacy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-reference-invalid": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
+ "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/comma-separated-tokens": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decode-named-character-reference": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz",
+ "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==",
+ "license": "MIT",
+ "dependencies": {
+ "character-entities": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/devlop": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.261",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.261.tgz",
+ "integrity": "sha512-cmyHEWFqEt3ICUNF93ShneOF47DHoSDbLb7E/AonsWcbzg95N+kPXeLNfkdzgTT/vEUcoW76fxbLBkeYtfoM8A==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/embla-carousel": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
+ "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
+ "license": "MIT"
+ },
+ "node_modules/embla-carousel-autoplay": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/embla-carousel-autoplay/-/embla-carousel-autoplay-8.6.0.tgz",
+ "integrity": "sha512-OBu5G3nwaSXkZCo1A6LTaFMZ8EpkYbwIaH+bPqdBnDGQ2fh4+NbzjXjs2SktoPNKCtflfVMc75njaDHOYXcrsA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "embla-carousel": "8.6.0"
+ }
+ },
+ "node_modules/embla-carousel-fade": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/embla-carousel-fade/-/embla-carousel-fade-8.6.0.tgz",
+ "integrity": "sha512-qaYsx5mwCz72ZrjlsXgs1nKejSrW+UhkbOMwLgfRT7w2LtdEB03nPRI06GHuHv5ac2USvbEiX2/nAHctcDwvpg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "embla-carousel": "8.6.0"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
+ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
+ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.1",
+ "@humanwhocodes/config-array": "^0.13.0",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz",
+ "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.24.tgz",
+ "integrity": "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": ">=8.40"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/eslint/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-util-is-identifier-name": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
+ "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "license": "MIT"
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globby": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/hast-util-to-jsx-runtime": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
+ "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "devlop": "^1.0.0",
+ "estree-util-is-identifier-name": "^3.0.0",
+ "hast-util-whitespace": "^3.0.0",
+ "mdast-util-mdx-expression": "^2.0.0",
+ "mdast-util-mdx-jsx": "^3.0.0",
+ "mdast-util-mdxjs-esm": "^2.0.0",
+ "property-information": "^7.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "style-to-js": "^1.0.0",
+ "unist-util-position": "^5.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-whitespace": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/html-url-attributes": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
+ "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/inline-style-parser": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
+ "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==",
+ "license": "MIT"
+ },
+ "node_modules/is-alphabetical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
+ "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-alphanumerical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
+ "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
+ "license": "MIT",
+ "dependencies": {
+ "is-alphabetical": "^2.0.0",
+ "is-decimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-decimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
+ "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-hexadecimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
+ "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/keyborg": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/keyborg/-/keyborg-2.6.0.tgz",
+ "integrity": "sha512-o5kvLbuTF+o326CMVYpjlaykxqYP9DphFQZ2ZpgrvBouyvOxyEB7oqe8nOLFpiV5VCtz0D3pt8gXQYWpLpBnmA==",
+ "license": "MIT"
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/longest-streak": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
+ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/mdast-util-from-markdown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
+ "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark": "^4.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdx-expression": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
+ "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdx-jsx": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz",
+ "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "ccount": "^2.0.0",
+ "devlop": "^1.1.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "parse-entities": "^4.0.0",
+ "stringify-entities": "^4.0.0",
+ "unist-util-stringify-position": "^4.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdxjs-esm": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz",
+ "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-phrasing": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
+ "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-hast": {
+ "version": "13.2.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz",
+ "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "trim-lines": "^3.0.0",
+ "unist-util-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-markdown": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz",
+ "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "longest-streak": "^3.0.0",
+ "mdast-util-phrasing": "^4.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "unist-util-visit": "^5.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
+ "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromark": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
+ "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@types/debug": "^4.0.0",
+ "debug": "^4.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-combine-extensions": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-core-commonmark": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz",
+ "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-factory-destination": "^2.0.0",
+ "micromark-factory-label": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-factory-title": "^2.0.0",
+ "micromark-factory-whitespace": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-html-tag-name": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-destination": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
+ "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-label": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz",
+ "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-space": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz",
+ "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-title": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz",
+ "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-whitespace": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz",
+ "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-character": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
+ "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-chunked": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz",
+ "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-classify-character": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz",
+ "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-combine-extensions": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz",
+ "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-numeric-character-reference": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz",
+ "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-string": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz",
+ "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-encode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
+ "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-html-tag-name": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz",
+ "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-normalize-identifier": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz",
+ "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-resolve-all": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz",
+ "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-sanitize-uri": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
+ "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-subtokenize": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz",
+ "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-symbol": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
+ "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-types": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
+ "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-entities": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
+ "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "character-entities-legacy": "^3.0.0",
+ "character-reference-invalid": "^2.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "is-alphanumerical": "^2.0.0",
+ "is-decimal": "^2.0.0",
+ "is-hexadecimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/parse-entities/node_modules/@types/unist": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
+ "license": "MIT"
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/property-information": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
+ "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-dom/node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "license": "MIT"
+ },
+ "node_modules/react-markdown": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.1.0.tgz",
+ "integrity": "sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "hast-util-to-jsx-runtime": "^2.0.0",
+ "html-url-attributes": "^3.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "remark-parse": "^11.0.0",
+ "remark-rehype": "^11.0.0",
+ "unified": "^11.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18",
+ "react": ">=18"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/remark-parse": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
+ "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-rehype": {
+ "version": "11.1.2",
+ "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz",
+ "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "unified": "^11.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz",
+ "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.53.3",
+ "@rollup/rollup-android-arm64": "4.53.3",
+ "@rollup/rollup-darwin-arm64": "4.53.3",
+ "@rollup/rollup-darwin-x64": "4.53.3",
+ "@rollup/rollup-freebsd-arm64": "4.53.3",
+ "@rollup/rollup-freebsd-x64": "4.53.3",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.53.3",
+ "@rollup/rollup-linux-arm-musleabihf": "4.53.3",
+ "@rollup/rollup-linux-arm64-gnu": "4.53.3",
+ "@rollup/rollup-linux-arm64-musl": "4.53.3",
+ "@rollup/rollup-linux-loong64-gnu": "4.53.3",
+ "@rollup/rollup-linux-ppc64-gnu": "4.53.3",
+ "@rollup/rollup-linux-riscv64-gnu": "4.53.3",
+ "@rollup/rollup-linux-riscv64-musl": "4.53.3",
+ "@rollup/rollup-linux-s390x-gnu": "4.53.3",
+ "@rollup/rollup-linux-x64-gnu": "4.53.3",
+ "@rollup/rollup-linux-x64-musl": "4.53.3",
+ "@rollup/rollup-openharmony-arm64": "4.53.3",
+ "@rollup/rollup-win32-arm64-msvc": "4.53.3",
+ "@rollup/rollup-win32-ia32-msvc": "4.53.3",
+ "@rollup/rollup-win32-x64-gnu": "4.53.3",
+ "@rollup/rollup-win32-x64-msvc": "4.53.3",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz",
+ "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/rtl-css-js": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz",
+ "integrity": "sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.1.2"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/space-separated-tokens": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/stringify-entities": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
+ "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
+ "license": "MIT",
+ "dependencies": {
+ "character-entities-html4": "^2.0.0",
+ "character-entities-legacy": "^3.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/style-to-js": {
+ "version": "1.1.21",
+ "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz",
+ "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==",
+ "license": "MIT",
+ "dependencies": {
+ "style-to-object": "1.0.14"
+ }
+ },
+ "node_modules/style-to-object": {
+ "version": "1.0.14",
+ "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz",
+ "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==",
+ "license": "MIT",
+ "dependencies": {
+ "inline-style-parser": "0.2.7"
+ }
+ },
+ "node_modules/stylis": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz",
+ "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==",
+ "license": "MIT"
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tabster": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/tabster/-/tabster-8.5.6.tgz",
+ "integrity": "sha512-2vfrRGrx8O9BjdrtSlVA5fvpmbq5HQBRN13XFRg6LAvZ1Fr3QdBnswgT4YgFS5Bhoo5nxwgjRaRueI2Us/dv7g==",
+ "license": "MIT",
+ "dependencies": {
+ "keyborg": "2.6.0",
+ "tslib": "^2.8.1"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-linux-x64-gnu": "4.40.0"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/trim-lines": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/trough": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz",
+ "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
+ "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.2.0"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unified": {
+ "version": "11.0.5",
+ "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
+ "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "bail": "^2.0.0",
+ "devlop": "^1.0.0",
+ "extend": "^3.0.0",
+ "is-plain-obj": "^4.0.0",
+ "trough": "^2.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-is": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz",
+ "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-position": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
+ "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-stringify-position": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
+ "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit-parents": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz",
+ "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz",
+ "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/uuid": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
+ "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/vfile": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
+ "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile-message": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz",
+ "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zwitch": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ }
+ }
+}
diff --git a/content-gen/src/app/frontend/package.json b/content-gen/src/app/frontend/package.json
new file mode 100644
index 000000000..bc10996b4
--- /dev/null
+++ b/content-gen/src/app/frontend/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "content-generation-frontend",
+ "version": "1.0.0",
+ "description": "Frontend for Intelligent Content Generation Accelerator",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
+ },
+ "dependencies": {
+ "@fluentui/react-components": "^9.54.0",
+ "@fluentui/react-icons": "^2.0.245",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-markdown": "^9.0.1",
+ "uuid": "^10.0.0"
+ },
+ "devDependencies": {
+ "@types/node": "^24.10.1",
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
+ "@types/uuid": "^10.0.0",
+ "@typescript-eslint/eslint-plugin": "^7.15.0",
+ "@typescript-eslint/parser": "^7.15.0",
+ "@vitejs/plugin-react": "^4.3.1",
+ "eslint": "^8.57.0",
+ "eslint-plugin-react-hooks": "^4.6.2",
+ "eslint-plugin-react-refresh": "^0.4.7",
+ "typescript": "^5.5.2",
+ "vite": "^5.3.2"
+ }
+}
diff --git a/content-gen/src/app/frontend/src/App.tsx b/content-gen/src/app/frontend/src/App.tsx
new file mode 100644
index 000000000..a0556a178
--- /dev/null
+++ b/content-gen/src/app/frontend/src/App.tsx
@@ -0,0 +1,819 @@
+import { useState, useCallback, useEffect, useRef } from 'react';
+import {
+ Text,
+ Avatar,
+ Button,
+ Tooltip,
+ tokens,
+} from '@fluentui/react-components';
+import {
+ History24Regular,
+ History24Filled,
+} from '@fluentui/react-icons';
+import { v4 as uuidv4 } from 'uuid';
+
+import { ChatPanel } from './components/ChatPanel';
+import { ChatHistory } from './components/ChatHistory';
+import type { ChatMessage, CreativeBrief, Product, GeneratedContent } from './types';
+import ContosoLogo from './styles/images/contoso.svg';
+
+interface UserInfo {
+ user_principal_id: string;
+ user_name: string;
+ auth_provider: string;
+ is_authenticated: boolean;
+}
+
+
+function App() {
+ const [conversationId, setConversationId] = useState(() => uuidv4());
+ const [userId, setUserId] = useState('');
+ const [messages, setMessages] = useState([]);
+ const [isLoading, setIsLoading] = useState(false);
+ const [generationStatus, setGenerationStatus] = useState('');
+
+ // Feature flags from config
+ const [imageGenerationEnabled, setImageGenerationEnabled] = useState(true);
+
+ // Brief confirmation flow
+ const [pendingBrief, setPendingBrief] = useState(null);
+ const [confirmedBrief, setConfirmedBrief] = useState(null);
+ const [awaitingClarification, setAwaitingClarification] = useState(false);
+
+ // Product selection
+ const [selectedProducts, setSelectedProducts] = useState([]);
+ const [availableProducts, setAvailableProducts] = useState([]);
+
+ // Generated content
+ const [generatedContent, setGeneratedContent] = useState(null);
+
+ // Trigger for refreshing chat history
+ const [historyRefreshTrigger, setHistoryRefreshTrigger] = useState(0);
+
+ // Toggle for showing/hiding chat history panel
+ const [showChatHistory, setShowChatHistory] = useState(true);
+
+ // Abort controller for cancelling ongoing requests
+ const abortControllerRef = useRef(null);
+
+ // Fetch app config on mount
+ useEffect(() => {
+ const fetchConfig = async () => {
+ try {
+ const { getAppConfig } = await import('./api');
+ const config = await getAppConfig();
+ setImageGenerationEnabled(config.enable_image_generation);
+ } catch (err) {
+ console.error('Error fetching config:', err);
+ // Default to enabled if config fetch fails
+ setImageGenerationEnabled(true);
+ }
+ };
+ fetchConfig();
+ }, []);
+
+ // Fetch current user on mount
+ useEffect(() => {
+ const fetchUser = async () => {
+ try {
+ const response = await fetch('/api/user');
+ if (response.ok) {
+ const user: UserInfo = await response.json();
+ setUserId(user.user_principal_id || 'anonymous');
+ }
+ } catch (err) {
+ console.error('Error fetching user:', err);
+ setUserId('anonymous');
+ }
+ };
+ fetchUser();
+ }, []);
+
+ // Handle selecting a conversation from history
+ const handleSelectConversation = useCallback(async (selectedConversationId: string) => {
+ try {
+ const response = await fetch(`/api/conversations/${selectedConversationId}?user_id=${encodeURIComponent(userId)}`);
+ if (response.ok) {
+ const data = await response.json();
+ setConversationId(selectedConversationId);
+ const loadedMessages: ChatMessage[] = (data.messages || []).map((msg: { role: string; content: string; timestamp?: string; agent?: string }, index: number) => ({
+ id: `${selectedConversationId}-${index}`,
+ role: msg.role as 'user' | 'assistant',
+ content: msg.content,
+ timestamp: msg.timestamp || new Date().toISOString(),
+ agent: msg.agent,
+ }));
+ setMessages(loadedMessages);
+ setPendingBrief(null);
+ setAwaitingClarification(false);
+ setConfirmedBrief(data.brief || null);
+
+ if (data.generated_content) {
+ const gc = data.generated_content;
+ let textContent = gc.text_content;
+ if (typeof textContent === 'string') {
+ try {
+ textContent = JSON.parse(textContent);
+ } catch {
+ }
+ }
+
+ let imageUrl: string | undefined = gc.image_url;
+ if (imageUrl && imageUrl.includes('blob.core.windows.net')) {
+ const parts = imageUrl.split('/');
+ const filename = parts[parts.length - 1];
+ const convId = parts[parts.length - 2];
+ imageUrl = `/api/images/${convId}/${filename}`;
+ }
+ if (!imageUrl && gc.image_base64) {
+ imageUrl = `data:image/png;base64,${gc.image_base64}`;
+ }
+
+ const restoredContent: GeneratedContent = {
+ text_content: typeof textContent === 'object' && textContent ? {
+ headline: textContent?.headline,
+ body: textContent?.body,
+ cta_text: textContent?.cta,
+ tagline: textContent?.tagline,
+ } : undefined,
+ image_content: (imageUrl || gc.image_prompt) ? {
+ image_url: imageUrl,
+ prompt_used: gc.image_prompt,
+ alt_text: gc.image_revised_prompt || 'Generated marketing image',
+ } : undefined,
+ violations: gc.violations || [],
+ requires_modification: gc.requires_modification || false,
+ error: gc.error,
+ image_error: gc.image_error,
+ text_error: gc.text_error,
+ };
+ setGeneratedContent(restoredContent);
+
+ if (gc.selected_products && Array.isArray(gc.selected_products)) {
+ setSelectedProducts(gc.selected_products);
+ } else {
+ setSelectedProducts([]);
+ }
+ } else {
+ setGeneratedContent(null);
+ setSelectedProducts([]);
+ }
+ }
+ } catch (error) {
+ console.error('Error loading conversation:', error);
+ }
+ }, [userId]);
+
+ // Handle starting a new conversation
+ const handleNewConversation = useCallback(() => {
+ setConversationId(uuidv4());
+ setMessages([]);
+ setPendingBrief(null);
+ setAwaitingClarification(false);
+ setConfirmedBrief(null);
+ setGeneratedContent(null);
+ setSelectedProducts([]);
+ }, []);
+
+ const handleSendMessage = useCallback(async (content: string) => {
+ const userMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'user',
+ content,
+ timestamp: new Date().toISOString(),
+ };
+
+ setMessages(prev => [...prev, userMessage]);
+ setIsLoading(true);
+
+ // Create new abort controller for this request
+ abortControllerRef.current = new AbortController();
+ const signal = abortControllerRef.current.signal;
+
+ try {
+ // Import dynamically to avoid SSR issues
+ const { streamChat, parseBrief, selectProducts } = await import('./api');
+
+ // If we have a pending brief and user is providing feedback, update the brief
+ if (pendingBrief && !confirmedBrief) {
+ // User is refining the brief or providing clarification
+ const refinementKeywords = ['change', 'update', 'modify', 'add', 'remove', 'delete', 'set', 'make', 'should be'];
+ const isRefinement = refinementKeywords.some(kw => content.toLowerCase().includes(kw));
+
+ // If awaiting clarification, treat ANY response as a brief update
+ if (isRefinement || awaitingClarification) {
+ // Send the refinement request to update the brief
+ // Combine original brief context with the refinement request
+ const refinementPrompt = `Current creative brief:\n${JSON.stringify(pendingBrief, null, 2)}\n\nUser requested change: ${content}\n\nPlease update the brief accordingly and return the complete updated brief.`;
+
+ setGenerationStatus('Updating creative brief...');
+ const parsed = await parseBrief(refinementPrompt, conversationId, userId, signal);
+ if (parsed.brief) {
+ setPendingBrief(parsed.brief);
+ }
+
+ // Check if we still need more clarification
+ if (parsed.requires_clarification && parsed.clarifying_questions) {
+ setAwaitingClarification(true);
+ setGenerationStatus('');
+
+ const assistantMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'assistant',
+ content: parsed.clarifying_questions,
+ agent: 'PlanningAgent',
+ timestamp: new Date().toISOString(),
+ };
+ setMessages(prev => [...prev, assistantMessage]);
+ } else {
+ // Brief is now complete
+ setAwaitingClarification(false);
+ setGenerationStatus('');
+
+ const assistantMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'assistant',
+ content: "I've updated the brief based on your feedback. Please review the changes above. Let me know if you'd like any other modifications, or click **Confirm Brief** when you're satisfied.",
+ agent: 'PlanningAgent',
+ timestamp: new Date().toISOString(),
+ };
+ setMessages(prev => [...prev, assistantMessage]);
+ }
+ } else {
+ // General question or comment while brief is pending
+ let fullContent = '';
+ let currentAgent = '';
+ let messageAdded = false;
+
+ setGenerationStatus('Processing your question...');
+ for await (const response of streamChat(content, conversationId, userId, signal)) {
+ if (response.type === 'agent_response') {
+ fullContent = response.content;
+ currentAgent = response.agent || '';
+
+ if ((response.is_final || response.requires_user_input) && !messageAdded) {
+ const assistantMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'assistant',
+ content: fullContent,
+ agent: currentAgent,
+ timestamp: new Date().toISOString(),
+ };
+ setMessages(prev => [...prev, assistantMessage]);
+ messageAdded = true;
+ }
+ } else if (response.type === 'error') {
+ const errorMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'assistant',
+ content: response.content || 'An error occurred while processing your request.',
+ timestamp: new Date().toISOString(),
+ };
+ setMessages(prev => [...prev, errorMessage]);
+ messageAdded = true;
+ }
+ }
+ setGenerationStatus('');
+ }
+ } else if (confirmedBrief && !generatedContent) {
+ // Brief confirmed, in product selection phase - treat messages as product selection requests
+ setGenerationStatus('Finding products...');
+ const result = await selectProducts(content, selectedProducts, conversationId, userId, signal);
+
+ // Update selected products with the result
+ setSelectedProducts(result.products || []);
+ setGenerationStatus('');
+
+ const assistantMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'assistant',
+ content: result.message || 'Products updated.',
+ agent: 'ProductAgent',
+ timestamp: new Date().toISOString(),
+ };
+ setMessages(prev => [...prev, assistantMessage]);
+ } else if (generatedContent && confirmedBrief) {
+ // Content has been generated - check if user wants to modify the image
+ const imageModificationKeywords = [
+ 'change', 'modify', 'update', 'replace', 'show', 'display', 'use',
+ 'instead', 'different', 'another', 'make it', 'make the',
+ 'kitchen', 'dining', 'living', 'bedroom', 'bathroom', 'outdoor', 'office',
+ 'room', 'scene', 'setting', 'background', 'style', 'color', 'lighting'
+ ];
+ const isImageModification = imageModificationKeywords.some(kw => content.toLowerCase().includes(kw));
+
+ if (isImageModification) {
+ // User wants to modify the image - use regeneration endpoint
+ const { streamRegenerateImage } = await import('./api');
+
+ setGenerationStatus('Regenerating image with your changes...');
+
+ let responseData: GeneratedContent | null = null;
+ let messageContent = '';
+
+ // Get previous prompt from image_content if available
+ const previousPrompt = generatedContent.image_content?.prompt_used;
+
+ for await (const response of streamRegenerateImage(
+ content,
+ confirmedBrief,
+ selectedProducts,
+ previousPrompt,
+ conversationId,
+ userId,
+ signal
+ )) {
+ if (response.type === 'heartbeat') {
+ setGenerationStatus(response.message || 'Regenerating image...');
+ } else if (response.type === 'agent_response' && response.is_final) {
+ try {
+ const parsedContent = JSON.parse(response.content);
+
+ // Update generatedContent with new image
+ if (parsedContent.image_url || parsedContent.image_base64) {
+ responseData = {
+ ...generatedContent,
+ image_content: {
+ ...generatedContent.image_content,
+ image_url: parsedContent.image_url || generatedContent.image_content?.image_url,
+ image_base64: parsedContent.image_base64,
+ prompt_used: parsedContent.image_prompt || generatedContent.image_content?.prompt_used,
+ },
+ };
+ setGeneratedContent(responseData);
+
+ // Update the confirmed brief to include the modification
+ // This ensures subsequent "Regenerate" clicks use the updated visual guidelines
+ const updatedBrief = {
+ ...confirmedBrief,
+ visual_guidelines: `${confirmedBrief.visual_guidelines}. User modification: ${content}`,
+ };
+ setConfirmedBrief(updatedBrief);
+
+ messageContent = parsedContent.message || 'Image regenerated with your requested changes.';
+ } else if (parsedContent.error) {
+ messageContent = parsedContent.error;
+ } else {
+ messageContent = parsedContent.message || 'I processed your request.';
+ }
+ } catch {
+ messageContent = response.content || 'Image regenerated.';
+ }
+ } else if (response.type === 'error') {
+ messageContent = response.content || 'An error occurred while regenerating the image.';
+ }
+ }
+
+ setGenerationStatus('');
+
+ const assistantMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'assistant',
+ content: messageContent,
+ agent: 'ImageAgent',
+ timestamp: new Date().toISOString(),
+ };
+ setMessages(prev => [...prev, assistantMessage]);
+ } else {
+ // General question after content generation - use regular chat
+ let fullContent = '';
+ let currentAgent = '';
+ let messageAdded = false;
+
+ setGenerationStatus('Processing your request...');
+ for await (const response of streamChat(content, conversationId, userId, signal)) {
+ if (response.type === 'agent_response') {
+ fullContent = response.content;
+ currentAgent = response.agent || '';
+
+ if ((response.is_final || response.requires_user_input) && !messageAdded) {
+ const assistantMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'assistant',
+ content: fullContent,
+ agent: currentAgent,
+ timestamp: new Date().toISOString(),
+ };
+ setMessages(prev => [...prev, assistantMessage]);
+ messageAdded = true;
+ }
+ } else if (response.type === 'error') {
+ const errorMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'assistant',
+ content: response.content || 'An error occurred.',
+ timestamp: new Date().toISOString(),
+ };
+ setMessages(prev => [...prev, errorMessage]);
+ messageAdded = true;
+ }
+ }
+ setGenerationStatus('');
+ }
+ } else {
+ // Check if this looks like a creative brief
+ const briefKeywords = ['campaign', 'marketing', 'target audience', 'objective', 'deliverable'];
+ const isBriefLike = briefKeywords.some(kw => content.toLowerCase().includes(kw));
+
+ if (isBriefLike && !confirmedBrief) {
+ // Parse as a creative brief
+ setGenerationStatus('Analyzing creative brief...');
+ const parsed = await parseBrief(content, conversationId, userId, signal);
+
+ // Check if request was blocked due to harmful content
+ if (parsed.rai_blocked) {
+ // Show the refusal message without any brief UI
+ setGenerationStatus('');
+
+ const assistantMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'assistant',
+ content: parsed.message,
+ agent: 'ContentSafety',
+ timestamp: new Date().toISOString(),
+ };
+ setMessages(prev => [...prev, assistantMessage]);
+ } else if (parsed.requires_clarification && parsed.clarifying_questions) {
+ // Set partial brief for display but show clarifying questions
+ if (parsed.brief) {
+ setPendingBrief(parsed.brief);
+ }
+ setAwaitingClarification(true);
+ setGenerationStatus('');
+
+ const assistantMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'assistant',
+ content: parsed.clarifying_questions,
+ agent: 'PlanningAgent',
+ timestamp: new Date().toISOString(),
+ };
+ setMessages(prev => [...prev, assistantMessage]);
+ } else {
+ // Brief is complete, show for confirmation
+ if (parsed.brief) {
+ setPendingBrief(parsed.brief);
+ }
+ setAwaitingClarification(false);
+ setGenerationStatus('');
+
+ const assistantMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'assistant',
+ content: "I've parsed your creative brief. Please review the details below and let me know if you'd like to make any changes. You can say things like \"change the target audience to...\" or \"add a call to action...\". When everything looks good, click **Confirm Brief** to proceed.",
+ agent: 'PlanningAgent',
+ timestamp: new Date().toISOString(),
+ };
+ setMessages(prev => [...prev, assistantMessage]);
+ }
+ } else {
+ // Stream chat response
+ let fullContent = '';
+ let currentAgent = '';
+ let messageAdded = false;
+
+ setGenerationStatus('Processing your request...');
+ for await (const response of streamChat(content, conversationId, userId, signal)) {
+ if (response.type === 'agent_response') {
+ fullContent = response.content;
+ currentAgent = response.agent || '';
+
+ // Add message when final OR when requiring user input (interactive response)
+ if ((response.is_final || response.requires_user_input) && !messageAdded) {
+ const assistantMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'assistant',
+ content: fullContent,
+ agent: currentAgent,
+ timestamp: new Date().toISOString(),
+ };
+ setMessages(prev => [...prev, assistantMessage]);
+ messageAdded = true;
+ }
+ } else if (response.type === 'error') {
+ // Handle error responses
+ const errorMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'assistant',
+ content: response.content || 'An error occurred while processing your request.',
+ timestamp: new Date().toISOString(),
+ };
+ setMessages(prev => [...prev, errorMessage]);
+ messageAdded = true;
+ }
+ }
+ setGenerationStatus('');
+ }
+ }
+ } catch (error) {
+ // Check if this was a user-initiated cancellation
+ if (error instanceof Error && error.name === 'AbortError') {
+ console.log('Request cancelled by user');
+ const cancelMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'assistant',
+ content: 'Generation stopped.',
+ timestamp: new Date().toISOString(),
+ };
+ setMessages(prev => [...prev, cancelMessage]);
+ } else {
+ console.error('Error sending message:', error);
+ const errorMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'assistant',
+ content: 'Sorry, there was an error processing your request. Please try again.',
+ timestamp: new Date().toISOString(),
+ };
+ setMessages(prev => [...prev, errorMessage]);
+ }
+ } finally {
+ setIsLoading(false);
+ setGenerationStatus('');
+ abortControllerRef.current = null;
+ // Trigger refresh of chat history after message is sent
+ setHistoryRefreshTrigger(prev => prev + 1);
+ }
+ }, [conversationId, userId, confirmedBrief, pendingBrief, selectedProducts, generatedContent]);
+
+ const handleBriefConfirm = useCallback(async () => {
+ if (!pendingBrief) return;
+
+ try {
+ const { confirmBrief } = await import('./api');
+ await confirmBrief(pendingBrief, conversationId, userId);
+ setConfirmedBrief(pendingBrief);
+ setPendingBrief(null);
+ setAwaitingClarification(false);
+
+ const productsResponse = await fetch('/api/products');
+ if (productsResponse.ok) {
+ const productsData = await productsResponse.json();
+ setAvailableProducts(productsData.products || []);
+ }
+
+ const assistantMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'assistant',
+ content: "Great! Your creative brief has been confirmed. Here are the available products for your campaign. Select the ones you'd like to feature, or tell me what you're looking for.",
+ agent: 'ProductAgent',
+ timestamp: new Date().toISOString(),
+ };
+ setMessages(prev => [...prev, assistantMessage]);
+ } catch (error) {
+ console.error('Error confirming brief:', error);
+ }
+ }, [conversationId, userId, pendingBrief]);
+
+ const handleBriefCancel = useCallback(() => {
+ setPendingBrief(null);
+ setAwaitingClarification(false);
+ const assistantMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'assistant',
+ content: 'No problem. Please provide your creative brief again or ask me any questions.',
+ timestamp: new Date().toISOString(),
+ };
+ setMessages(prev => [...prev, assistantMessage]);
+ }, []);
+
+ const handleProductsStartOver = useCallback(() => {
+ setSelectedProducts([]);
+ setConfirmedBrief(null);
+ const assistantMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'assistant',
+ content: 'Starting over. Please provide your creative brief to begin a new campaign.',
+ timestamp: new Date().toISOString(),
+ };
+ setMessages(prev => [...prev, assistantMessage]);
+ }, []);
+
+ const handleProductSelect = useCallback((product: Product) => {
+ setSelectedProducts(prev => {
+ const isSelected = prev.some(p => (p.sku || p.product_name) === (product.sku || product.product_name));
+ if (isSelected) {
+ // Deselect - but user must have at least one selected to proceed
+ return [];
+ } else {
+ // Single selection mode - replace any existing selection
+ return [product];
+ }
+ });
+ }, []);
+
+ const handleStopGeneration = useCallback(() => {
+ if (abortControllerRef.current) {
+ abortControllerRef.current.abort();
+ }
+ }, []);
+
+ const handleGenerateContent = useCallback(async () => {
+ if (!confirmedBrief) return;
+
+ setIsLoading(true);
+ setGenerationStatus('Starting content generation...');
+
+ // Create new abort controller for this request
+ abortControllerRef.current = new AbortController();
+ const signal = abortControllerRef.current.signal;
+
+ try {
+ const { streamGenerateContent } = await import('./api');
+
+ for await (const response of streamGenerateContent(
+ confirmedBrief,
+ selectedProducts,
+ true,
+ conversationId,
+ userId,
+ signal
+ )) {
+ // Handle heartbeat events to show progress
+ if (response.type === 'heartbeat') {
+ // Use the message from the heartbeat directly - it contains the stage description
+ const statusMessage = response.content || 'Generating content...';
+ const elapsed = (response as { elapsed?: number }).elapsed || 0;
+ setGenerationStatus(`${statusMessage} (${elapsed}s)`);
+ continue;
+ }
+
+ if (response.is_final && response.type !== 'error') {
+ setGenerationStatus('Processing results...');
+ try {
+ const rawContent = JSON.parse(response.content);
+
+ // Parse text_content if it's a string (from orchestrator)
+ let textContent = rawContent.text_content;
+ if (typeof textContent === 'string') {
+ try {
+ textContent = JSON.parse(textContent);
+ } catch {
+ // Keep as string if not valid JSON
+ }
+ }
+
+ // Build image_url: prefer blob URL, fallback to base64 data URL
+ let imageUrl: string | undefined;
+ if (rawContent.image_url) {
+ imageUrl = rawContent.image_url;
+ } else if (rawContent.image_base64) {
+ imageUrl = `data:image/png;base64,${rawContent.image_base64}`;
+ }
+
+ const content: GeneratedContent = {
+ text_content: typeof textContent === 'object' ? {
+ headline: textContent?.headline,
+ body: textContent?.body,
+ cta_text: textContent?.cta,
+ tagline: textContent?.tagline,
+ } : undefined,
+ image_content: (imageUrl || rawContent.image_prompt) ? {
+ image_url: imageUrl,
+ prompt_used: rawContent.image_prompt,
+ alt_text: rawContent.image_revised_prompt || 'Generated marketing image',
+ } : undefined,
+ violations: rawContent.violations || [],
+ requires_modification: rawContent.requires_modification || false,
+ // Capture any generation errors
+ error: rawContent.error,
+ image_error: rawContent.image_error,
+ text_error: rawContent.text_error,
+ };
+ setGeneratedContent(content);
+ setGenerationStatus('');
+
+ // Content is displayed via InlineContentPreview - no need for a separate chat message
+ } catch (parseError) {
+ console.error('Error parsing generated content:', parseError);
+ }
+ } else if (response.type === 'error') {
+ setGenerationStatus('');
+ const errorMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'assistant',
+ content: `Error generating content: ${response.content}`,
+ timestamp: new Date().toISOString(),
+ };
+ setMessages(prev => [...prev, errorMessage]);
+ }
+ }
+ } catch (error) {
+ // Check if this was a user-initiated cancellation
+ if (error instanceof Error && error.name === 'AbortError') {
+ console.log('Content generation cancelled by user');
+ const cancelMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'assistant',
+ content: 'Content generation stopped.',
+ timestamp: new Date().toISOString(),
+ };
+ setMessages(prev => [...prev, cancelMessage]);
+ } else {
+ console.error('Error generating content:', error);
+ const errorMessage: ChatMessage = {
+ id: uuidv4(),
+ role: 'assistant',
+ content: 'Sorry, there was an error generating content. Please try again.',
+ timestamp: new Date().toISOString(),
+ };
+ setMessages(prev => [...prev, errorMessage]);
+ }
+ } finally {
+ setIsLoading(false);
+ setGenerationStatus('');
+ abortControllerRef.current = null;
+ }
+ }, [confirmedBrief, selectedProducts, conversationId]);
+
+ // Get user initials for avatar
+ const getUserInitials = () => {
+ if (!userId) return 'U';
+ // If we have a name, use first letter of first and last name
+ const parts = userId.split('@')[0].split('.');
+ if (parts.length >= 2) {
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
+ }
+ return userId[0].toUpperCase();
+ };
+
+ return (
+
+
+ {/* Chat History Sidebar - RIGHT side */}
+ {showChatHistory && (
+
+
+
+ )}
+
+
+ );
+}
+
+export default App;
diff --git a/content-gen/src/app/frontend/src/api/index.ts b/content-gen/src/app/frontend/src/api/index.ts
new file mode 100644
index 000000000..1525bd366
--- /dev/null
+++ b/content-gen/src/app/frontend/src/api/index.ts
@@ -0,0 +1,351 @@
+/**
+ * API service for interacting with the Content Generation backend
+ */
+
+import type {
+ CreativeBrief,
+ Product,
+ AgentResponse,
+ ParsedBriefResponse,
+ AppConfig,
+} from '../types';
+
+const API_BASE = '/api';
+
+/**
+ * Get application configuration including feature flags
+ */
+export async function getAppConfig(): Promise {
+ const response = await fetch(`${API_BASE}/config`);
+
+ if (!response.ok) {
+ throw new Error(`Failed to get config: ${response.statusText}`);
+ }
+
+ return response.json();
+}
+
+/**
+ * Parse a free-text creative brief into structured format
+ */
+export async function parseBrief(
+ briefText: string,
+ conversationId?: string,
+ userId?: string,
+ signal?: AbortSignal
+): Promise {
+ const response = await fetch(`${API_BASE}/brief/parse`, {
+ signal,
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ brief_text: briefText,
+ conversation_id: conversationId,
+ user_id: userId || 'anonymous',
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`Failed to parse brief: ${response.statusText}`);
+ }
+
+ return response.json();
+}
+
+/**
+ * Confirm a parsed creative brief
+ */
+export async function confirmBrief(
+ brief: CreativeBrief,
+ conversationId?: string,
+ userId?: string
+): Promise<{ status: string; conversation_id: string; brief: CreativeBrief }> {
+ const response = await fetch(`${API_BASE}/brief/confirm`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ brief,
+ conversation_id: conversationId,
+ user_id: userId || 'anonymous',
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`Failed to confirm brief: ${response.statusText}`);
+ }
+
+ return response.json();
+}
+
+/**
+ * Select or modify products via natural language
+ */
+export async function selectProducts(
+ request: string,
+ currentProducts: Product[],
+ conversationId?: string,
+ userId?: string,
+ signal?: AbortSignal
+): Promise<{ products: Product[]; action: string; message: string; conversation_id: string }> {
+ const response = await fetch(`${API_BASE}/products/select`, {
+ signal,
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ request,
+ current_products: currentProducts,
+ conversation_id: conversationId,
+ user_id: userId || 'anonymous',
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`Failed to select products: ${response.statusText}`);
+ }
+
+ return response.json();
+}
+
+/**
+ * Stream chat messages from the agent orchestration
+ */
+export async function* streamChat(
+ message: string,
+ conversationId?: string,
+ userId?: string,
+ signal?: AbortSignal
+): AsyncGenerator {
+ const response = await fetch(`${API_BASE}/chat`, {
+ signal,
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ message,
+ conversation_id: conversationId,
+ user_id: userId || 'anonymous',
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`Chat request failed: ${response.statusText}`);
+ }
+
+ const reader = response.body?.getReader();
+ if (!reader) {
+ throw new Error('No response body');
+ }
+
+ const decoder = new TextDecoder();
+ let buffer = '';
+
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ buffer += decoder.decode(value, { stream: true });
+ const lines = buffer.split('\n\n');
+ buffer = lines.pop() || '';
+
+ for (const line of lines) {
+ if (line.startsWith('data: ')) {
+ const data = line.slice(6);
+ if (data === '[DONE]') {
+ return;
+ }
+ try {
+ yield JSON.parse(data) as AgentResponse;
+ } catch {
+ console.error('Failed to parse SSE data:', data);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Generate content from a confirmed brief
+ */
+export async function* streamGenerateContent(
+ brief: CreativeBrief,
+ products?: Product[],
+ generateImages: boolean = true,
+ conversationId?: string,
+ userId?: string,
+ signal?: AbortSignal
+): AsyncGenerator {
+ // Use polling-based approach for reliability with long-running tasks
+ const startResponse = await fetch(`${API_BASE}/generate/start`, {
+ signal,
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ brief,
+ products: products || [],
+ generate_images: generateImages,
+ conversation_id: conversationId,
+ user_id: userId || 'anonymous',
+ }),
+ });
+
+ if (!startResponse.ok) {
+ throw new Error(`Content generation failed to start: ${startResponse.statusText}`);
+ }
+
+ const startData = await startResponse.json();
+ const taskId = startData.task_id;
+
+ console.log(`Generation started with task ID: ${taskId}`);
+
+ // Yield initial status
+ yield {
+ type: 'status',
+ content: 'Generation started...',
+ is_final: false,
+ } as AgentResponse;
+
+ // Poll for completion
+ let attempts = 0;
+ const maxAttempts = 600; // 10 minutes max with 1-second polling (image generation can take 3-5 min)
+ const pollInterval = 1000; // 1 second
+
+ while (attempts < maxAttempts) {
+ // Check if cancelled before waiting
+ if (signal?.aborted) {
+ throw new DOMException('Generation cancelled by user', 'AbortError');
+ }
+
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
+ attempts++;
+
+ // Check if cancelled after waiting
+ if (signal?.aborted) {
+ throw new DOMException('Generation cancelled by user', 'AbortError');
+ }
+
+ try {
+ const statusResponse = await fetch(`${API_BASE}/generate/status/${taskId}`, { signal });
+ if (!statusResponse.ok) {
+ throw new Error(`Failed to get task status: ${statusResponse.statusText}`);
+ }
+
+ const statusData = await statusResponse.json();
+
+ if (statusData.status === 'completed') {
+ // Yield the final result
+ yield {
+ type: 'agent_response',
+ content: JSON.stringify(statusData.result),
+ is_final: true,
+ } as AgentResponse;
+ return;
+ } else if (statusData.status === 'failed') {
+ throw new Error(statusData.error || 'Generation failed');
+ } else if (statusData.status === 'running') {
+ // Determine progress stage based on elapsed time
+ // Typical generation: 0-10s briefing, 10-25s copy, 25-45s image, 45-60s compliance
+ const elapsedSeconds = attempts;
+ let stage: number;
+ let stageMessage: string;
+
+ if (elapsedSeconds < 10) {
+ stage = 0;
+ stageMessage = 'Analyzing creative brief...';
+ } else if (elapsedSeconds < 25) {
+ stage = 1;
+ stageMessage = 'Generating marketing copy...';
+ } else if (elapsedSeconds < 35) {
+ stage = 2;
+ stageMessage = 'Creating image prompt...';
+ } else if (elapsedSeconds < 55) {
+ stage = 3;
+ stageMessage = 'Generating image with AI...';
+ } else if (elapsedSeconds < 70) {
+ stage = 4;
+ stageMessage = 'Running compliance check...';
+ } else {
+ stage = 5;
+ stageMessage = 'Finalizing content...';
+ }
+
+ // Send status update every second for smoother progress
+ yield {
+ type: 'heartbeat',
+ content: stageMessage,
+ count: stage,
+ elapsed: elapsedSeconds,
+ is_final: false,
+ } as AgentResponse;
+ }
+ } catch (error) {
+ console.error(`Error polling task ${taskId}:`, error);
+ // Continue polling on transient errors
+ if (attempts >= maxAttempts) {
+ throw error;
+ }
+ }
+ }
+
+ throw new Error('Generation timed out after 10 minutes');
+}
+/**
+ * Regenerate image with a modification request
+ * Used when user wants to change the generated image after initial content generation
+ */
+export async function* streamRegenerateImage(
+ modificationRequest: string,
+ brief: CreativeBrief,
+ products?: Product[],
+ previousImagePrompt?: string,
+ conversationId?: string,
+ userId?: string,
+ signal?: AbortSignal
+): AsyncGenerator {
+ const response = await fetch(`${API_BASE}/regenerate`, {
+ signal,
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ modification_request: modificationRequest,
+ brief,
+ products: products || [],
+ previous_image_prompt: previousImagePrompt,
+ conversation_id: conversationId,
+ user_id: userId || 'anonymous',
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`Regeneration request failed: ${response.statusText}`);
+ }
+
+ const reader = response.body?.getReader();
+ if (!reader) {
+ throw new Error('No response body');
+ }
+
+ const decoder = new TextDecoder();
+ let buffer = '';
+
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ buffer += decoder.decode(value, { stream: true });
+ const lines = buffer.split('\n\n');
+ buffer = lines.pop() || '';
+
+ for (const line of lines) {
+ if (line.startsWith('data: ')) {
+ const data = line.slice(6);
+ if (data === '[DONE]') {
+ return;
+ }
+ try {
+ yield JSON.parse(data) as AgentResponse;
+ } catch {
+ console.error('Failed to parse SSE data:', data);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/content-gen/src/app/frontend/src/components/BriefReview.tsx b/content-gen/src/app/frontend/src/components/BriefReview.tsx
new file mode 100644
index 000000000..6ea755905
--- /dev/null
+++ b/content-gen/src/app/frontend/src/components/BriefReview.tsx
@@ -0,0 +1,185 @@
+import {
+ Button,
+ Text,
+ tokens,
+} from '@fluentui/react-components';
+import type { CreativeBrief } from '../types';
+
+interface BriefReviewProps {
+ brief: CreativeBrief;
+ onConfirm: () => void;
+ onStartOver: () => void;
+ isAwaitingResponse?: boolean;
+}
+
+// Mapping of field keys to user-friendly labels for the 9 key areas
+const fieldLabels: Record = {
+ overview: 'Overview',
+ objectives: 'Objectives',
+ target_audience: 'Target Audience',
+ key_message: 'Key Message',
+ tone_and_style: 'Tone and Style',
+ deliverable: 'Deliverable',
+ timelines: 'Timelines',
+ visual_guidelines: 'Visual Guidelines',
+ cta: 'Call to Action',
+};
+
+export function BriefReview({
+ brief,
+ onConfirm,
+ onStartOver,
+ isAwaitingResponse = false,
+}: BriefReviewProps) {
+ const allFields: (keyof CreativeBrief)[] = [
+ 'overview', 'objectives', 'target_audience', 'key_message',
+ 'tone_and_style', 'deliverable', 'timelines', 'visual_guidelines', 'cta'
+ ];
+ const populatedFields = allFields.filter(key => brief[key]?.trim()).length;
+ const missingFields = allFields.filter(key => !brief[key]?.trim());
+
+ // Define the order and labels for display in the card
+ const displayOrder: { key: keyof CreativeBrief; label: string }[] = [
+ { key: 'overview', label: 'Campaign Objective' },
+ { key: 'objectives', label: 'Objectives' },
+ { key: 'target_audience', label: 'Target Audience' },
+ { key: 'key_message', label: 'Key Message' },
+ { key: 'tone_and_style', label: 'Tone & Style' },
+ { key: 'visual_guidelines', label: 'Visual Guidelines' },
+ { key: 'deliverable', label: 'Deliverables' },
+ { key: 'timelines', label: 'Timelines' },
+ { key: 'cta', label: 'Call to Action' },
+ ];
+
+ // Filter to only populated fields
+ const populatedDisplayFields = displayOrder.filter(({ key }) => brief[key]?.trim());
+
+ return (
+
+ {/* Header text */}
+
+ Thanks—here's my understanding:
+
+
+ {/* All Brief Fields in a single bordered card */}
+ {populatedDisplayFields.length > 0 && (
+
+
+ {populatedFields < 5 ? (
+ <>
+ I've captured {populatedFields} of 9 key areas. Would you like to add more details?
+ You are missing: {missingFields.map(f => fieldLabels[f]).join(', ')}.
+
+ You can tell me things like:
+
+
"The target audience should be homeowners aged 35-55"
+
"Add a timeline of 2 weeks for the campaign"
+
"The tone should be warm and inviting"
+
+ >
+ ) : (
+ <>
+ Does this look correct? You can:
+
+
Modify: "Change the target audience to young professionals"
+
Add: "Add a call to action: Shop Now"
+
Remove: "Remove the timelines section"
+
+ Or if everything looks good, click Confirm brief to proceed.
+ >
+ )}
+
+
+
+
+ {getErrorMessage(image_error || error).title}
+
+
+ Click Regenerate to try again
+
+
+ )}
+
+
+
+ {/* User guidance callout for compliance status */}
+ {requires_modification ? (
+
+
+ Action needed: This content has compliance issues that must be addressed before use.
+ Please review the details in the Compliance Guidelines section below and regenerate with modifications,
+ or manually edit the content to resolve the flagged items.
+
+
+ ) : violations.length > 0 ? (
+
+
+ Optional review: This content is approved but has minor suggestions for improvement.
+ You can use it as-is or review the recommendations in the Compliance Guidelines section below.
+
+
+ );
+}
diff --git a/content-gen/src/app/frontend/src/components/WelcomeCard.tsx b/content-gen/src/app/frontend/src/components/WelcomeCard.tsx
new file mode 100644
index 000000000..ab5740708
--- /dev/null
+++ b/content-gen/src/app/frontend/src/components/WelcomeCard.tsx
@@ -0,0 +1,158 @@
+import {
+ Card,
+ Text,
+ tokens,
+} from '@fluentui/react-components';
+import FirstPromptIcon from '../styles/images/firstprompt.png';
+import SecondPromptIcon from '../styles/images/secondprompt.png';
+
+interface SuggestionCard {
+ title: string;
+ prompt: string;
+ icon: string;
+}
+
+const suggestions: SuggestionCard[] = [
+ {
+ title: "I need to create a social media post about paint products for home remodels. The campaign is titled \"Brighten Your Springtime\" and the audience is new homeowners. I need marketing copy plus an image. The image should be an informal living room with tasteful furnishings.",
+ prompt: "I need to create a social media post about paint products for home remodels. The campaign is titled \"Brighten Your Springtime\" and the audience is new homeowners. I need marketing copy plus an image. The image should be an informal living room with tasteful furnishings.",
+ icon: FirstPromptIcon,
+ },
+ {
+ title: "Generate a social media campaign with ad copy and an image. This is for \"Back to School\" and the audience is parents of school age children. Tone is playful and humorous. The image must have minimal kids accessories in a children's bedroom. Show the room in a wide view.",
+ prompt: "Generate a social media campaign with ad copy and an image. This is for \"Back to School\" and the audience is parents of school age children. Tone is playful and humorous. The image must have minimal kids accessories in a children's bedroom. Show the room in a wide view.",
+ icon: SecondPromptIcon,
+ }
+];
+
+interface WelcomeCardProps {
+ onSuggestionClick: (prompt: string) => void;
+ currentInput?: string;
+}
+
+export function WelcomeCard({ onSuggestionClick, currentInput = '' }: WelcomeCardProps) {
+ const selectedIndex = suggestions.findIndex(s => s.prompt === currentInput);
+ return (
+
+ {/* Welcome card with suggestions inside */}
+
+ {/* Header with icon and welcome message */}
+
+
+ Welcome to your Content Generation Accelerator
+
+
+ Here are the options I can assist you with today
+
+