From 476d6aba932f02d3b2bc131d530cc364c500274a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Gr=C3=A4f?= Date: Mon, 26 Aug 2024 17:23:56 +1000 Subject: [PATCH 1/5] fix: Updated description for AIServices deployment (#3096) ## Description ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.cognitive-services.account](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.cognitive-services.account.yml/badge.svg?branch=segraef-patch-1)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.cognitive-services.account.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [ ] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/cognitive-services/account/README.md | 8 +------- .../tests/e2e/ai-model-deployment/main.test.bicep | 9 +-------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/avm/res/cognitive-services/account/README.md b/avm/res/cognitive-services/account/README.md index fc7d2e6409..6add4f02af 100644 --- a/avm/res/cognitive-services/account/README.md +++ b/avm/res/cognitive-services/account/README.md @@ -153,13 +153,7 @@ module account 'br/public:avm/res/cognitive-services/account:' = { ### Example 2: _Using `AIServices` with `deployments` in parameter set_ -This instance deploys the module with the AI model deployment feature.' - -Note, this test is temporarily disabled as it needs to be enabled on the subscription. -As we don't want other contributions from being blocked by this, we disabled the test for now / rely on a manual execution outside the CI environemnt -You can find more information here: https://learn.microsoft.com/en-us/legal/cognitive-services/openai/limited-access -And register here: https://aka.ms/oai/access - +This instance deploys the module with the AI model deployment feature.
diff --git a/avm/res/cognitive-services/account/tests/e2e/ai-model-deployment/main.test.bicep b/avm/res/cognitive-services/account/tests/e2e/ai-model-deployment/main.test.bicep index f07f8c89ae..50301f23e5 100644 --- a/avm/res/cognitive-services/account/tests/e2e/ai-model-deployment/main.test.bicep +++ b/avm/res/cognitive-services/account/tests/e2e/ai-model-deployment/main.test.bicep @@ -1,14 +1,7 @@ targetScope = 'subscription' metadata name = 'Using `AIServices` with `deployments` in parameter set' -metadata description = ''' -This instance deploys the module with the AI model deployment feature.' - -Note, this test is temporarily disabled as it needs to be enabled on the subscription. -As we don't want other contributions from being blocked by this, we disabled the test for now / rely on a manual execution outside the CI environemnt -You can find more information here: https://learn.microsoft.com/en-us/legal/cognitive-services/openai/limited-access -And register here: https://aka.ms/oai/access -''' +metadata description = 'This instance deploys the module with the AI model deployment feature.' // ========== // // Parameters // From 02168d0a37120004985a0458103e4a900a8d8b1e Mon Sep 17 00:00:00 2001 From: Buddy <38195643+tsc-buddy@users.noreply.github.com> Date: Mon, 26 Aug 2024 21:00:44 +1200 Subject: [PATCH 2/5] feat: postgres az updates (#3098) ## Description This PR includes minor updates to leverage AZ by default. ## Pipeline Reference | Pipeline | | -------- | [![avm.res.db-for-postgre-sql.flexible-server](https://github.com/tsc-buddy/bicep-registry-modules/actions/workflows/avm.res.db-for-postgre-sql.flexible-server.yml/badge.svg?branch=fix%2Fpostgres-az-updates&event=workflow_dispatch)](https://github.com/tsc-buddy/bicep-registry-modules/actions/workflows/avm.res.db-for-postgre-sql.flexible-server.yml) ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [x] The bug was found by the module author, and no one has opened an issue to report it yet. - [x] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [x] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Alexander Sehr --- avm/res/db-for-postgre-sql/flexible-server/README.md | 2 +- avm/res/db-for-postgre-sql/flexible-server/main.bicep | 2 +- avm/res/db-for-postgre-sql/flexible-server/main.json | 4 ++-- avm/res/db-for-postgre-sql/flexible-server/version.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/avm/res/db-for-postgre-sql/flexible-server/README.md b/avm/res/db-for-postgre-sql/flexible-server/README.md index 9bcd1bf2a3..236390be6e 100644 --- a/avm/res/db-for-postgre-sql/flexible-server/README.md +++ b/avm/res/db-for-postgre-sql/flexible-server/README.md @@ -1284,7 +1284,7 @@ The mode for high availability. - Required: No - Type: string -- Default: `'Disabled'` +- Default: `'ZoneRedundant'` - Allowed: ```Bicep [ diff --git a/avm/res/db-for-postgre-sql/flexible-server/main.bicep b/avm/res/db-for-postgre-sql/flexible-server/main.bicep index fc8b5d923c..24a84a31e8 100644 --- a/avm/res/db-for-postgre-sql/flexible-server/main.bicep +++ b/avm/res/db-for-postgre-sql/flexible-server/main.bicep @@ -100,7 +100,7 @@ param version string = '16' 'ZoneRedundant' ]) @description('Optional. The mode for high availability.') -param highAvailability string = 'Disabled' +param highAvailability string = 'ZoneRedundant' @allowed([ 'Create' diff --git a/avm/res/db-for-postgre-sql/flexible-server/main.json b/avm/res/db-for-postgre-sql/flexible-server/main.json index 3b358952cf..2c67a4f617 100644 --- a/avm/res/db-for-postgre-sql/flexible-server/main.json +++ b/avm/res/db-for-postgre-sql/flexible-server/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "6369286683210643119" + "templateHash": "4308585226053074434" }, "name": "DBforPostgreSQL Flexible Servers", "description": "This module deploys a DBforPostgreSQL Flexible Server.", @@ -428,7 +428,7 @@ }, "highAvailability": { "type": "string", - "defaultValue": "Disabled", + "defaultValue": "ZoneRedundant", "allowedValues": [ "Disabled", "SameZone", diff --git a/avm/res/db-for-postgre-sql/flexible-server/version.json b/avm/res/db-for-postgre-sql/flexible-server/version.json index 83083db694..1c035df49f 100644 --- a/avm/res/db-for-postgre-sql/flexible-server/version.json +++ b/avm/res/db-for-postgre-sql/flexible-server/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.1", + "version": "0.2", "pathFilters": [ "./main.json" ] From eefa332e070468813abec33dfa3263ead76f86c5 Mon Sep 17 00:00:00 2001 From: Nate Arnold Date: Mon, 26 Aug 2024 07:56:40 -0600 Subject: [PATCH 3/5] feat: `avm/res/db-for-postgre-sql/flexible-server` (#3094) ## Description Changed "highAvailability" parameter default value to "zoneredundant" to comply with WAF resiliency pillar. ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.db-for-postgre-sql.flexible-server](https://github.com/arnoldna/bicep-registry-modules/actions/workflows/avm.res.db-for-postgre-sql.flexible-server.yml/badge.svg?branch=avm%2Fres%2Fdb-for-postgre-sql%2Fflexible-server)](https://github.com/arnoldna/bicep-registry-modules/actions/workflows/avm.res.db-for-postgre-sql.flexible-server.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [X] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [X] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [X] Update to documentation ## Checklist - [X] I'm sure there are no other open Pull Requests for the same update/change - [X] I have run `Set-AVMModule` locally to generate the supporting module files. - [X] My corresponding pipelines / checks run clean and green without any errors or warnings From bb574a7cf62c995a78501e1b35acd56144051f14 Mon Sep 17 00:00:00 2001 From: anotherRedbeard <34103220+anotherRedbeard@users.noreply.github.com> Date: Mon, 26 Aug 2024 11:07:19 -0500 Subject: [PATCH 4/5] feat: avm/res/apim-management/service/identity-provider Add contentLibrary property to the Identity Provider apim module (#3067) ## Description This pull request includes several updates to the API management service configuration files, focusing on adding a new `clientLibrary` parameter and simplifying the codebase by using optional chaining and nullish coalescing operators. ### Additions: * Added `clientLibrary` parameter to the identity provider configuration in `README.md`, `main.bicep`, and `main.json` files. This parameter specifies the client library to be used in the developer portal and is applicable to AAD and AAD B2C Identity Providers. [[1]](diffhunk://#diff-9ddb8330ed79263d6e746d65e3b3257b4505f97927a1629181a76f1c5b6a6613R340) [[2]](diffhunk://#diff-9ddb8330ed79263d6e746d65e3b3257b4505f97927a1629181a76f1c5b6a6613R583) [[3]](diffhunk://#diff-9ddb8330ed79263d6e746d65e3b3257b4505f97927a1629181a76f1c5b6a6613R898) [[4]](diffhunk://#diff-9ddb8330ed79263d6e746d65e3b3257b4505f97927a1629181a76f1c5b6a6613R1119) [[5]](diffhunk://#diff-43eea61a91298ae7bef3d481f3173899f538a8965ed5dd2f3dcc969831f9ac1fR41) [[6]](diffhunk://#diff-43eea61a91298ae7bef3d481f3173899f538a8965ed5dd2f3dcc969831f9ac1fR95-R110) [[7]](diffhunk://#diff-a4a02b7176d3a8c138078d850f9eb87c6ff174240e076fad16f2828e81d95984R17-R24) [[8]](diffhunk://#diff-a4a02b7176d3a8c138078d850f9eb87c6ff174240e076fad16f2828e81d95984R78) [[9]](diffhunk://#diff-16e9f7f16068d75fc42585c3dff1869a905e7ae9623294ef13e0012ff8e8cfe4R42-R53) [[10]](diffhunk://#diff-16e9f7f16068d75fc42585c3dff1869a905e7ae9623294ef13e0012ff8e8cfe4R136) ### Code Simplification: * Replaced `contains` checks with optional chaining and nullish coalescing operators in `main.bicep` and `main.json` files to simplify the code and improve readability. [[1]](diffhunk://#diff-8f2edf3d1f48cf6c680a1183f0e64d26c87c0ceb60ca907ed141beb343d1c762L145-R145) [[2]](diffhunk://#diff-2ca925a05c195f083f978183605a2057d590ad2b8bfe530789ee04df61e7af40L273-R275) [[3]](diffhunk://#diff-0f11bdd53cbb4764551480ccb25116f6805372f2ad26f15c7d01f0eb8624fabaL302-R302) [[4]](diffhunk://#diff-0f11bdd53cbb4764551480ccb25116f6805372f2ad26f15c7d01f0eb8624fabaL314-R327) [[5]](diffhunk://#diff-0f11bdd53cbb4764551480ccb25116f6805372f2ad26f15c7d01f0eb8624fabaL416-R406) [[6]](diffhunk://#diff-0f11bdd53cbb4764551480ccb25116f6805372f2ad26f15c7d01f0eb8624fabaL440-R421) [[7]](diffhunk://#diff-0f11bdd53cbb4764551480ccb25116f6805372f2ad26f15c7d01f0eb8624fabaL458-R439) [[8]](diffhunk://#diff-0f11bdd53cbb4764551480ccb25116f6805372f2ad26f15c7d01f0eb8624fabaL484-R461) [[9]](diffhunk://#diff-0f11bdd53cbb4764551480ccb25116f6805372f2ad26f15c7d01f0eb8624fabaL494-R479) [[10]](diffhunk://#diff-af393a2ffcf4cca471b3ed98bb92055747cf64a3a95f05fd5c7ea5597468a8d8L1059-R1061) [[11]](diffhunk://#diff-af393a2ffcf4cca471b3ed98bb92055747cf64a3a95f05fd5c7ea5597468a8d8L1435-R1439) ### Metadata Updates: * Updated `_generator` metadata in various `main.json` files to reflect the new template hash values. [[1]](diffhunk://#diff-2ca925a05c195f083f978183605a2057d590ad2b8bfe530789ee04df61e7af40L9-R9) [[2]](diffhunk://#diff-16e9f7f16068d75fc42585c3dff1869a905e7ae9623294ef13e0012ff8e8cfe4L8-R8) [[3]](diffhunk://#diff-af393a2ffcf4cca471b3ed98bb92055747cf64a3a95f05fd5c7ea5597468a8d8L9-R9) [[4]](diffhunk://#diff-af393a2ffcf4cca471b3ed98bb92055747cf64a3a95f05fd5c7ea5597468a8d8L795-R795) These changes aim to enhance the maintainability and functionality of the API management service configuration. ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.api-management.service](https://github.com/anotherRedbeard/bicep-registry-modules/actions/workflows/avm.res.api-management.service.yml/badge.svg)](https://github.com/anotherRedbeard/bicep-registry-modules/actions/workflows/avm.res.api-management.service.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [ x] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [x ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [x ] Update to documentation ## Checklist - [x ] I'm sure there are no other open Pull Requests for the same update/change - [ x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x ] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: anotherRedbeard Co-authored-by: anotherRedbeard Co-authored-by: Tony Box --- avm/res/api-management/service/README.md | 4 + avm/res/api-management/service/api/main.bicep | 2 +- avm/res/api-management/service/api/main.json | 6 +- .../service/identity-provider/README.md | 15 ++ .../service/identity-provider/main.bicep | 8 + .../service/identity-provider/main.json | 32 ++- avm/res/api-management/service/main.bicep | 103 ++++----- avm/res/api-management/service/main.json | 199 +++++++++++++----- .../service/tests/e2e/max/main.test.bicep | 1 + .../tests/e2e/waf-aligned/main.test.bicep | 1 + avm/res/api-management/service/version.json | 2 +- 11 files changed, 254 insertions(+), 119 deletions(-) diff --git a/avm/res/api-management/service/README.md b/avm/res/api-management/service/README.md index c2a78b25c3..1a74dba710 100644 --- a/avm/res/api-management/service/README.md +++ b/avm/res/api-management/service/README.md @@ -337,6 +337,7 @@ module service 'br/public:avm/res/api-management/service:' = { ] authority: '' clientId: 'apimClientid' + clientLibrary: 'MSAL-2' clientSecret: 'apimSlientSecret' name: 'aad' signinTenant: 'mytenant.onmicrosoft.com' @@ -579,6 +580,7 @@ module service 'br/public:avm/res/api-management/service:' = { ], "authority": "", "clientId": "apimClientid", + "clientLibrary": "MSAL-2", "clientSecret": "apimSlientSecret", "name": "aad", "signinTenant": "mytenant.onmicrosoft.com" @@ -893,6 +895,7 @@ module service 'br/public:avm/res/api-management/service:' = { ] authority: '' clientId: 'apimClientid' + clientLibrary: 'MSAL-2' clientSecret: '' name: 'aad' signinTenant: 'mytenant.onmicrosoft.com' @@ -1113,6 +1116,7 @@ module service 'br/public:avm/res/api-management/service:' = { ], "authority": "", "clientId": "apimClientid", + "clientLibrary": "MSAL-2", "clientSecret": "", "name": "aad", "signinTenant": "mytenant.onmicrosoft.com" diff --git a/avm/res/api-management/service/api/main.bicep b/avm/res/api-management/service/api/main.bicep index f3cf6a2b3c..6b83aae577 100644 --- a/avm/res/api-management/service/api/main.bicep +++ b/avm/res/api-management/service/api/main.bicep @@ -142,7 +142,7 @@ module policy 'policy/main.bicep' = [ params: { apiManagementServiceName: apiManagementServiceName apiName: api.name - format: contains(policy, 'format') ? policy.format : 'xml' + format: policy.?format ?? 'xml' value: policy.value } } diff --git a/avm/res/api-management/service/api/main.json b/avm/res/api-management/service/api/main.json index e94c21cc8f..970b83350d 100644 --- a/avm/res/api-management/service/api/main.json +++ b/avm/res/api-management/service/api/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "13121653397859804060" + "templateHash": "17160750790361326516" }, "name": "API Management Service APIs", "description": "This module deploys an API Management Service API.", @@ -270,7 +270,9 @@ "apiName": { "value": "[parameters('name')]" }, - "format": "[if(contains(coalesce(parameters('policies'), createArray())[copyIndex()], 'format'), createObject('value', coalesce(parameters('policies'), createArray())[copyIndex()].format), createObject('value', 'xml'))]", + "format": { + "value": "[coalesce(tryGet(coalesce(parameters('policies'), createArray())[copyIndex()], 'format'), 'xml')]" + }, "value": { "value": "[coalesce(parameters('policies'), createArray())[copyIndex()].value]" } diff --git a/avm/res/api-management/service/identity-provider/README.md b/avm/res/api-management/service/identity-provider/README.md index efbbbae18b..eb33eef785 100644 --- a/avm/res/api-management/service/identity-provider/README.md +++ b/avm/res/api-management/service/identity-provider/README.md @@ -38,6 +38,7 @@ This module deploys an API Management Service Identity Provider. | :-- | :-- | :-- | | [`allowedTenants`](#parameter-allowedtenants) | array | List of Allowed Tenants when configuring Azure Active Directory login. - string. | | [`authority`](#parameter-authority) | string | OpenID Connect discovery endpoint hostname for AAD or AAD B2C. | +| [`clientLibrary`](#parameter-clientlibrary) | string | The client library to be used in the developer portal. Only applies to AAD and AAD B2C Identity Provider. | | [`passwordResetPolicyName`](#parameter-passwordresetpolicyname) | string | Password Reset Policy Name. Only applies to AAD B2C Identity Provider. | | [`profileEditingPolicyName`](#parameter-profileeditingpolicyname) | string | Profile Editing Policy Name. Only applies to AAD B2C Identity Provider. | | [`signInPolicyName`](#parameter-signinpolicyname) | string | Signin Policy Name. Only applies to AAD B2C Identity Provider. | @@ -91,6 +92,20 @@ OpenID Connect discovery endpoint hostname for AAD or AAD B2C. - Type: string - Default: `''` +### Parameter: `clientLibrary` + +The client library to be used in the developer portal. Only applies to AAD and AAD B2C Identity Provider. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'ADAL' + 'MSAL-2' + ] + ``` + ### Parameter: `passwordResetPolicyName` Password Reset Policy Name. Only applies to AAD B2C Identity Provider. diff --git a/avm/res/api-management/service/identity-provider/main.bicep b/avm/res/api-management/service/identity-provider/main.bicep index 0c6ec2ebcb..8306fe54c9 100644 --- a/avm/res/api-management/service/identity-provider/main.bicep +++ b/avm/res/api-management/service/identity-provider/main.bicep @@ -14,6 +14,13 @@ param authority string = '' @description('Conditional. Client ID of the Application in the external Identity Provider. Required if identity provider is used.') param clientId string = '' +@description('Optional. The client library to be used in the developer portal. Only applies to AAD and AAD B2C Identity Provider.') +@allowed([ + 'ADAL' + 'MSAL-2' +]) +param clientLibrary string? + @description('Conditional. Client secret of the Application in external Identity Provider, used to authenticate login request. Required if identity provider is used.') @secure() param clientSecret string = '' @@ -67,6 +74,7 @@ resource identityProvider 'Microsoft.ApiManagement/service/identityProviders@202 profileEditingPolicyName: isAadB2C ? profileEditingPolicyName : null passwordResetPolicyName: isAadB2C ? passwordResetPolicyName : null clientId: clientId + clientLibrary: clientLibrary clientSecret: clientSecret } } diff --git a/avm/res/api-management/service/identity-provider/main.json b/avm/res/api-management/service/identity-provider/main.json index a6563d4a31..f9e6cbe086 100644 --- a/avm/res/api-management/service/identity-provider/main.json +++ b/avm/res/api-management/service/identity-provider/main.json @@ -1,11 +1,12 @@ { "$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.29.47.4906", - "templateHash": "13129392765749462635" + "templateHash": "12757169124799431378" }, "name": "API Management Service Identity Providers", "description": "This module deploys an API Management Service Identity Provider.", @@ -39,6 +40,17 @@ "description": "Conditional. Client ID of the Application in the external Identity Provider. Required if identity provider is used." } }, + "clientLibrary": { + "type": "string", + "nullable": true, + "allowedValues": [ + "ADAL", + "MSAL-2" + ], + "metadata": { + "description": "Optional. The client library to be used in the developer portal. Only applies to AAD and AAD B2C Identity Provider." + } + }, "clientSecret": { "type": "securestring", "defaultValue": "", @@ -106,8 +118,14 @@ "variables": { "isAadB2C": "[equals(parameters('type'), 'aadB2C')]" }, - "resources": [ - { + "resources": { + "service": { + "existing": true, + "type": "Microsoft.ApiManagement/service", + "apiVersion": "2023-05-01-preview", + "name": "[parameters('apiManagementServiceName')]" + }, + "identityProvider": { "type": "Microsoft.ApiManagement/service/identityProviders", "apiVersion": "2022-08-01", "name": "[format('{0}/{1}', parameters('apiManagementServiceName'), parameters('name'))]", @@ -121,10 +139,14 @@ "profileEditingPolicyName": "[if(variables('isAadB2C'), parameters('profileEditingPolicyName'), null())]", "passwordResetPolicyName": "[if(variables('isAadB2C'), parameters('passwordResetPolicyName'), null())]", "clientId": "[parameters('clientId')]", + "clientLibrary": "[parameters('clientLibrary')]", "clientSecret": "[parameters('clientSecret')]" - } + }, + "dependsOn": [ + "service" + ] } - ], + }, "outputs": { "resourceId": { "type": "string", diff --git a/avm/res/api-management/service/main.bicep b/avm/res/api-management/service/main.bicep index f78d4be2a0..2ee1c95c75 100644 --- a/avm/res/api-management/service/main.bicep +++ b/avm/res/api-management/service/main.bicep @@ -299,7 +299,7 @@ module service_apiVersionSets 'api-version-set/main.bicep' = [ params: { apiManagementServiceName: service.name name: apiVersionSet.name - properties: contains(apiVersionSet, 'properties') ? apiVersionSet.properties : {} + properties: apiVersionSet.?properties ?? {} } } ] @@ -311,40 +311,20 @@ module service_authorizationServers 'authorization-server/main.bicep' = [ apiManagementServiceName: service.name name: authorizationServer.name authorizationEndpoint: authorizationServer.authorizationEndpoint - authorizationMethods: contains(authorizationServer, 'authorizationMethods') - ? authorizationServer.authorizationMethods - : [ - 'GET' - ] - bearerTokenSendingMethods: contains(authorizationServer, 'bearerTokenSendingMethods') - ? authorizationServer.bearerTokenSendingMethods - : [ - 'authorizationHeader' - ] - clientAuthenticationMethod: contains(authorizationServer, 'clientAuthenticationMethod') - ? authorizationServer.clientAuthenticationMethod - : [ - 'Basic' - ] + authorizationMethods: authorizationServer.?authorizationMethods ?? ['GET'] + bearerTokenSendingMethods: authorizationServer.?bearerTokenSendingMethods ?? ['authorizationHeader'] + clientAuthenticationMethod: authorizationServer.?clientAuthenticationMethod ?? ['Basic'] clientId: authorizationServer.clientId clientSecret: authorizationServer.clientSecret - clientRegistrationEndpoint: contains(authorizationServer, 'clientRegistrationEndpoint') - ? authorizationServer.clientRegistrationEndpoint - : '' - defaultScope: contains(authorizationServer, 'defaultScope') ? authorizationServer.defaultScope : '' + clientRegistrationEndpoint: authorizationServer.?clientRegistrationEndpoint ?? '' + defaultScope: authorizationServer.?defaultScope ?? '' grantTypes: authorizationServer.grantTypes - resourceOwnerPassword: contains(authorizationServer, 'resourceOwnerPassword') - ? authorizationServer.resourceOwnerPassword - : '' - resourceOwnerUsername: contains(authorizationServer, 'resourceOwnerUsername') - ? authorizationServer.resourceOwnerUsername - : '' - serverDescription: contains(authorizationServer, 'serverDescription') ? authorizationServer.serverDescription : '' - supportState: contains(authorizationServer, 'supportState') ? authorizationServer.supportState : false - tokenBodyParameters: contains(authorizationServer, 'tokenBodyParameters') - ? authorizationServer.tokenBodyParameters - : [] - tokenEndpoint: contains(authorizationServer, 'tokenEndpoint') ? authorizationServer.tokenEndpoint : '' + resourceOwnerPassword: authorizationServer.?resourceOwnerPassword ?? '' + resourceOwnerUsername: authorizationServer.?resourceOwnerUsername ?? '' + serverDescription: authorizationServer.?serverDescription ?? '' + supportState: authorizationServer.?supportState ?? false + tokenBodyParameters: authorizationServer.?tokenBodyParameters ?? [] + tokenEndpoint: authorizationServer.?tokenEndpoint ?? '' } } ] @@ -413,20 +393,17 @@ module service_identityProviders 'identity-provider/main.bicep' = [ params: { apiManagementServiceName: service.name name: identityProvider.name - allowedTenants: contains(identityProvider, 'allowedTenants') ? identityProvider.allowedTenants : [] - authority: contains(identityProvider, 'authority') ? identityProvider.authority : '' - clientId: contains(identityProvider, 'clientId') ? identityProvider.clientId : '' - clientSecret: contains(identityProvider, 'clientSecret') ? identityProvider.clientSecret : '' - passwordResetPolicyName: contains(identityProvider, 'passwordResetPolicyName') - ? identityProvider.passwordResetPolicyName - : '' - profileEditingPolicyName: contains(identityProvider, 'profileEditingPolicyName') - ? identityProvider.profileEditingPolicyName - : '' - signInPolicyName: contains(identityProvider, 'signInPolicyName') ? identityProvider.signInPolicyName : '' - signInTenant: contains(identityProvider, 'signInTenant') ? identityProvider.signInTenant : '' - signUpPolicyName: contains(identityProvider, 'signUpPolicyName') ? identityProvider.signUpPolicyName : '' - type: contains(identityProvider, 'type') ? identityProvider.type : 'aad' + allowedTenants: identityProvider.?allowedTenants ?? [] + authority: identityProvider.?authority ?? '' + clientId: identityProvider.?clientId ?? '' + clientLibrary: identityProvider.?clientLibrary ?? '' + clientSecret: identityProvider.?clientSecret ?? '' + passwordResetPolicyName: identityProvider.?passwordResetPolicyName ?? '' + profileEditingPolicyName: identityProvider.?profileEditingPolicyName ?? '' + signInPolicyName: identityProvider.?signInPolicyName ?? '' + signInTenant: identityProvider.?signInTenant ?? '' + signUpPolicyName: identityProvider.?signUpPolicyName ?? '' + type: identityProvider.?type ?? 'aad' } } ] @@ -437,11 +414,11 @@ module service_loggers 'loggers/main.bicep' = [ params: { name: logger.name apiManagementServiceName: service.name - credentials: contains(logger, 'credentials') ? logger.credentials : {} - isBuffered: contains(logger, 'isBuffered') ? logger.isBuffered : true - loggerDescription: contains(logger, 'loggerDescription') ? logger.loggerDescription : '' - loggerType: contains(logger, 'loggerType') ? logger.loggerType : 'azureMonitor' - targetResourceId: contains(logger, 'targetResourceId') ? logger.targetResourceId : '' + credentials: logger.?credentials ?? {} + isBuffered: logger.?isBuffered ?? true + loggerDescription: logger.?loggerDescription ?? '' + loggerType: logger.?loggerType ?? 'azureMonitor' + targetResourceId: logger.?targetResourceId ?? '' } dependsOn: [ service_namedValues @@ -455,11 +432,11 @@ module service_namedValues 'named-value/main.bicep' = [ params: { apiManagementServiceName: service.name displayName: namedValue.displayName - keyVault: contains(namedValue, 'keyVault') ? namedValue.keyVault : {} + keyVault: namedValue.?keyVault ?? {} name: namedValue.name tags: namedValue.?tags // Note: these are not resource tags - secret: contains(namedValue, 'secret') ? namedValue.secret : false - value: contains(namedValue, 'value') ? namedValue.value : newGuidValue + secret: namedValue.?secret ?? false + value: namedValue.?value ?? newGuidValue } } ] @@ -481,7 +458,7 @@ module service_policies 'policy/main.bicep' = [ params: { apiManagementServiceName: service.name value: policy.value - format: contains(policy, 'format') ? policy.format : 'xml' + format: policy.?format ?? 'xml' } } ] @@ -491,15 +468,15 @@ module service_products 'product/main.bicep' = [ name: '${uniqueString(deployment().name, location)}-Apim-Product-${index}' params: { apiManagementServiceName: service.name - apis: contains(product, 'apis') ? product.apis : [] - approvalRequired: contains(product, 'approvalRequired') ? product.approvalRequired : false - groups: contains(product, 'groups') ? product.groups : [] + apis: product.?apis ?? [] + approvalRequired: product.?approvalRequired ?? false + groups: product.?groups ?? [] name: product.name - description: contains(product, 'description') ? product.description : '' - state: contains(product, 'state') ? product.state : 'published' - subscriptionRequired: contains(product, 'subscriptionRequired') ? product.subscriptionRequired : false - subscriptionsLimit: contains(product, 'subscriptionsLimit') ? product.subscriptionsLimit : 1 - terms: contains(product, 'terms') ? product.terms : '' + description: product.?description ?? '' + state: product.?state ?? 'published' + subscriptionRequired: product.?subscriptionRequired ?? false + subscriptionsLimit: product.?subscriptionsLimit ?? 1 + terms: product.?terms ?? '' } dependsOn: [ service_apis diff --git a/avm/res/api-management/service/main.json b/avm/res/api-management/service/main.json index bf4c30828b..fdb9002616 100644 --- a/avm/res/api-management/service/main.json +++ b/avm/res/api-management/service/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "17512486952547559585" + "templateHash": "17385483787660298242" }, "name": "API Management Services", "description": "This module deploys an API Management Service. The default deployment is set to use a Premium SKU to align with Microsoft WAF-aligned best practices. In most cases, non-prod deployments should use a lower-tier SKU.", @@ -792,7 +792,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "13121653397859804060" + "templateHash": "17160750790361326516" }, "name": "API Management Service APIs", "description": "This module deploys an API Management Service API.", @@ -1056,7 +1056,9 @@ "apiName": { "value": "[parameters('name')]" }, - "format": "[if(contains(coalesce(parameters('policies'), createArray())[copyIndex()], 'format'), createObject('value', coalesce(parameters('policies'), createArray())[copyIndex()].format), createObject('value', 'xml'))]", + "format": { + "value": "[coalesce(tryGet(coalesce(parameters('policies'), createArray())[copyIndex()], 'format'), 'xml')]" + }, "value": { "value": "[coalesce(parameters('policies'), createArray())[copyIndex()].value]" } @@ -1432,7 +1434,9 @@ "name": { "value": "[parameters('apiVersionSets')[copyIndex()].name]" }, - "properties": "[if(contains(parameters('apiVersionSets')[copyIndex()], 'properties'), createObject('value', parameters('apiVersionSets')[copyIndex()].properties), createObject('value', createObject()))]" + "properties": { + "value": "[coalesce(tryGet(parameters('apiVersionSets')[copyIndex()], 'properties'), createObject())]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -1529,26 +1533,48 @@ "authorizationEndpoint": { "value": "[variables('authorizationServerList')[copyIndex()].authorizationEndpoint]" }, - "authorizationMethods": "[if(contains(variables('authorizationServerList')[copyIndex()], 'authorizationMethods'), createObject('value', variables('authorizationServerList')[copyIndex()].authorizationMethods), createObject('value', createArray('GET')))]", - "bearerTokenSendingMethods": "[if(contains(variables('authorizationServerList')[copyIndex()], 'bearerTokenSendingMethods'), createObject('value', variables('authorizationServerList')[copyIndex()].bearerTokenSendingMethods), createObject('value', createArray('authorizationHeader')))]", - "clientAuthenticationMethod": "[if(contains(variables('authorizationServerList')[copyIndex()], 'clientAuthenticationMethod'), createObject('value', variables('authorizationServerList')[copyIndex()].clientAuthenticationMethod), createObject('value', createArray('Basic')))]", + "authorizationMethods": { + "value": "[coalesce(tryGet(variables('authorizationServerList')[copyIndex()], 'authorizationMethods'), createArray('GET'))]" + }, + "bearerTokenSendingMethods": { + "value": "[coalesce(tryGet(variables('authorizationServerList')[copyIndex()], 'bearerTokenSendingMethods'), createArray('authorizationHeader'))]" + }, + "clientAuthenticationMethod": { + "value": "[coalesce(tryGet(variables('authorizationServerList')[copyIndex()], 'clientAuthenticationMethod'), createArray('Basic'))]" + }, "clientId": { "value": "[variables('authorizationServerList')[copyIndex()].clientId]" }, "clientSecret": { "value": "[variables('authorizationServerList')[copyIndex()].clientSecret]" }, - "clientRegistrationEndpoint": "[if(contains(variables('authorizationServerList')[copyIndex()], 'clientRegistrationEndpoint'), createObject('value', variables('authorizationServerList')[copyIndex()].clientRegistrationEndpoint), createObject('value', ''))]", - "defaultScope": "[if(contains(variables('authorizationServerList')[copyIndex()], 'defaultScope'), createObject('value', variables('authorizationServerList')[copyIndex()].defaultScope), createObject('value', ''))]", + "clientRegistrationEndpoint": { + "value": "[coalesce(tryGet(variables('authorizationServerList')[copyIndex()], 'clientRegistrationEndpoint'), '')]" + }, + "defaultScope": { + "value": "[coalesce(tryGet(variables('authorizationServerList')[copyIndex()], 'defaultScope'), '')]" + }, "grantTypes": { "value": "[variables('authorizationServerList')[copyIndex()].grantTypes]" }, - "resourceOwnerPassword": "[if(contains(variables('authorizationServerList')[copyIndex()], 'resourceOwnerPassword'), createObject('value', variables('authorizationServerList')[copyIndex()].resourceOwnerPassword), createObject('value', ''))]", - "resourceOwnerUsername": "[if(contains(variables('authorizationServerList')[copyIndex()], 'resourceOwnerUsername'), createObject('value', variables('authorizationServerList')[copyIndex()].resourceOwnerUsername), createObject('value', ''))]", - "serverDescription": "[if(contains(variables('authorizationServerList')[copyIndex()], 'serverDescription'), createObject('value', variables('authorizationServerList')[copyIndex()].serverDescription), createObject('value', ''))]", - "supportState": "[if(contains(variables('authorizationServerList')[copyIndex()], 'supportState'), createObject('value', variables('authorizationServerList')[copyIndex()].supportState), createObject('value', false()))]", - "tokenBodyParameters": "[if(contains(variables('authorizationServerList')[copyIndex()], 'tokenBodyParameters'), createObject('value', variables('authorizationServerList')[copyIndex()].tokenBodyParameters), createObject('value', createArray()))]", - "tokenEndpoint": "[if(contains(variables('authorizationServerList')[copyIndex()], 'tokenEndpoint'), createObject('value', variables('authorizationServerList')[copyIndex()].tokenEndpoint), createObject('value', ''))]" + "resourceOwnerPassword": { + "value": "[coalesce(tryGet(variables('authorizationServerList')[copyIndex()], 'resourceOwnerPassword'), '')]" + }, + "resourceOwnerUsername": { + "value": "[coalesce(tryGet(variables('authorizationServerList')[copyIndex()], 'resourceOwnerUsername'), '')]" + }, + "serverDescription": { + "value": "[coalesce(tryGet(variables('authorizationServerList')[copyIndex()], 'serverDescription'), '')]" + }, + "supportState": { + "value": "[coalesce(tryGet(variables('authorizationServerList')[copyIndex()], 'supportState'), false())]" + }, + "tokenBodyParameters": { + "value": "[coalesce(tryGet(variables('authorizationServerList')[copyIndex()], 'tokenBodyParameters'), createArray())]" + }, + "tokenEndpoint": { + "value": "[coalesce(tryGet(variables('authorizationServerList')[copyIndex()], 'tokenEndpoint'), '')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -2330,25 +2356,49 @@ "name": { "value": "[parameters('identityProviders')[copyIndex()].name]" }, - "allowedTenants": "[if(contains(parameters('identityProviders')[copyIndex()], 'allowedTenants'), createObject('value', parameters('identityProviders')[copyIndex()].allowedTenants), createObject('value', createArray()))]", - "authority": "[if(contains(parameters('identityProviders')[copyIndex()], 'authority'), createObject('value', parameters('identityProviders')[copyIndex()].authority), createObject('value', ''))]", - "clientId": "[if(contains(parameters('identityProviders')[copyIndex()], 'clientId'), createObject('value', parameters('identityProviders')[copyIndex()].clientId), createObject('value', ''))]", - "clientSecret": "[if(contains(parameters('identityProviders')[copyIndex()], 'clientSecret'), createObject('value', parameters('identityProviders')[copyIndex()].clientSecret), createObject('value', ''))]", - "passwordResetPolicyName": "[if(contains(parameters('identityProviders')[copyIndex()], 'passwordResetPolicyName'), createObject('value', parameters('identityProviders')[copyIndex()].passwordResetPolicyName), createObject('value', ''))]", - "profileEditingPolicyName": "[if(contains(parameters('identityProviders')[copyIndex()], 'profileEditingPolicyName'), createObject('value', parameters('identityProviders')[copyIndex()].profileEditingPolicyName), createObject('value', ''))]", - "signInPolicyName": "[if(contains(parameters('identityProviders')[copyIndex()], 'signInPolicyName'), createObject('value', parameters('identityProviders')[copyIndex()].signInPolicyName), createObject('value', ''))]", - "signInTenant": "[if(contains(parameters('identityProviders')[copyIndex()], 'signInTenant'), createObject('value', parameters('identityProviders')[copyIndex()].signInTenant), createObject('value', ''))]", - "signUpPolicyName": "[if(contains(parameters('identityProviders')[copyIndex()], 'signUpPolicyName'), createObject('value', parameters('identityProviders')[copyIndex()].signUpPolicyName), createObject('value', ''))]", - "type": "[if(contains(parameters('identityProviders')[copyIndex()], 'type'), createObject('value', parameters('identityProviders')[copyIndex()].type), createObject('value', 'aad'))]" + "allowedTenants": { + "value": "[coalesce(tryGet(parameters('identityProviders')[copyIndex()], 'allowedTenants'), createArray())]" + }, + "authority": { + "value": "[coalesce(tryGet(parameters('identityProviders')[copyIndex()], 'authority'), '')]" + }, + "clientId": { + "value": "[coalesce(tryGet(parameters('identityProviders')[copyIndex()], 'clientId'), '')]" + }, + "clientLibrary": { + "value": "[coalesce(tryGet(parameters('identityProviders')[copyIndex()], 'clientLibrary'), '')]" + }, + "clientSecret": { + "value": "[coalesce(tryGet(parameters('identityProviders')[copyIndex()], 'clientSecret'), '')]" + }, + "passwordResetPolicyName": { + "value": "[coalesce(tryGet(parameters('identityProviders')[copyIndex()], 'passwordResetPolicyName'), '')]" + }, + "profileEditingPolicyName": { + "value": "[coalesce(tryGet(parameters('identityProviders')[copyIndex()], 'profileEditingPolicyName'), '')]" + }, + "signInPolicyName": { + "value": "[coalesce(tryGet(parameters('identityProviders')[copyIndex()], 'signInPolicyName'), '')]" + }, + "signInTenant": { + "value": "[coalesce(tryGet(parameters('identityProviders')[copyIndex()], 'signInTenant'), '')]" + }, + "signUpPolicyName": { + "value": "[coalesce(tryGet(parameters('identityProviders')[copyIndex()], 'signUpPolicyName'), '')]" + }, + "type": { + "value": "[coalesce(tryGet(parameters('identityProviders')[copyIndex()], 'type'), 'aad')]" + } }, "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.29.47.4906", - "templateHash": "13129392765749462635" + "templateHash": "12757169124799431378" }, "name": "API Management Service Identity Providers", "description": "This module deploys an API Management Service Identity Provider.", @@ -2382,6 +2432,17 @@ "description": "Conditional. Client ID of the Application in the external Identity Provider. Required if identity provider is used." } }, + "clientLibrary": { + "type": "string", + "nullable": true, + "allowedValues": [ + "ADAL", + "MSAL-2" + ], + "metadata": { + "description": "Optional. The client library to be used in the developer portal. Only applies to AAD and AAD B2C Identity Provider." + } + }, "clientSecret": { "type": "securestring", "defaultValue": "", @@ -2449,8 +2510,14 @@ "variables": { "isAadB2C": "[equals(parameters('type'), 'aadB2C')]" }, - "resources": [ - { + "resources": { + "service": { + "existing": true, + "type": "Microsoft.ApiManagement/service", + "apiVersion": "2023-05-01-preview", + "name": "[parameters('apiManagementServiceName')]" + }, + "identityProvider": { "type": "Microsoft.ApiManagement/service/identityProviders", "apiVersion": "2022-08-01", "name": "[format('{0}/{1}', parameters('apiManagementServiceName'), parameters('name'))]", @@ -2464,10 +2531,14 @@ "profileEditingPolicyName": "[if(variables('isAadB2C'), parameters('profileEditingPolicyName'), null())]", "passwordResetPolicyName": "[if(variables('isAadB2C'), parameters('passwordResetPolicyName'), null())]", "clientId": "[parameters('clientId')]", + "clientLibrary": "[parameters('clientLibrary')]", "clientSecret": "[parameters('clientSecret')]" - } + }, + "dependsOn": [ + "service" + ] } - ], + }, "outputs": { "resourceId": { "type": "string", @@ -2517,11 +2588,21 @@ "apiManagementServiceName": { "value": "[parameters('name')]" }, - "credentials": "[if(contains(parameters('loggers')[copyIndex()], 'credentials'), createObject('value', parameters('loggers')[copyIndex()].credentials), createObject('value', createObject()))]", - "isBuffered": "[if(contains(parameters('loggers')[copyIndex()], 'isBuffered'), createObject('value', parameters('loggers')[copyIndex()].isBuffered), createObject('value', true()))]", - "loggerDescription": "[if(contains(parameters('loggers')[copyIndex()], 'loggerDescription'), createObject('value', parameters('loggers')[copyIndex()].loggerDescription), createObject('value', ''))]", - "loggerType": "[if(contains(parameters('loggers')[copyIndex()], 'loggerType'), createObject('value', parameters('loggers')[copyIndex()].loggerType), createObject('value', 'azureMonitor'))]", - "targetResourceId": "[if(contains(parameters('loggers')[copyIndex()], 'targetResourceId'), createObject('value', parameters('loggers')[copyIndex()].targetResourceId), createObject('value', ''))]" + "credentials": { + "value": "[coalesce(tryGet(parameters('loggers')[copyIndex()], 'credentials'), createObject())]" + }, + "isBuffered": { + "value": "[coalesce(tryGet(parameters('loggers')[copyIndex()], 'isBuffered'), true())]" + }, + "loggerDescription": { + "value": "[coalesce(tryGet(parameters('loggers')[copyIndex()], 'loggerDescription'), '')]" + }, + "loggerType": { + "value": "[coalesce(tryGet(parameters('loggers')[copyIndex()], 'loggerType'), 'azureMonitor')]" + }, + "targetResourceId": { + "value": "[coalesce(tryGet(parameters('loggers')[copyIndex()], 'targetResourceId'), '')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -2650,15 +2731,21 @@ "displayName": { "value": "[parameters('namedValues')[copyIndex()].displayName]" }, - "keyVault": "[if(contains(parameters('namedValues')[copyIndex()], 'keyVault'), createObject('value', parameters('namedValues')[copyIndex()].keyVault), createObject('value', createObject()))]", + "keyVault": { + "value": "[coalesce(tryGet(parameters('namedValues')[copyIndex()], 'keyVault'), createObject())]" + }, "name": { "value": "[parameters('namedValues')[copyIndex()].name]" }, "tags": { "value": "[tryGet(parameters('namedValues')[copyIndex()], 'tags')]" }, - "secret": "[if(contains(parameters('namedValues')[copyIndex()], 'secret'), createObject('value', parameters('namedValues')[copyIndex()].secret), createObject('value', false()))]", - "value": "[if(contains(parameters('namedValues')[copyIndex()], 'value'), createObject('value', parameters('namedValues')[copyIndex()].value), createObject('value', parameters('newGuidValue')))]" + "secret": { + "value": "[coalesce(tryGet(parameters('namedValues')[copyIndex()], 'secret'), false())]" + }, + "value": { + "value": "[coalesce(tryGet(parameters('namedValues')[copyIndex()], 'value'), parameters('newGuidValue'))]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -2897,7 +2984,9 @@ "value": { "value": "[parameters('policies')[copyIndex()].value]" }, - "format": "[if(contains(parameters('policies')[copyIndex()], 'format'), createObject('value', parameters('policies')[copyIndex()].format), createObject('value', 'xml'))]" + "format": { + "value": "[coalesce(tryGet(parameters('policies')[copyIndex()], 'format'), 'xml')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -3003,17 +3092,33 @@ "apiManagementServiceName": { "value": "[parameters('name')]" }, - "apis": "[if(contains(parameters('products')[copyIndex()], 'apis'), createObject('value', parameters('products')[copyIndex()].apis), createObject('value', createArray()))]", - "approvalRequired": "[if(contains(parameters('products')[copyIndex()], 'approvalRequired'), createObject('value', parameters('products')[copyIndex()].approvalRequired), createObject('value', false()))]", - "groups": "[if(contains(parameters('products')[copyIndex()], 'groups'), createObject('value', parameters('products')[copyIndex()].groups), createObject('value', createArray()))]", + "apis": { + "value": "[coalesce(tryGet(parameters('products')[copyIndex()], 'apis'), createArray())]" + }, + "approvalRequired": { + "value": "[coalesce(tryGet(parameters('products')[copyIndex()], 'approvalRequired'), false())]" + }, + "groups": { + "value": "[coalesce(tryGet(parameters('products')[copyIndex()], 'groups'), createArray())]" + }, "name": { "value": "[parameters('products')[copyIndex()].name]" }, - "description": "[if(contains(parameters('products')[copyIndex()], 'description'), createObject('value', parameters('products')[copyIndex()].description), createObject('value', ''))]", - "state": "[if(contains(parameters('products')[copyIndex()], 'state'), createObject('value', parameters('products')[copyIndex()].state), createObject('value', 'published'))]", - "subscriptionRequired": "[if(contains(parameters('products')[copyIndex()], 'subscriptionRequired'), createObject('value', parameters('products')[copyIndex()].subscriptionRequired), createObject('value', false()))]", - "subscriptionsLimit": "[if(contains(parameters('products')[copyIndex()], 'subscriptionsLimit'), createObject('value', parameters('products')[copyIndex()].subscriptionsLimit), createObject('value', 1))]", - "terms": "[if(contains(parameters('products')[copyIndex()], 'terms'), createObject('value', parameters('products')[copyIndex()].terms), createObject('value', ''))]" + "description": { + "value": "[coalesce(tryGet(parameters('products')[copyIndex()], 'description'), '')]" + }, + "state": { + "value": "[coalesce(tryGet(parameters('products')[copyIndex()], 'state'), 'published')]" + }, + "subscriptionRequired": { + "value": "[coalesce(tryGet(parameters('products')[copyIndex()], 'subscriptionRequired'), false())]" + }, + "subscriptionsLimit": { + "value": "[coalesce(tryGet(parameters('products')[copyIndex()], 'subscriptionsLimit'), 1)]" + }, + "terms": { + "value": "[coalesce(tryGet(parameters('products')[copyIndex()], 'terms'), '')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/avm/res/api-management/service/tests/e2e/max/main.test.bicep b/avm/res/api-management/service/tests/e2e/max/main.test.bicep index 39628c62ba..2989495645 100644 --- a/avm/res/api-management/service/tests/e2e/max/main.test.bicep +++ b/avm/res/api-management/service/tests/e2e/max/main.test.bicep @@ -171,6 +171,7 @@ module testDeployment '../../../main.bicep' = [ { name: 'aad' clientId: 'apimClientid' + clientLibrary: 'MSAL-2' clientSecret: 'apimSlientSecret' authority: split(environment().authentication.loginEndpoint, '/')[2] signinTenant: 'mytenant.onmicrosoft.com' diff --git a/avm/res/api-management/service/tests/e2e/waf-aligned/main.test.bicep b/avm/res/api-management/service/tests/e2e/waf-aligned/main.test.bicep index 35fac0d255..ccf1f295b4 100644 --- a/avm/res/api-management/service/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/api-management/service/tests/e2e/waf-aligned/main.test.bicep @@ -161,6 +161,7 @@ module testDeployment '../../../main.bicep' = [ { name: 'aad' clientId: 'apimClientid' + clientLibrary: 'MSAL-2' clientSecret: customSecret authority: split(environment().authentication.loginEndpoint, '/')[2] signinTenant: 'mytenant.onmicrosoft.com' diff --git a/avm/res/api-management/service/version.json b/avm/res/api-management/service/version.json index c177b1bb58..3f863a2bec 100644 --- a/avm/res/api-management/service/version.json +++ b/avm/res/api-management/service/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.3", + "version": "0.4", "pathFilters": [ "./main.json" ] From cabac4722c8e0db3992c357a5013c82e09817f37 Mon Sep 17 00:00:00 2001 From: Ahmad Abdalla <28486158+ahmadabdalla@users.noreply.github.com> Date: Tue, 27 Aug 2024 21:36:20 +1000 Subject: [PATCH 5/5] feat: `avm/res/dev-test-lab/lab` Enable Artifact repo Security Token on the parent module & uplift to leverage UDTs (#3102) ## Description Closes #3056 - Enable `securityToken` from the parent module for artifacts authentication. **Module deployment history View** ![image](https://github.com/user-attachments/assets/1cf641ce-5855-4917-994a-5dc744ed5550) **Child Module deployment history View** ![image](https://github.com/user-attachments/assets/e24bd2d7-537e-4f40-a396-f4a2dd35e5d6) **Code view** ![image](https://github.com/user-attachments/assets/c95981c1-488e-43f6-aaef-59e9d2765e80) - Uplift module to support User Defined Types (UDT) - Leverage the `@export` feature of bicep to reduce code duplication for UDT. - Uplift module to `v0.3` - Simplified `events` parameters. ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.dev-test-lab.lab](https://github.com/ahmadabdalla/bicep-registry-modules/actions/workflows/avm.res.dev-test-lab.lab.yml/badge.svg?branch=users%2Fahmad%2F3056_DTL)](https://github.com/ahmadabdalla/bicep-registry-modules/actions/workflows/avm.res.dev-test-lab.lab.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [ ] Azure Verified Module updates: - [x] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [x] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [x] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/dev-test-lab/lab/README.md | 1320 +++++++++++++--- .../dev-test-lab/lab/artifactsource/README.md | 6 - .../lab/artifactsource/main.bicep | 21 +- .../dev-test-lab/lab/artifactsource/main.json | 25 +- avm/res/dev-test-lab/lab/cost/README.md | 2 - avm/res/dev-test-lab/lab/cost/main.bicep | 4 +- avm/res/dev-test-lab/lab/cost/main.json | 8 +- avm/res/dev-test-lab/lab/main.bicep | 327 +++- avm/res/dev-test-lab/lab/main.json | 1321 +++++++++++++++-- .../lab/notificationchannel/README.md | 1 - .../lab/notificationchannel/main.bicep | 8 +- .../lab/notificationchannel/main.json | 16 +- .../lab/policyset/policy/main.bicep | 4 +- .../lab/policyset/policy/main.json | 8 +- avm/res/dev-test-lab/lab/schedule/README.md | 107 +- avm/res/dev-test-lab/lab/schedule/main.bicep | 77 +- avm/res/dev-test-lab/lab/schedule/main.json | 147 +- .../lab/tests/e2e/max/main.test.bicep | 33 +- avm/res/dev-test-lab/lab/version.json | 4 +- .../lab/virtualnetwork/main.bicep | 46 + .../dev-test-lab/lab/virtualnetwork/main.json | 124 +- 21 files changed, 3093 insertions(+), 516 deletions(-) diff --git a/avm/res/dev-test-lab/lab/README.md b/avm/res/dev-test-lab/lab/README.md index 60f0e50380..2ba9317754 100644 --- a/avm/res/dev-test-lab/lab/README.md +++ b/avm/res/dev-test-lab/lab/README.md @@ -109,12 +109,11 @@ module lab 'br/public:avm/res/dev-test-lab/lab:' = { } artifactsources: [ { - branchRef: 'master' displayName: 'Public Artifact Repo' folderPath: '/Artifacts' name: 'Public Repo' sourceType: 'GitHub' - status: 'Disabled' + status: 'Enabled' uri: 'https://github.com/Azure/azure-devtestlab.git' } { @@ -124,12 +123,28 @@ module lab 'br/public:avm/res/dev-test-lab/lab:' = { name: 'Public Environment Repo' sourceType: 'GitHub' status: 'Disabled' + tags: { + 'hidden-title': 'This is visible in the resource name' + labName: 'dtllmax001' + resourceType: 'DevTest Lab' + } + uri: 'https://github.com/Azure/azure-devtestlab.git' + } + { + armTemplateFolderPath: '/ArmTemplates' + branchRef: 'main' + displayName: 'Private Artifact Repo' + folderPath: '/Artifacts' + name: 'Private Repo' + securityToken: '' + status: 'Disabled' uri: 'https://github.com/Azure/azure-devtestlab.git' } ] artifactsStorageAccount: '' browserConnect: 'Enabled' costs: { + currencyCode: 'AUD' cycleType: 'CalendarMonth' status: 'Enabled' target: 450 @@ -163,9 +178,7 @@ module lab 'br/public:avm/res/dev-test-lab/lab:' = { description: 'Integration configured for auto-shutdown' emailRecipient: 'mail@contosodtlmail.com' events: [ - { - eventName: 'AutoShutdown' - } + 'AutoShutdown' ] name: 'autoShutdown' notificationLocale: 'en' @@ -173,9 +186,7 @@ module lab 'br/public:avm/res/dev-test-lab/lab:' = { } { events: [ - { - eventName: 'Cost' - } + 'Cost' ] name: 'costThreshold' webHookUrl: 'https://webhook.contosotest.com' @@ -268,8 +279,10 @@ module lab 'br/public:avm/res/dev-test-lab/lab:' = { time: '0000' } name: 'LabVmsShutdown' - notificationSettingsStatus: 'Enabled' - notificationSettingsTimeInMinutes: 30 + notificationSettings: { + status: 'Enabled' + timeInMinutes: 30 + } status: 'Enabled' taskType: 'LabVmsShutdownTask' timeZoneId: 'AUS Eastern Standard Time' @@ -367,12 +380,11 @@ module lab 'br/public:avm/res/dev-test-lab/lab:' = { "artifactsources": { "value": [ { - "branchRef": "master", "displayName": "Public Artifact Repo", "folderPath": "/Artifacts", "name": "Public Repo", "sourceType": "GitHub", - "status": "Disabled", + "status": "Enabled", "uri": "https://github.com/Azure/azure-devtestlab.git" }, { @@ -382,6 +394,21 @@ module lab 'br/public:avm/res/dev-test-lab/lab:' = { "name": "Public Environment Repo", "sourceType": "GitHub", "status": "Disabled", + "tags": { + "hidden-title": "This is visible in the resource name", + "labName": "dtllmax001", + "resourceType": "DevTest Lab" + }, + "uri": "https://github.com/Azure/azure-devtestlab.git" + }, + { + "armTemplateFolderPath": "/ArmTemplates", + "branchRef": "main", + "displayName": "Private Artifact Repo", + "folderPath": "/Artifacts", + "name": "Private Repo", + "securityToken": "", + "status": "Disabled", "uri": "https://github.com/Azure/azure-devtestlab.git" } ] @@ -394,6 +421,7 @@ module lab 'br/public:avm/res/dev-test-lab/lab:' = { }, "costs": { "value": { + "currencyCode": "AUD", "cycleType": "CalendarMonth", "status": "Enabled", "target": 450, @@ -451,9 +479,7 @@ module lab 'br/public:avm/res/dev-test-lab/lab:' = { "description": "Integration configured for auto-shutdown", "emailRecipient": "mail@contosodtlmail.com", "events": [ - { - "eventName": "AutoShutdown" - } + "AutoShutdown" ], "name": "autoShutdown", "notificationLocale": "en", @@ -461,9 +487,7 @@ module lab 'br/public:avm/res/dev-test-lab/lab:' = { }, { "events": [ - { - "eventName": "Cost" - } + "Cost" ], "name": "costThreshold", "webHookUrl": "https://webhook.contosotest.com" @@ -564,8 +588,10 @@ module lab 'br/public:avm/res/dev-test-lab/lab:' = { "time": "0000" }, "name": "LabVmsShutdown", - "notificationSettingsStatus": "Enabled", - "notificationSettingsTimeInMinutes": 30, + "notificationSettings": { + "status": "Enabled", + "timeInMinutes": 30 + }, "status": "Enabled", "taskType": "LabVmsShutdownTask", "timeZoneId": "AUS Eastern Standard Time" @@ -775,7 +801,84 @@ Notification Channels to create for the lab. Required if the schedules property - Required: No - Type: array -- Default: `[]` + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`events`](#parameter-notificationchannelsevents) | array | The list of event for which this notification is enabled. Can be "AutoShutdown" or "Cost". | +| [`name`](#parameter-notificationchannelsname) | string | The name of the notification channel. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`emailRecipient`](#parameter-notificationchannelsemailrecipient) | string | The email recipient to send notifications to (can be a list of semi-colon separated email addresses). Required if "webHookUrl" is empty. | +| [`webHookUrl`](#parameter-notificationchannelswebhookurl) | string | The webhook URL to which the notification will be sent. Required if "emailRecipient" is empty. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`description`](#parameter-notificationchannelsdescription) | string | The description of the notification. | +| [`notificationLocale`](#parameter-notificationchannelsnotificationlocale) | string | The locale to use when sending a notification (fallback for unsupported languages is EN). | +| [`tags`](#parameter-notificationchannelstags) | object | The tags of the notification channel. | + +### Parameter: `notificationchannels.events` + +The list of event for which this notification is enabled. Can be "AutoShutdown" or "Cost". + +- Required: Yes +- Type: array + +### Parameter: `notificationchannels.name` + +The name of the notification channel. + +- Required: Yes +- Type: string +- Allowed: + ```Bicep + [ + 'autoShutdown' + 'costThreshold' + ] + ``` + +### Parameter: `notificationchannels.emailRecipient` + +The email recipient to send notifications to (can be a list of semi-colon separated email addresses). Required if "webHookUrl" is empty. + +- Required: No +- Type: string + +### Parameter: `notificationchannels.webHookUrl` + +The webhook URL to which the notification will be sent. Required if "emailRecipient" is empty. + +- Required: No +- Type: string + +### Parameter: `notificationchannels.description` + +The description of the notification. + +- Required: No +- Type: string + +### Parameter: `notificationchannels.notificationLocale` + +The locale to use when sending a notification (fallback for unsupported languages is EN). + +- Required: No +- Type: string + +### Parameter: `notificationchannels.tags` + +The tags of the notification channel. + +- Required: No +- Type: object ### Parameter: `announcement` @@ -791,227 +894,248 @@ Artifact sources to create for the lab. - Required: No - Type: array -- Default: `[]` -### Parameter: `artifactsStorageAccount` +**Required parameters** -The resource ID of the storage account used to store artifacts and images by the lab. Also used for defaultStorageAccount, defaultPremiumStorageAccount and premiumDataDiskStorageAccount properties. If left empty, a default storage account will be created by the lab and used. +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-artifactsourcesname) | string | The name of the artifact source. | +| [`uri`](#parameter-artifactsourcesuri) | string | The artifact source's URI. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`armTemplateFolderPath`](#parameter-artifactsourcesarmtemplatefolderpath) | string | The folder containing Azure Resource Manager templates. Required if "folderPath" is empty. | +| [`folderPath`](#parameter-artifactsourcesfolderpath) | string | The folder containing artifacts. At least one folder path is required. Required if "armTemplateFolderPath" is empty. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`branchRef`](#parameter-artifactsourcesbranchref) | string | The artifact source's branch reference (e.g. main or master). | +| [`displayName`](#parameter-artifactsourcesdisplayname) | string | The display name of the artifact source. Default is the name of the artifact source. | +| [`securityToken`](#parameter-artifactsourcessecuritytoken) | securestring | The security token to authenticate to the artifact source. | +| [`sourceType`](#parameter-artifactsourcessourcetype) | string | The artifact source's type. | +| [`status`](#parameter-artifactsourcesstatus) | string | Indicates if the artifact source is enabled (values: Enabled, Disabled). Default is "Enabled". | +| [`tags`](#parameter-artifactsourcestags) | object | The tags of the artifact source. | + +### Parameter: `artifactsources.name` + +The name of the artifact source. + +- Required: Yes +- Type: string + +### Parameter: `artifactsources.uri` + +The artifact source's URI. + +- Required: Yes +- Type: string + +### Parameter: `artifactsources.armTemplateFolderPath` + +The folder containing Azure Resource Manager templates. Required if "folderPath" is empty. - Required: No - Type: string -- Default: `''` -### Parameter: `browserConnect` +### Parameter: `artifactsources.folderPath` -Enable browser connect on virtual machines if the lab's VNETs have configured Azure Bastion. +The folder containing artifacts. At least one folder path is required. Required if "armTemplateFolderPath" is empty. - Required: No - Type: string -- Default: `'Disabled'` -- Allowed: - ```Bicep - [ - 'Disabled' - 'Enabled' - ] - ``` -### Parameter: `costs` +### Parameter: `artifactsources.branchRef` -Costs to create for the lab. +The artifact source's branch reference (e.g. main or master). - Required: No -- Type: object -- Default: `{}` +- Type: string -### Parameter: `disableAutoUpgradeCseMinorVersion` +### Parameter: `artifactsources.displayName` -Disable auto upgrade custom script extension minor version. +The display name of the artifact source. Default is the name of the artifact source. - Required: No -- Type: bool -- Default: `False` +- Type: string -### Parameter: `enableTelemetry` +### Parameter: `artifactsources.securityToken` -Enable/Disable usage telemetry for module. +The security token to authenticate to the artifact source. - Required: No -- Type: bool -- Default: `True` +- Type: securestring -### Parameter: `encryptionType` +### Parameter: `artifactsources.sourceType` -Specify how OS and data disks created as part of the lab are encrypted. +The artifact source's type. - Required: No - Type: string -- Default: `'EncryptionAtRestWithPlatformKey'` - Allowed: ```Bicep [ - 'EncryptionAtRestWithCustomerKey' - 'EncryptionAtRestWithPlatformKey' + 'GitHub' + 'StorageAccount' + 'VsoGit' ] ``` -### Parameter: `environmentPermission` +### Parameter: `artifactsources.status` -The access rights to be granted to the user when provisioning an environment. +Indicates if the artifact source is enabled (values: Enabled, Disabled). Default is "Enabled". - Required: No - Type: string -- Default: `'Reader'` - Allowed: ```Bicep [ - 'Contributor' - 'Reader' + 'Disabled' + 'Enabled' ] ``` -### Parameter: `extendedProperties` +### Parameter: `artifactsources.tags` -Extended properties of the lab used for experimental features. +The tags of the artifact source. - Required: No - Type: object -- Default: `{}` -### Parameter: `isolateLabResources` +### Parameter: `artifactsStorageAccount` -Enable lab resources isolation from the public internet. +The resource ID of the storage account used to store artifacts and images by the lab. Also used for defaultStorageAccount, defaultPremiumStorageAccount and premiumDataDiskStorageAccount properties. If left empty, a default storage account will be created by the lab and used. - Required: No - Type: string -- Default: `'Enabled'` -- Allowed: - ```Bicep - [ - 'Disabled' - 'Enabled' - ] - ``` +- Default: `''` -### Parameter: `labStorageType` +### Parameter: `browserConnect` -Type of storage used by the lab. It can be either Premium or Standard. +Enable browser connect on virtual machines if the lab's VNETs have configured Azure Bastion. - Required: No - Type: string -- Default: `'Premium'` +- Default: `'Disabled'` - Allowed: ```Bicep [ - 'Premium' - 'Standard' - 'StandardSSD' + 'Disabled' + 'Enabled' ] ``` -### Parameter: `location` +### Parameter: `costs` -Location for all Resources. +Costs to create for the lab. - Required: No -- Type: string -- Default: `[resourceGroup().location]` - -### Parameter: `lock` +- Type: object -The lock settings of the service. +**Required parameters** -- Required: No -- Type: object +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`cycleType`](#parameter-costscycletype) | string | Reporting cycle type. | -**Optional parameters** +**Conditional parameters** | Parameter | Type | Description | | :-- | :-- | :-- | -| [`kind`](#parameter-lockkind) | string | Specify the type of lock. | -| [`name`](#parameter-lockname) | string | Specify the name of lock. | +| [`cycleEndDateTime`](#parameter-costscycleenddatetime) | string | Reporting cycle end date in the zulu time format (e.g. 2023-12-01T00:00:00.000Z). Required if cycleType is set to "Custom". | +| [`cycleStartDateTime`](#parameter-costscyclestartdatetime) | string | Reporting cycle start date in the zulu time format (e.g. 2023-12-01T00:00:00.000Z). Required if cycleType is set to "Custom". | -### Parameter: `lock.kind` +**Optional parameters** -Specify the type of lock. +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`currencyCode`](#parameter-costscurrencycode) | string | The currency code of the cost. Default is "USD". | +| [`status`](#parameter-costsstatus) | string | Target cost status. | +| [`tags`](#parameter-coststags) | object | The tags of the resource. | +| [`target`](#parameter-coststarget) | int | Lab target cost (e.g. 100). The target cost will appear in the "Cost trend" chart to allow tracking lab spending relative to the target cost for the current reporting cycleSetting the target cost to 0 will disable all thresholds. | +| [`thresholdValue100DisplayOnChart`](#parameter-coststhresholdvalue100displayonchart) | string | Target Cost threshold at 100% display on chart. Indicates whether this threshold will be displayed on cost charts. | +| [`thresholdValue100SendNotificationWhenExceeded`](#parameter-coststhresholdvalue100sendnotificationwhenexceeded) | string | Target cost threshold at 100% send notification when exceeded. Indicates whether notifications will be sent when this threshold is exceeded. | +| [`thresholdValue125DisplayOnChart`](#parameter-coststhresholdvalue125displayonchart) | string | Target Cost threshold at 125% display on chart. Indicates whether this threshold will be displayed on cost charts. | +| [`thresholdValue125SendNotificationWhenExceeded`](#parameter-coststhresholdvalue125sendnotificationwhenexceeded) | string | Target cost threshold at 125% send notification when exceeded. Indicates whether notifications will be sent when this threshold is exceeded. | +| [`thresholdValue25DisplayOnChart`](#parameter-coststhresholdvalue25displayonchart) | string | Target Cost threshold at 25% display on chart. Indicates whether this threshold will be displayed on cost charts. | +| [`thresholdValue25SendNotificationWhenExceeded`](#parameter-coststhresholdvalue25sendnotificationwhenexceeded) | string | Target cost threshold at 25% send notification when exceeded. Indicates whether notifications will be sent when this threshold is exceeded. | +| [`thresholdValue50DisplayOnChart`](#parameter-coststhresholdvalue50displayonchart) | string | Target Cost threshold at 50% display on chart. Indicates whether this threshold will be displayed on cost charts. | +| [`thresholdValue50SendNotificationWhenExceeded`](#parameter-coststhresholdvalue50sendnotificationwhenexceeded) | string | Target cost threshold at 50% send notification when exceeded. Indicates whether notifications will be sent when this threshold is exceeded. | +| [`thresholdValue75DisplayOnChart`](#parameter-coststhresholdvalue75displayonchart) | string | Target Cost threshold at 75% display on chart. Indicates whether this threshold will be displayed on cost charts. | +| [`thresholdValue75SendNotificationWhenExceeded`](#parameter-coststhresholdvalue75sendnotificationwhenexceeded) | string | Target cost threshold at 75% send notification when exceeded. Indicates whether notifications will be sent when this threshold is exceeded. | + +### Parameter: `costs.cycleType` + +Reporting cycle type. -- Required: No +- Required: Yes - Type: string - Allowed: ```Bicep [ - 'CanNotDelete' - 'None' - 'ReadOnly' + 'CalendarMonth' + 'Custom' ] ``` -### Parameter: `lock.name` +### Parameter: `costs.cycleEndDateTime` -Specify the name of lock. +Reporting cycle end date in the zulu time format (e.g. 2023-12-01T00:00:00.000Z). Required if cycleType is set to "Custom". - Required: No - Type: string -### Parameter: `managedIdentities` +### Parameter: `costs.cycleStartDateTime` -The managed identity definition for this resource. DevTest Labs creates a system-assigned identity by default the first time it creates the lab environment. +Reporting cycle start date in the zulu time format (e.g. 2023-12-01T00:00:00.000Z). Required if cycleType is set to "Custom". - Required: No -- Type: object - -**Optional parameters** - -| Parameter | Type | Description | -| :-- | :-- | :-- | -| [`userAssignedResourceIds`](#parameter-managedidentitiesuserassignedresourceids) | array | The resource ID(s) to assign to the resource. Currently, a single user-assigned identity is supported per lab. | - -### Parameter: `managedIdentities.userAssignedResourceIds` - -The resource ID(s) to assign to the resource. Currently, a single user-assigned identity is supported per lab. - -- Required: Yes -- Type: array +- Type: string -### Parameter: `managementIdentitiesResourceIds` +### Parameter: `costs.currencyCode` -The resource ID(s) to assign to the virtual machines associated with this lab. +The currency code of the cost. Default is "USD". - Required: No -- Type: array -- Default: `[]` +- Type: string -### Parameter: `mandatoryArtifactsResourceIdsLinux` +### Parameter: `costs.status` -The ordered list of artifact resource IDs that should be applied on all Linux VM creations by default, prior to the artifacts specified by the user. +Target cost status. - Required: No -- Type: array -- Default: `[]` +- Type: string +- Allowed: + ```Bicep + [ + 'Disabled' + 'Enabled' + ] + ``` -### Parameter: `mandatoryArtifactsResourceIdsWindows` +### Parameter: `costs.tags` -The ordered list of artifact resource IDs that should be applied on all Windows VM creations by default, prior to the artifacts specified by the user. +The tags of the resource. - Required: No -- Type: array -- Default: `[]` +- Type: object -### Parameter: `policies` +### Parameter: `costs.target` -Policies to create for the lab. +Lab target cost (e.g. 100). The target cost will appear in the "Cost trend" chart to allow tracking lab spending relative to the target cost for the current reporting cycleSetting the target cost to 0 will disable all thresholds. - Required: No -- Type: array -- Default: `[]` +- Type: int -### Parameter: `premiumDataDisks` +### Parameter: `costs.thresholdValue100DisplayOnChart` -The setting to enable usage of premium data disks. When its value is "Enabled", creation of standard or premium data disks is allowed. When its value is "Disabled", only creation of standard data disks is allowed. Default is "Disabled". +Target Cost threshold at 100% display on chart. Indicates whether this threshold will be displayed on cost charts. - Required: No - Type: string -- Default: `'Disabled'` - Allowed: ```Bicep [ @@ -1020,110 +1144,740 @@ The setting to enable usage of premium data disks. When its value is "Enabled", ] ``` -### Parameter: `roleAssignments` +### Parameter: `costs.thresholdValue100SendNotificationWhenExceeded` -Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalIds' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. +Target cost threshold at 100% send notification when exceeded. Indicates whether notifications will be sent when this threshold is exceeded. - Required: No -- Type: array +- Type: string +- Allowed: + ```Bicep + [ + 'Disabled' + 'Enabled' + ] + ``` + +### Parameter: `costs.thresholdValue125DisplayOnChart` + +Target Cost threshold at 125% display on chart. Indicates whether this threshold will be displayed on cost charts. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Disabled' + 'Enabled' + ] + ``` + +### Parameter: `costs.thresholdValue125SendNotificationWhenExceeded` + +Target cost threshold at 125% send notification when exceeded. Indicates whether notifications will be sent when this threshold is exceeded. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Disabled' + 'Enabled' + ] + ``` + +### Parameter: `costs.thresholdValue25DisplayOnChart` + +Target Cost threshold at 25% display on chart. Indicates whether this threshold will be displayed on cost charts. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Disabled' + 'Enabled' + ] + ``` + +### Parameter: `costs.thresholdValue25SendNotificationWhenExceeded` + +Target cost threshold at 25% send notification when exceeded. Indicates whether notifications will be sent when this threshold is exceeded. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Disabled' + 'Enabled' + ] + ``` + +### Parameter: `costs.thresholdValue50DisplayOnChart` + +Target Cost threshold at 50% display on chart. Indicates whether this threshold will be displayed on cost charts. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Disabled' + 'Enabled' + ] + ``` + +### Parameter: `costs.thresholdValue50SendNotificationWhenExceeded` + +Target cost threshold at 50% send notification when exceeded. Indicates whether notifications will be sent when this threshold is exceeded. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Disabled' + 'Enabled' + ] + ``` + +### Parameter: `costs.thresholdValue75DisplayOnChart` + +Target Cost threshold at 75% display on chart. Indicates whether this threshold will be displayed on cost charts. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Disabled' + 'Enabled' + ] + ``` + +### Parameter: `costs.thresholdValue75SendNotificationWhenExceeded` + +Target cost threshold at 75% send notification when exceeded. Indicates whether notifications will be sent when this threshold is exceeded. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Disabled' + 'Enabled' + ] + ``` + +### Parameter: `disableAutoUpgradeCseMinorVersion` + +Disable auto upgrade custom script extension minor version. + +- Required: No +- Type: bool +- Default: `False` + +### Parameter: `enableTelemetry` + +Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `encryptionType` + +Specify how OS and data disks created as part of the lab are encrypted. + +- Required: No +- Type: string +- Default: `'EncryptionAtRestWithPlatformKey'` +- Allowed: + ```Bicep + [ + 'EncryptionAtRestWithCustomerKey' + 'EncryptionAtRestWithPlatformKey' + ] + ``` + +### Parameter: `environmentPermission` + +The access rights to be granted to the user when provisioning an environment. + +- Required: No +- Type: string +- Default: `'Reader'` +- Allowed: + ```Bicep + [ + 'Contributor' + 'Reader' + ] + ``` + +### Parameter: `extendedProperties` + +Extended properties of the lab used for experimental features. + +- Required: No +- Type: object +- Default: `{}` + +### Parameter: `isolateLabResources` + +Enable lab resources isolation from the public internet. + +- Required: No +- Type: string +- Default: `'Enabled'` +- Allowed: + ```Bicep + [ + 'Disabled' + 'Enabled' + ] + ``` + +### Parameter: `labStorageType` + +Type of storage used by the lab. It can be either Premium or Standard. + +- Required: No +- Type: string +- Default: `'Premium'` +- Allowed: + ```Bicep + [ + 'Premium' + 'Standard' + 'StandardSSD' + ] + ``` + +### Parameter: `location` + +Location for all Resources. + +- Required: No +- Type: string +- Default: `[resourceGroup().location]` + +### Parameter: `lock` + +The lock settings of the service. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`kind`](#parameter-lockkind) | string | Specify the type of lock. | +| [`name`](#parameter-lockname) | string | Specify the name of lock. | + +### Parameter: `lock.kind` + +Specify the type of lock. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'CanNotDelete' + 'None' + 'ReadOnly' + ] + ``` + +### Parameter: `lock.name` + +Specify the name of lock. + +- Required: No +- Type: string + +### Parameter: `managedIdentities` + +The managed identity definition for this resource. DevTest Labs creates a system-assigned identity by default the first time it creates the lab environment. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`userAssignedResourceIds`](#parameter-managedidentitiesuserassignedresourceids) | array | The resource ID(s) to assign to the resource. Currently, a single user-assigned identity is supported per lab. | + +### Parameter: `managedIdentities.userAssignedResourceIds` + +The resource ID(s) to assign to the resource. Currently, a single user-assigned identity is supported per lab. + +- Required: Yes +- Type: array + +### Parameter: `managementIdentitiesResourceIds` + +The resource ID(s) to assign to the virtual machines associated with this lab. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `mandatoryArtifactsResourceIdsLinux` + +The ordered list of artifact resource IDs that should be applied on all Linux VM creations by default, prior to the artifacts specified by the user. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `mandatoryArtifactsResourceIdsWindows` + +The ordered list of artifact resource IDs that should be applied on all Windows VM creations by default, prior to the artifacts specified by the user. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `policies` + +Policies to create for the lab. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`evaluatorType`](#parameter-policiesevaluatortype) | string | The evaluator type of the policy (i.e. AllowedValuesPolicy, MaxValuePolicy). | +| [`factName`](#parameter-policiesfactname) | string | The fact name of the policy. | +| [`name`](#parameter-policiesname) | string | The name of the policy. | +| [`threshold`](#parameter-policiesthreshold) | string | The threshold of the policy (i.e. a number for MaxValuePolicy, and a JSON array of values for AllowedValuesPolicy). | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`description`](#parameter-policiesdescription) | string | The description of the policy. | +| [`factData`](#parameter-policiesfactdata) | string | The fact data of the policy. | +| [`status`](#parameter-policiesstatus) | string | The status of the policy. Default is "Enabled". | + +### Parameter: `policies.evaluatorType` + +The evaluator type of the policy (i.e. AllowedValuesPolicy, MaxValuePolicy). + +- Required: Yes +- Type: string +- Allowed: + ```Bicep + [ + 'AllowedValuesPolicy' + 'MaxValuePolicy' + ] + ``` + +### Parameter: `policies.factName` + +The fact name of the policy. + +- Required: Yes +- Type: string +- Allowed: + ```Bicep + [ + 'EnvironmentTemplate' + 'GalleryImage' + 'LabPremiumVmCount' + 'LabTargetCost' + 'LabVmCount' + 'LabVmSize' + 'ScheduleEditPermission' + 'UserOwnedLabPremiumVmCount' + 'UserOwnedLabVmCount' + 'UserOwnedLabVmCountInSubnet' + ] + ``` + +### Parameter: `policies.name` + +The name of the policy. + +- Required: Yes +- Type: string + +### Parameter: `policies.threshold` + +The threshold of the policy (i.e. a number for MaxValuePolicy, and a JSON array of values for AllowedValuesPolicy). + +- Required: Yes +- Type: string + +### Parameter: `policies.description` + +The description of the policy. + +- Required: No +- Type: string + +### Parameter: `policies.factData` + +The fact data of the policy. + +- Required: No +- Type: string + +### Parameter: `policies.status` + +The status of the policy. Default is "Enabled". + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Disabled' + 'Enabled' + ] + ``` + +### Parameter: `premiumDataDisks` + +The setting to enable usage of premium data disks. When its value is "Enabled", creation of standard or premium data disks is allowed. When its value is "Disabled", only creation of standard data disks is allowed. Default is "Disabled". + +- Required: No +- Type: string +- Default: `'Disabled'` +- Allowed: + ```Bicep + [ + 'Disabled' + 'Enabled' + ] + ``` + +### Parameter: `roleAssignments` + +Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalIds' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`principalId`](#parameter-roleassignmentsprincipalid) | string | The principal ID of the principal (user/group/identity) to assign the role to. | +| [`roleDefinitionIdOrName`](#parameter-roleassignmentsroledefinitionidorname) | string | 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'. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`condition`](#parameter-roleassignmentscondition) | string | 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`](#parameter-roleassignmentsconditionversion) | string | Version of the condition. | +| [`delegatedManagedIdentityResourceId`](#parameter-roleassignmentsdelegatedmanagedidentityresourceid) | string | The Resource Id of the delegated managed identity resource. | +| [`description`](#parameter-roleassignmentsdescription) | string | The description of the role assignment. | +| [`name`](#parameter-roleassignmentsname) | string | The name (as GUID) of the role assignment. If not provided, a GUID will be generated. | +| [`principalType`](#parameter-roleassignmentsprincipaltype) | string | The principal type of the assigned principal ID. | + +### Parameter: `roleAssignments.principalId` + +The principal ID of the principal (user/group/identity) to assign the role to. + +- Required: Yes +- Type: string + +### Parameter: `roleAssignments.roleDefinitionIdOrName` + +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'. + +- Required: Yes +- Type: string + +### Parameter: `roleAssignments.condition` + +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". + +- Required: No +- Type: string + +### Parameter: `roleAssignments.conditionVersion` + +Version of the condition. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + '2.0' + ] + ``` + +### Parameter: `roleAssignments.delegatedManagedIdentityResourceId` + +The Resource Id of the delegated managed identity resource. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.description` + +The description of the role assignment. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.name` + +The name (as GUID) of the role assignment. If not provided, a GUID will be generated. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.principalType` + +The principal type of the assigned principal ID. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Device' + 'ForeignGroup' + 'Group' + 'ServicePrincipal' + 'User' + ] + ``` + +### Parameter: `schedules` + +Schedules to create for the lab. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-schedulesname) | string | The name of the schedule. | +| [`taskType`](#parameter-schedulestasktype) | string | The task type of the schedule (e.g. LabVmsShutdownTask, LabVmsStartupTask). | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`dailyRecurrence`](#parameter-schedulesdailyrecurrence) | object | The daily recurrence of the schedule. | +| [`hourlyRecurrence`](#parameter-scheduleshourlyrecurrence) | object | If the schedule will occur multiple times a day, specify the hourly recurrence. | +| [`notificationSettings`](#parameter-schedulesnotificationsettings) | object | The notification settings for the schedule. | +| [`status`](#parameter-schedulesstatus) | string | The status of the schedule (i.e. Enabled, Disabled). Default is "Enabled". | +| [`tags`](#parameter-schedulestags) | object | The tags of the schedule. | +| [`targetResourceId`](#parameter-schedulestargetresourceid) | string | The resource ID to which the schedule belongs. | +| [`timeZoneId`](#parameter-schedulestimezoneid) | string | The time zone ID of the schedule. Defaults to "Pacific Standard time". | +| [`weeklyRecurrence`](#parameter-schedulesweeklyrecurrence) | object | If the schedule will occur only some days of the week, specify the weekly recurrence. | + +### Parameter: `schedules.name` + +The name of the schedule. + +- Required: Yes +- Type: string +- Allowed: + ```Bicep + [ + 'LabVmAutoStart' + 'LabVmsShutdown' + ] + ``` + +### Parameter: `schedules.taskType` + +The task type of the schedule (e.g. LabVmsShutdownTask, LabVmsStartupTask). + +- Required: Yes +- Type: string +- Allowed: + ```Bicep + [ + 'LabVmsShutdownTask' + 'LabVmsStartupTask' + ] + ``` + +### Parameter: `schedules.dailyRecurrence` + +The daily recurrence of the schedule. + +- Required: No +- Type: object **Required parameters** | Parameter | Type | Description | | :-- | :-- | :-- | -| [`principalId`](#parameter-roleassignmentsprincipalid) | string | The principal ID of the principal (user/group/identity) to assign the role to. | -| [`roleDefinitionIdOrName`](#parameter-roleassignmentsroledefinitionidorname) | string | 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'. | +| [`time`](#parameter-schedulesdailyrecurrencetime) | string | The time of day the schedule will occur. | -**Optional parameters** +### Parameter: `schedules.dailyRecurrence.time` + +The time of day the schedule will occur. + +- Required: Yes +- Type: string + +### Parameter: `schedules.hourlyRecurrence` + +If the schedule will occur multiple times a day, specify the hourly recurrence. + +- Required: No +- Type: object + +**Required parameters** | Parameter | Type | Description | | :-- | :-- | :-- | -| [`condition`](#parameter-roleassignmentscondition) | string | 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`](#parameter-roleassignmentsconditionversion) | string | Version of the condition. | -| [`delegatedManagedIdentityResourceId`](#parameter-roleassignmentsdelegatedmanagedidentityresourceid) | string | The Resource Id of the delegated managed identity resource. | -| [`description`](#parameter-roleassignmentsdescription) | string | The description of the role assignment. | -| [`name`](#parameter-roleassignmentsname) | string | The name (as GUID) of the role assignment. If not provided, a GUID will be generated. | -| [`principalType`](#parameter-roleassignmentsprincipaltype) | string | The principal type of the assigned principal ID. | +| [`minute`](#parameter-scheduleshourlyrecurrenceminute) | int | Minutes of the hour the schedule will run. | -### Parameter: `roleAssignments.principalId` +### Parameter: `schedules.hourlyRecurrence.minute` -The principal ID of the principal (user/group/identity) to assign the role to. +Minutes of the hour the schedule will run. - Required: Yes +- Type: int + +### Parameter: `schedules.notificationSettings` + +The notification settings for the schedule. + +- Required: No +- Type: object + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`emailRecipient`](#parameter-schedulesnotificationsettingsemailrecipient) | string | The email recipient to send notifications to (can be a list of semi-colon separated email addresses). Required if "webHookUrl" is empty. | +| [`webHookUrl`](#parameter-schedulesnotificationsettingswebhookurl) | string | The webhook URL to which the notification will be sent. Required if "emailRecipient" is empty. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`notificationLocale`](#parameter-schedulesnotificationsettingsnotificationlocale) | string | The locale to use when sending a notification (fallback for unsupported languages is EN). | +| [`status`](#parameter-schedulesnotificationsettingsstatus) | string | If notifications are enabled for this schedule (i.e. Enabled, Disabled). Default is Disabled. | +| [`timeInMinutes`](#parameter-schedulesnotificationsettingstimeinminutes) | int | Time in minutes before event at which notification will be sent. Default is 30 minutes if status is Enabled and not specified. | + +### Parameter: `schedules.notificationSettings.emailRecipient` + +The email recipient to send notifications to (can be a list of semi-colon separated email addresses). Required if "webHookUrl" is empty. + +- Required: No - Type: string -### Parameter: `roleAssignments.roleDefinitionIdOrName` +### Parameter: `schedules.notificationSettings.webHookUrl` -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'. +The webhook URL to which the notification will be sent. Required if "emailRecipient" is empty. -- Required: Yes +- Required: No - Type: string -### Parameter: `roleAssignments.condition` +### Parameter: `schedules.notificationSettings.notificationLocale` -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". +The locale to use when sending a notification (fallback for unsupported languages is EN). - Required: No - Type: string -### Parameter: `roleAssignments.conditionVersion` +### Parameter: `schedules.notificationSettings.status` -Version of the condition. +If notifications are enabled for this schedule (i.e. Enabled, Disabled). Default is Disabled. - Required: No - Type: string - Allowed: ```Bicep [ - '2.0' + 'Disabled' + 'Enabled' ] ``` -### Parameter: `roleAssignments.delegatedManagedIdentityResourceId` +### Parameter: `schedules.notificationSettings.timeInMinutes` -The Resource Id of the delegated managed identity resource. +Time in minutes before event at which notification will be sent. Default is 30 minutes if status is Enabled and not specified. - Required: No -- Type: string +- Type: int -### Parameter: `roleAssignments.description` +### Parameter: `schedules.status` -The description of the role assignment. +The status of the schedule (i.e. Enabled, Disabled). Default is "Enabled". - Required: No - Type: string +- Allowed: + ```Bicep + [ + 'Disabled' + 'Enabled' + ] + ``` -### Parameter: `roleAssignments.name` +### Parameter: `schedules.tags` -The name (as GUID) of the role assignment. If not provided, a GUID will be generated. +The tags of the schedule. + +- Required: No +- Type: object + +### Parameter: `schedules.targetResourceId` + +The resource ID to which the schedule belongs. - Required: No - Type: string -### Parameter: `roleAssignments.principalType` +### Parameter: `schedules.timeZoneId` -The principal type of the assigned principal ID. +The time zone ID of the schedule. Defaults to "Pacific Standard time". - Required: No - Type: string -- Allowed: - ```Bicep - [ - 'Device' - 'ForeignGroup' - 'Group' - 'ServicePrincipal' - 'User' - ] - ``` -### Parameter: `schedules` +### Parameter: `schedules.weeklyRecurrence` -Schedules to create for the lab. +If the schedule will occur only some days of the week, specify the weekly recurrence. - Required: No +- Type: object + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`time`](#parameter-schedulesweeklyrecurrencetime) | string | The time of day the schedule will occur. | +| [`weekdays`](#parameter-schedulesweeklyrecurrenceweekdays) | array | The days of the week for which the schedule is set (e.g. Sunday, Monday, Tuesday, etc.). | + +### Parameter: `schedules.weeklyRecurrence.time` + +The time of day the schedule will occur. + +- Required: Yes +- Type: string + +### Parameter: `schedules.weeklyRecurrence.weekdays` + +The days of the week for which the schedule is set (e.g. Sunday, Monday, Tuesday, etc.). + +- Required: Yes - Type: array -- Default: `[]` ### Parameter: `support` @@ -1146,7 +1900,221 @@ Virtual networks to create for the lab. - Required: No - Type: array -- Default: `[]` + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`externalProviderResourceId`](#parameter-virtualnetworksexternalproviderresourceid) | string | The external provider resource ID of the virtual network. | +| [`name`](#parameter-virtualnetworksname) | string | The name of the virtual network. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`allowedSubnets`](#parameter-virtualnetworksallowedsubnets) | array | The allowed subnets of the virtual network. | +| [`description`](#parameter-virtualnetworksdescription) | string | The description of the virtual network. | +| [`subnetOverrides`](#parameter-virtualnetworkssubnetoverrides) | array | The subnet overrides of the virtual network. | +| [`tags`](#parameter-virtualnetworkstags) | object | The tags of the virtual network. | + +### Parameter: `virtualnetworks.externalProviderResourceId` + +The external provider resource ID of the virtual network. + +- Required: Yes +- Type: string + +### Parameter: `virtualnetworks.name` + +The name of the virtual network. + +- Required: Yes +- Type: string + +### Parameter: `virtualnetworks.allowedSubnets` + +The allowed subnets of the virtual network. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`labSubnetName`](#parameter-virtualnetworksallowedsubnetslabsubnetname) | string | The name of the subnet as seen in the lab. | +| [`resourceId`](#parameter-virtualnetworksallowedsubnetsresourceid) | string | The resource ID of the allowed subnet. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`allowPublicIp`](#parameter-virtualnetworksallowedsubnetsallowpublicip) | string | The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny)). | + +### Parameter: `virtualnetworks.allowedSubnets.labSubnetName` + +The name of the subnet as seen in the lab. + +- Required: Yes +- Type: string + +### Parameter: `virtualnetworks.allowedSubnets.resourceId` + +The resource ID of the allowed subnet. + +- Required: Yes +- Type: string + +### Parameter: `virtualnetworks.allowedSubnets.allowPublicIp` + +The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny)). + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Allow' + 'Default' + 'Deny' + ] + ``` + +### Parameter: `virtualnetworks.description` + +The description of the virtual network. + +- Required: No +- Type: string + +### Parameter: `virtualnetworks.subnetOverrides` + +The subnet overrides of the virtual network. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`labSubnetName`](#parameter-virtualnetworkssubnetoverrideslabsubnetname) | string | The name given to the subnet within the lab. | +| [`resourceId`](#parameter-virtualnetworkssubnetoverridesresourceid) | string | The resource ID of the subnet. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`sharedPublicIpAddressConfiguration`](#parameter-virtualnetworkssubnetoverridessharedpublicipaddressconfiguration) | object | The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny)). | +| [`useInVmCreationPermission`](#parameter-virtualnetworkssubnetoverridesuseinvmcreationpermission) | string | Indicates whether this subnet can be used during virtual machine creation (i.e. Allow, Deny). | +| [`usePublicIpAddressPermission`](#parameter-virtualnetworkssubnetoverridesusepublicipaddresspermission) | string | Indicates whether public IP addresses can be assigned to virtual machines on this subnet (i.e. Allow, Deny). | +| [`virtualNetworkPoolName`](#parameter-virtualnetworkssubnetoverridesvirtualnetworkpoolname) | string | The virtual network pool associated with this subnet. | + +### Parameter: `virtualnetworks.subnetOverrides.labSubnetName` + +The name given to the subnet within the lab. + +- Required: Yes +- Type: string + +### Parameter: `virtualnetworks.subnetOverrides.resourceId` + +The resource ID of the subnet. + +- Required: Yes +- Type: string + +### Parameter: `virtualnetworks.subnetOverrides.sharedPublicIpAddressConfiguration` + +The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny)). + +- Required: Yes +- Type: object + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`allowedPorts`](#parameter-virtualnetworkssubnetoverridessharedpublicipaddressconfigurationallowedports) | array | Backend ports that virtual machines on this subnet are allowed to expose. | + +### Parameter: `virtualnetworks.subnetOverrides.sharedPublicIpAddressConfiguration.allowedPorts` + +Backend ports that virtual machines on this subnet are allowed to expose. + +- Required: Yes +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`backendPort`](#parameter-virtualnetworkssubnetoverridessharedpublicipaddressconfigurationallowedportsbackendport) | int | Backend port of the target virtual machine. | +| [`transportProtocol`](#parameter-virtualnetworkssubnetoverridessharedpublicipaddressconfigurationallowedportstransportprotocol) | string | Protocol type of the port. | + +### Parameter: `virtualnetworks.subnetOverrides.sharedPublicIpAddressConfiguration.allowedPorts.backendPort` + +Backend port of the target virtual machine. + +- Required: Yes +- Type: int + +### Parameter: `virtualnetworks.subnetOverrides.sharedPublicIpAddressConfiguration.allowedPorts.transportProtocol` + +Protocol type of the port. + +- Required: Yes +- Type: string +- Allowed: + ```Bicep + [ + 'Tcp' + 'Udp' + ] + ``` + +### Parameter: `virtualnetworks.subnetOverrides.useInVmCreationPermission` + +Indicates whether this subnet can be used during virtual machine creation (i.e. Allow, Deny). + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Allow' + 'Default' + 'Deny' + ] + ``` + +### Parameter: `virtualnetworks.subnetOverrides.usePublicIpAddressPermission` + +Indicates whether public IP addresses can be assigned to virtual machines on this subnet (i.e. Allow, Deny). + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Allow' + 'Default' + 'Deny' + ] + ``` + +### Parameter: `virtualnetworks.subnetOverrides.virtualNetworkPoolName` + +The virtual network pool associated with this subnet. + +- Required: No +- Type: string + +### Parameter: `virtualnetworks.tags` + +The tags of the virtual network. + +- Required: No +- Type: object ### Parameter: `vmCreationResourceGroupId` diff --git a/avm/res/dev-test-lab/lab/artifactsource/README.md b/avm/res/dev-test-lab/lab/artifactsource/README.md index 317b5d0302..4499ea5096 100644 --- a/avm/res/dev-test-lab/lab/artifactsource/README.md +++ b/avm/res/dev-test-lab/lab/artifactsource/README.md @@ -66,7 +66,6 @@ The folder containing Azure Resource Manager templates. Required if "folderPath" - Required: No - Type: string -- Default: `''` ### Parameter: `folderPath` @@ -74,7 +73,6 @@ The folder containing artifacts. At least one folder path is required. Required - Required: No - Type: string -- Default: `''` ### Parameter: `labName` @@ -89,7 +87,6 @@ The artifact source's branch reference (e.g. main or master). - Required: No - Type: string -- Default: `''` ### Parameter: `displayName` @@ -105,7 +102,6 @@ The security token to authenticate to the artifact source. - Required: No - Type: securestring -- Default: `''` ### Parameter: `sourceType` @@ -113,11 +109,9 @@ The artifact source's type. - Required: No - Type: string -- Default: `''` - Allowed: ```Bicep [ - '' 'GitHub' 'StorageAccount' 'VsoGit' diff --git a/avm/res/dev-test-lab/lab/artifactsource/main.bicep b/avm/res/dev-test-lab/lab/artifactsource/main.bicep index b350884783..cf9fecf54b 100644 --- a/avm/res/dev-test-lab/lab/artifactsource/main.bicep +++ b/avm/res/dev-test-lab/lab/artifactsource/main.bicep @@ -17,26 +17,25 @@ param tags object? param displayName string = name @sys.description('Optional. The artifact source\'s branch reference (e.g. main or master).') -param branchRef string = '' +param branchRef string? @sys.description('Conditional. The folder containing artifacts. At least one folder path is required. Required if "armTemplateFolderPath" is empty.') -param folderPath string = '' +param folderPath string? @sys.description('Conditional. The folder containing Azure Resource Manager templates. Required if "folderPath" is empty.') -param armTemplateFolderPath string = '' +param armTemplateFolderPath string? @sys.description('Optional. The security token to authenticate to the artifact source.') @secure() -param securityToken string = '' +param securityToken string? @allowed([ - '' 'GitHub' 'StorageAccount' 'VsoGit' ]) @sys.description('Optional. The artifact source\'s type.') -param sourceType string = '' +param sourceType string? @allowed([ 'Enabled' @@ -58,11 +57,11 @@ resource artifactsource 'Microsoft.DevTestLab/labs/artifactsources@2018-09-15' = tags: tags properties: { displayName: displayName - branchRef: !empty(branchRef) ? branchRef : null - folderPath: !empty(folderPath) ? folderPath : null - armTemplateFolderPath: !empty(armTemplateFolderPath) ? armTemplateFolderPath : null - securityToken: !empty(securityToken) ? securityToken : null - sourceType: !empty(sourceType) ? sourceType : null + branchRef: branchRef + folderPath: folderPath + armTemplateFolderPath: armTemplateFolderPath + securityToken: securityToken + sourceType: sourceType status: status uri: uri } diff --git a/avm/res/dev-test-lab/lab/artifactsource/main.json b/avm/res/dev-test-lab/lab/artifactsource/main.json index cf2720c644..d6c775b85b 100644 --- a/avm/res/dev-test-lab/lab/artifactsource/main.json +++ b/avm/res/dev-test-lab/lab/artifactsource/main.json @@ -6,10 +6,10 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "15552685312013632487" + "templateHash": "13904061272597362111" }, "name": "DevTest Lab Artifact Sources", - "description": "This module deploys a DevTest Lab Artifact Source.\n\nAn artifact source allows you to create custom artifacts for the VMs in the lab, or use Azure Resource Manager templates to create a custom test environment. You must add a private Git repository for the artifacts or Resource Manager templates that your team creates. The repository can be hosted on GitHub or on Azure DevOps Services.", + "description": "This module deploys a DevTest Lab Artifact Source.\r\n\r\nAn artifact source allows you to create custom artifacts for the VMs in the lab, or use Azure Resource Manager templates to create a custom test environment. You must add a private Git repository for the artifacts or Resource Manager templates that your team creates. The repository can be hosted on GitHub or on Azure DevOps Services.", "owner": "Azure/module-maintainers" }, "parameters": { @@ -41,37 +41,36 @@ }, "branchRef": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Optional. The artifact source's branch reference (e.g. main or master)." } }, "folderPath": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Conditional. The folder containing artifacts. At least one folder path is required. Required if \"armTemplateFolderPath\" is empty." } }, "armTemplateFolderPath": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Conditional. The folder containing Azure Resource Manager templates. Required if \"folderPath\" is empty." } }, "securityToken": { "type": "securestring", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Optional. The security token to authenticate to the artifact source." } }, "sourceType": { "type": "string", - "defaultValue": "", + "nullable": true, "allowedValues": [ - "", "GitHub", "StorageAccount", "VsoGit" @@ -112,11 +111,11 @@ "tags": "[parameters('tags')]", "properties": { "displayName": "[parameters('displayName')]", - "branchRef": "[if(not(empty(parameters('branchRef'))), parameters('branchRef'), null())]", - "folderPath": "[if(not(empty(parameters('folderPath'))), parameters('folderPath'), null())]", - "armTemplateFolderPath": "[if(not(empty(parameters('armTemplateFolderPath'))), parameters('armTemplateFolderPath'), null())]", - "securityToken": "[if(not(empty(parameters('securityToken'))), parameters('securityToken'), null())]", - "sourceType": "[if(not(empty(parameters('sourceType'))), parameters('sourceType'), null())]", + "branchRef": "[parameters('branchRef')]", + "folderPath": "[parameters('folderPath')]", + "armTemplateFolderPath": "[parameters('armTemplateFolderPath')]", + "securityToken": "[parameters('securityToken')]", + "sourceType": "[parameters('sourceType')]", "status": "[parameters('status')]", "uri": "[parameters('uri')]" }, diff --git a/avm/res/dev-test-lab/lab/cost/README.md b/avm/res/dev-test-lab/lab/cost/README.md index 084c7e069d..861224b60f 100644 --- a/avm/res/dev-test-lab/lab/cost/README.md +++ b/avm/res/dev-test-lab/lab/cost/README.md @@ -73,7 +73,6 @@ Reporting cycle end date in the zulu time format (e.g. 2023-12-01T00:00:00.000Z) - Required: No - Type: string -- Default: `''` ### Parameter: `cycleStartDateTime` @@ -81,7 +80,6 @@ Reporting cycle start date in the zulu time format (e.g. 2023-12-01T00:00:00.000 - Required: No - Type: string -- Default: `''` ### Parameter: `labName` diff --git a/avm/res/dev-test-lab/lab/cost/main.bicep b/avm/res/dev-test-lab/lab/cost/main.bicep index a380c409b0..0cd9280b88 100644 --- a/avm/res/dev-test-lab/lab/cost/main.bicep +++ b/avm/res/dev-test-lab/lab/cost/main.bicep @@ -18,10 +18,10 @@ param cycleType string param tags object? @sys.description('Conditional. Reporting cycle start date in the zulu time format (e.g. 2023-12-01T00:00:00.000Z). Required if cycleType is set to "Custom".') -param cycleStartDateTime string = '' +param cycleStartDateTime string? @sys.description('Conditional. Reporting cycle end date in the zulu time format (e.g. 2023-12-01T00:00:00.000Z). Required if cycleType is set to "Custom".') -param cycleEndDateTime string = '' +param cycleEndDateTime string? @allowed([ 'Enabled' diff --git a/avm/res/dev-test-lab/lab/cost/main.json b/avm/res/dev-test-lab/lab/cost/main.json index 3b38827bc4..ff19b73a67 100644 --- a/avm/res/dev-test-lab/lab/cost/main.json +++ b/avm/res/dev-test-lab/lab/cost/main.json @@ -6,10 +6,10 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "16348143074487982445" + "templateHash": "7509251296299887127" }, "name": "DevTest Lab Costs", - "description": "This module deploys a DevTest Lab Cost.\n\nManage lab costs by setting a spending target that can be viewed in the Monthly Estimated Cost Trend chart. DevTest Labs can send a notification when spending reaches the specified target threshold.", + "description": "This module deploys a DevTest Lab Cost.\r\n\r\nManage lab costs by setting a spending target that can be viewed in the Monthly Estimated Cost Trend chart. DevTest Labs can send a notification when spending reaches the specified target threshold.", "owner": "Azure/module-maintainers" }, "parameters": { @@ -38,14 +38,14 @@ }, "cycleStartDateTime": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Conditional. Reporting cycle start date in the zulu time format (e.g. 2023-12-01T00:00:00.000Z). Required if cycleType is set to \"Custom\"." } }, "cycleEndDateTime": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Conditional. Reporting cycle end date in the zulu time format (e.g. 2023-12-01T00:00:00.000Z). Required if cycleType is set to \"Custom\"." } diff --git a/avm/res/dev-test-lab/lab/main.bicep b/avm/res/dev-test-lab/lab/main.bicep index 15db191482..94da257c7b 100644 --- a/avm/res/dev-test-lab/lab/main.bicep +++ b/avm/res/dev-test-lab/lab/main.bicep @@ -94,22 +94,22 @@ param encryptionType string = 'EncryptionAtRestWithPlatformKey' param encryptionDiskEncryptionSetId string = '' @description('Optional. Virtual networks to create for the lab.') -param virtualnetworks array = [] +param virtualnetworks virtualNetworkType @description('Optional. Policies to create for the lab.') -param policies array = [] +param policies policiesType @description('Optional. Schedules to create for the lab.') -param schedules array = [] +param schedules scheduleType @description('Conditional. Notification Channels to create for the lab. Required if the schedules property "notificationSettingsStatus" is set to "Enabled.') -param notificationchannels array = [] +param notificationchannels notificationChannelType @description('Optional. Artifact sources to create for the lab.') -param artifactsources array = [] +param artifactsources artifactsourcesType @description('Optional. Costs to create for the lab.') -param costs object = {} +param costs costsType @description('Optional. Enable/Disable usage telemetry for module.') param enableTelemetry bool = true @@ -226,95 +226,86 @@ resource lab_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ? } module lab_virtualNetworks 'virtualnetwork/main.bicep' = [ - for (virtualNetwork, index) in virtualnetworks: { + for (virtualNetwork, index) in (virtualnetworks ?? []): { name: '${uniqueString(deployment().name, location)}-Lab-VirtualNetwork-${index}' params: { labName: lab.name name: virtualNetwork.name tags: virtualNetwork.?tags ?? tags externalProviderResourceId: virtualNetwork.externalProviderResourceId - description: contains(virtualNetwork, 'description') ? virtualNetwork.description : '' - allowedSubnets: contains(virtualNetwork, 'allowedSubnets') ? virtualNetwork.allowedSubnets : [] - subnetOverrides: contains(virtualNetwork, 'subnetOverrides') ? virtualNetwork.subnetOverrides : [] + description: virtualNetwork.?description + allowedSubnets: virtualNetwork.?allowedSubnets + subnetOverrides: virtualNetwork.?subnetOverrides } } ] module lab_policies 'policyset/policy/main.bicep' = [ - for (policy, index) in policies: { + for (policy, index) in (policies ?? []): { name: '${uniqueString(deployment().name, location)}-Lab-PolicySets-Policy-${index}' params: { labName: lab.name name: policy.name - - description: contains(policy, 'description') ? policy.description : '' + description: policy.?description evaluatorType: policy.evaluatorType - factData: contains(policy, 'factData') ? policy.factData : '' + factData: policy.?factData factName: policy.factName - status: contains(policy, 'status') ? policy.status : 'Enabled' + status: policy.?status ?? 'Enabled' threshold: policy.threshold } } ] module lab_schedules 'schedule/main.bicep' = [ - for (schedule, index) in schedules: { + for (schedule, index) in (schedules ?? []): { name: '${uniqueString(deployment().name, location)}-Lab-Schedules-${index}' params: { labName: lab.name name: schedule.name tags: schedule.?tags ?? tags taskType: schedule.taskType - dailyRecurrence: contains(schedule, 'dailyRecurrence') ? schedule.dailyRecurrence : {} - hourlyRecurrence: contains(schedule, 'hourlyRecurrence') ? schedule.hourlyRecurrence : {} - weeklyRecurrence: contains(schedule, 'weeklyRecurrence') ? schedule.weeklyRecurrence : {} - status: contains(schedule, 'status') ? schedule.status : 'Enabled' - targetResourceId: contains(schedule, 'targetResourceId') ? schedule.targetResourceId : '' - timeZoneId: contains(schedule, 'timeZoneId') ? schedule.timeZoneId : 'Pacific Standard time' - notificationSettingsStatus: contains(schedule, 'notificationSettingsStatus') - ? schedule.notificationSettingsStatus - : 'Disabled' - notificationSettingsTimeInMinutes: contains(schedule, 'notificationSettingsTimeInMinutes') - ? schedule.notificationSettingsTimeInMinutes - : 30 + dailyRecurrence: schedule.?dailyRecurrence + hourlyRecurrence: schedule.?hourlyRecurrence + weeklyRecurrence: schedule.?weeklyRecurrence + status: schedule.?status ?? 'Enabled' + targetResourceId: schedule.?targetResourceId + timeZoneId: schedule.?timeZoneId ?? 'Pacific Standard time' + notificationSettings: schedule.?notificationSettings } } ] module lab_notificationChannels 'notificationchannel/main.bicep' = [ - for (notificationChannel, index) in notificationchannels: { + for (notificationChannel, index) in (notificationchannels ?? []): { name: '${uniqueString(deployment().name, location)}-Lab-NotificationChannels-${index}' params: { labName: lab.name name: notificationChannel.name tags: notificationChannel.?tags ?? tags - description: contains(notificationChannel, 'description') ? notificationChannel.description : '' + description: notificationChannel.?description events: notificationChannel.events - emailRecipient: contains(notificationChannel, 'emailRecipient') ? notificationChannel.emailRecipient : '' - webHookUrl: contains(notificationChannel, 'webhookUrl') ? notificationChannel.webhookUrl : '' - notificationLocale: contains(notificationChannel, 'notificationLocale') - ? notificationChannel.notificationLocale - : 'en' + emailRecipient: notificationChannel.?emailRecipient + webHookUrl: notificationChannel.?webHookUrl + notificationLocale: notificationChannel.?notificationLocale ?? 'en' } } ] module lab_artifactSources 'artifactsource/main.bicep' = [ - for (artifactSource, index) in artifactsources: { + for (artifactSource, index) in (artifactsources ?? []): { name: '${uniqueString(deployment().name, location)}-Lab-ArtifactSources-${index}' params: { labName: lab.name name: artifactSource.name tags: artifactSource.?tags ?? tags - displayName: contains(artifactSource, 'displayName') ? artifactSource.displayName : artifactSource.name - branchRef: contains(artifactSource, 'branchRef') ? artifactSource.branchRef : '' - folderPath: contains(artifactSource, 'folderPath') ? artifactSource.folderPath : '' - armTemplateFolderPath: contains(artifactSource, 'armTemplateFolderPath') - ? artifactSource.armTemplateFolderPath - : '' - sourceType: contains(artifactSource, 'sourceType') ? artifactSource.sourceType : '' - status: contains(artifactSource, 'status') ? artifactSource.status : 'Enabled' + displayName: artifactSource.?displayName ?? artifactSource.name + branchRef: artifactSource.?branchRef + folderPath: artifactSource.?folderPath + armTemplateFolderPath: artifactSource.?armTemplateFolderPath + sourceType: artifactSource.?sourceType + status: artifactSource.?status ?? 'Enabled' uri: artifactSource.uri + securityToken: artifactSource.?securityToken } } ] @@ -324,42 +315,22 @@ module lab_costs 'cost/main.bicep' = if (!empty(costs)) { params: { labName: lab.name tags: costs.?tags ?? tags - currencyCode: contains(costs, 'currencyCode') ? costs.currencyCode : 'USD' - cycleType: costs.cycleType - cycleStartDateTime: contains(costs, 'cycleStartDateTime') ? costs.cycleStartDateTime : '' - cycleEndDateTime: contains(costs, 'cycleEndDateTime') ? costs.cycleEndDateTime : '' - status: contains(costs, 'status') ? costs.status : 'Enabled' - target: contains(costs, 'target') ? costs.target : 0 - thresholdValue25DisplayOnChart: contains(costs, 'thresholdValue25DisplayOnChart') - ? costs.thresholdValue25DisplayOnChart - : 'Disabled' - thresholdValue25SendNotificationWhenExceeded: contains(costs, 'thresholdValue25SendNotificationWhenExceeded') - ? costs.thresholdValue25SendNotificationWhenExceeded - : 'Disabled' - thresholdValue50DisplayOnChart: contains(costs, 'thresholdValue50DisplayOnChart') - ? costs.thresholdValue50DisplayOnChart - : 'Disabled' - thresholdValue50SendNotificationWhenExceeded: contains(costs, 'thresholdValue50SendNotificationWhenExceeded') - ? costs.thresholdValue50SendNotificationWhenExceeded - : 'Disabled' - thresholdValue75DisplayOnChart: contains(costs, 'thresholdValue75DisplayOnChart') - ? costs.thresholdValue75DisplayOnChart - : 'Disabled' - thresholdValue75SendNotificationWhenExceeded: contains(costs, 'thresholdValue75SendNotificationWhenExceeded') - ? costs.thresholdValue75SendNotificationWhenExceeded - : 'Disabled' - thresholdValue100DisplayOnChart: contains(costs, 'thresholdValue100DisplayOnChart') - ? costs.thresholdValue100DisplayOnChart - : 'Disabled' - thresholdValue100SendNotificationWhenExceeded: contains(costs, 'thresholdValue100SendNotificationWhenExceeded') - ? costs.thresholdValue100SendNotificationWhenExceeded - : 'Disabled' - thresholdValue125DisplayOnChart: contains(costs, 'thresholdValue125DisplayOnChart') - ? costs.thresholdValue125DisplayOnChart - : 'Disabled' - thresholdValue125SendNotificationWhenExceeded: contains(costs, 'thresholdValue125SendNotificationWhenExceeded') - ? costs.thresholdValue125SendNotificationWhenExceeded - : 'Disabled' + currencyCode: costs.?currencyCode ?? 'USD' + cycleType: costs!.cycleType + cycleStartDateTime: costs.?cycleStartDateTime + cycleEndDateTime: costs.?cycleEndDateTime + status: costs.?status ?? 'Enabled' + target: costs.?target ?? 0 + thresholdValue25DisplayOnChart: costs.?thresholdValue25DisplayOnChart ?? 'Disabled' + thresholdValue25SendNotificationWhenExceeded: costs.?thresholdValue25SendNotificationWhenExceeded ?? 'Disabled' + thresholdValue50DisplayOnChart: costs.?thresholdValue50DisplayOnChart ?? 'Disabled' + thresholdValue50SendNotificationWhenExceeded: costs.?thresholdValue50SendNotificationWhenExceeded ?? 'Disabled' + thresholdValue75DisplayOnChart: costs.?thresholdValue75DisplayOnChart ?? 'Disabled' + thresholdValue75SendNotificationWhenExceeded: costs.?thresholdValue75SendNotificationWhenExceeded ?? 'Disabled' + thresholdValue100DisplayOnChart: costs.?thresholdValue100DisplayOnChart ?? 'Disabled' + thresholdValue100SendNotificationWhenExceeded: costs.?thresholdValue100SendNotificationWhenExceeded ?? 'Disabled' + thresholdValue125DisplayOnChart: costs.?thresholdValue125DisplayOnChart ?? 'Disabled' + thresholdValue125SendNotificationWhenExceeded: costs.?thresholdValue125SendNotificationWhenExceeded ?? 'Disabled' } } @@ -439,3 +410,199 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type artifactsourcesType = { + @description('Required. The name of the artifact source.') + name: string + + @description('Optional. The tags of the artifact source.') + tags: object? + + @description('Optional. The display name of the artifact source. Default is the name of the artifact source.') + displayName: string? + + @description('Optional. The artifact source\'s branch reference (e.g. main or master).') + branchRef: string? + + @description('Conditional. The folder containing artifacts. At least one folder path is required. Required if "armTemplateFolderPath" is empty.') + folderPath: string? + + @description('Conditional. The folder containing Azure Resource Manager templates. Required if "folderPath" is empty.') + armTemplateFolderPath: string? + + @description('Optional. The artifact source\'s type.') + sourceType: 'GitHub' | 'StorageAccount' | 'VsoGit'? + + @description('Optional. Indicates if the artifact source is enabled (values: Enabled, Disabled). Default is "Enabled".') + status: 'Enabled' | 'Disabled'? + + @description('Required. The artifact source\'s URI.') + uri: string + + @description('Optional. The security token to authenticate to the artifact source.') + @secure() + securityToken: string? +}[]? + +import { allowedSubnetType, subnetOverrideType } from 'virtualnetwork/main.bicep' +type virtualNetworkType = { + @description('Required. The name of the virtual network.') + name: string + + @description('Optional. The tags of the virtual network.') + tags: object? + + @description('Required. The external provider resource ID of the virtual network.') + externalProviderResourceId: string + + @description('Optional. The description of the virtual network.') + description: string? + + @description('Optional. The allowed subnets of the virtual network.') + allowedSubnets: allowedSubnetType[]? + + @description('Optional. The subnet overrides of the virtual network.') + subnetOverrides: subnetOverrideType[]? +}[]? + +type costsType = { + @description('Optional. The tags of the resource.') + tags: object? + + @description('Required. Reporting cycle type.') + cycleType: 'Custom' | 'CalendarMonth' + + @description('Conditional. Reporting cycle start date in the zulu time format (e.g. 2023-12-01T00:00:00.000Z). Required if cycleType is set to "Custom".') + cycleStartDateTime: string? + + @description('Conditional. Reporting cycle end date in the zulu time format (e.g. 2023-12-01T00:00:00.000Z). Required if cycleType is set to "Custom".') + cycleEndDateTime: string? + + @description('Optional. Target cost status.') + status: 'Enabled' | 'Disabled'? + + @description('Optional. Lab target cost (e.g. 100). The target cost will appear in the "Cost trend" chart to allow tracking lab spending relative to the target cost for the current reporting cycleSetting the target cost to 0 will disable all thresholds.') + target: int? + + @description('Optional. The currency code of the cost. Default is "USD".') + currencyCode: string? + + @description('Optional. Target Cost threshold at 25% display on chart. Indicates whether this threshold will be displayed on cost charts.') + thresholdValue25DisplayOnChart: 'Enabled' | 'Disabled'? + + @description('Optional. Target cost threshold at 25% send notification when exceeded. Indicates whether notifications will be sent when this threshold is exceeded.') + thresholdValue25SendNotificationWhenExceeded: 'Enabled' | 'Disabled'? + + @description('Optional. Target Cost threshold at 50% display on chart. Indicates whether this threshold will be displayed on cost charts.') + thresholdValue50DisplayOnChart: 'Enabled' | 'Disabled'? + + @description('Optional. Target cost threshold at 50% send notification when exceeded. Indicates whether notifications will be sent when this threshold is exceeded.') + thresholdValue50SendNotificationWhenExceeded: 'Enabled' | 'Disabled'? + + @description('Optional. Target Cost threshold at 75% display on chart. Indicates whether this threshold will be displayed on cost charts.') + thresholdValue75DisplayOnChart: 'Enabled' | 'Disabled'? + + @description('Optional. Target cost threshold at 75% send notification when exceeded. Indicates whether notifications will be sent when this threshold is exceeded.') + thresholdValue75SendNotificationWhenExceeded: 'Enabled' | 'Disabled'? + + @description('Optional. Target Cost threshold at 100% display on chart. Indicates whether this threshold will be displayed on cost charts.') + thresholdValue100DisplayOnChart: 'Enabled' | 'Disabled'? + + @description('Optional. Target cost threshold at 100% send notification when exceeded. Indicates whether notifications will be sent when this threshold is exceeded.') + thresholdValue100SendNotificationWhenExceeded: 'Enabled' | 'Disabled'? + + @description('Optional. Target Cost threshold at 125% display on chart. Indicates whether this threshold will be displayed on cost charts.') + thresholdValue125DisplayOnChart: 'Enabled' | 'Disabled'? + + @description('Optional. Target cost threshold at 125% send notification when exceeded. Indicates whether notifications will be sent when this threshold is exceeded.') + thresholdValue125SendNotificationWhenExceeded: 'Enabled' | 'Disabled'? +}? + +type notificationChannelType = { + @description('Required. The name of the notification channel.') + name: 'autoShutdown' | 'costThreshold' + + @description('Optional. The tags of the notification channel.') + tags: object? + + @description('Optional. The description of the notification.') + description: string? + + @description('Required. The list of event for which this notification is enabled. Can be "AutoShutdown" or "Cost".') + events: string[] + + @description('Conditional. The email recipient to send notifications to (can be a list of semi-colon separated email addresses). Required if "webHookUrl" is empty.') + emailRecipient: string? + + @description('Conditional. The webhook URL to which the notification will be sent. Required if "emailRecipient" is empty.') + webHookUrl: string? + + @description('Optional. The locale to use when sending a notification (fallback for unsupported languages is EN).') + notificationLocale: string? +}[]? + +type policiesType = { + @description('Required. The name of the policy.') + name: string + + @description('Optional. The description of the policy.') + description: string? + + @description('Required. The evaluator type of the policy (i.e. AllowedValuesPolicy, MaxValuePolicy).') + evaluatorType: 'AllowedValuesPolicy' | 'MaxValuePolicy' + + @description('Optional. The fact data of the policy.') + factData: string? + + @description('Required. The fact name of the policy.') + factName: + | 'EnvironmentTemplate' + | 'GalleryImage' + | 'LabPremiumVmCount' + | 'LabTargetCost' + | 'LabVmCount' + | 'LabVmSize' + | 'ScheduleEditPermission' + | 'UserOwnedLabPremiumVmCount' + | 'UserOwnedLabVmCount' + | 'UserOwnedLabVmCountInSubnet' + + @description('Optional. The status of the policy. Default is "Enabled".') + status: 'Disabled' | 'Enabled'? + + @description('Required. The threshold of the policy (i.e. a number for MaxValuePolicy, and a JSON array of values for AllowedValuesPolicy).') + threshold: string +}[]? + +import { dailyRecurrenceType, hourlyRecurrenceType, notificationSettingsType, weeklyRecurrenceType } from 'schedule/main.bicep' +type scheduleType = { + @description('Required. The name of the schedule.') + name: 'LabVmsShutdown' | 'LabVmAutoStart' + + @description('Optional. The tags of the schedule.') + tags: object? + + @description('Required. The task type of the schedule (e.g. LabVmsShutdownTask, LabVmsStartupTask).') + taskType: 'LabVmsShutdownTask' | 'LabVmsStartupTask' + + @description('Optional. The daily recurrence of the schedule.') + dailyRecurrence: dailyRecurrenceType? + + @description('Optional. If the schedule will occur multiple times a day, specify the hourly recurrence.') + hourlyRecurrence: hourlyRecurrenceType? + + @description('Optional. If the schedule will occur only some days of the week, specify the weekly recurrence.') + weeklyRecurrence: weeklyRecurrenceType? + + @description('Optional. The status of the schedule (i.e. Enabled, Disabled). Default is "Enabled".') + status: 'Disabled' | 'Enabled'? + + @description('Optional. The resource ID to which the schedule belongs.') + targetResourceId: string? + + @description('Optional. The time zone ID of the schedule. Defaults to "Pacific Standard time".') + timeZoneId: string? + + @description('Optional. The notification settings for the schedule.') + notificationSettings: notificationSettingsType? +}[]? diff --git a/avm/res/dev-test-lab/lab/main.json b/avm/res/dev-test-lab/lab/main.json index e43e8d6a81..6669059b2e 100644 --- a/avm/res/dev-test-lab/lab/main.json +++ b/avm/res/dev-test-lab/lab/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "18263140101339219429" + "templateHash": "17290806626721732157" }, "name": "DevTest Labs", "description": "This module deploys a DevTest Lab.", @@ -125,6 +125,777 @@ } }, "nullable": true + }, + "artifactsourcesType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the artifact source." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The tags of the artifact source." + } + }, + "displayName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The display name of the artifact source. Default is the name of the artifact source." + } + }, + "branchRef": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The artifact source's branch reference (e.g. main or master)." + } + }, + "folderPath": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The folder containing artifacts. At least one folder path is required. Required if \"armTemplateFolderPath\" is empty." + } + }, + "armTemplateFolderPath": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The folder containing Azure Resource Manager templates. Required if \"folderPath\" is empty." + } + }, + "sourceType": { + "type": "string", + "allowedValues": [ + "GitHub", + "StorageAccount", + "VsoGit" + ], + "nullable": true, + "metadata": { + "description": "Optional. The artifact source's type." + } + }, + "status": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. Indicates if the artifact source is enabled (values: Enabled, Disabled). Default is \"Enabled\"." + } + }, + "uri": { + "type": "string", + "metadata": { + "description": "Required. The artifact source's URI." + } + }, + "securityToken": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Optional. The security token to authenticate to the artifact source." + } + } + } + }, + "nullable": true + }, + "virtualNetworkType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual network." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The tags of the virtual network." + } + }, + "externalProviderResourceId": { + "type": "string", + "metadata": { + "description": "Required. The external provider resource ID of the virtual network." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the virtual network." + } + }, + "allowedSubnets": { + "type": "array", + "items": { + "$ref": "#/definitions/allowedSubnetType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The allowed subnets of the virtual network." + } + }, + "subnetOverrides": { + "type": "array", + "items": { + "$ref": "#/definitions/subnetOverrideType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The subnet overrides of the virtual network." + } + } + } + }, + "nullable": true + }, + "costsType": { + "type": "object", + "properties": { + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The tags of the resource." + } + }, + "cycleType": { + "type": "string", + "allowedValues": [ + "CalendarMonth", + "Custom" + ], + "metadata": { + "description": "Required. Reporting cycle type." + } + }, + "cycleStartDateTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. Reporting cycle start date in the zulu time format (e.g. 2023-12-01T00:00:00.000Z). Required if cycleType is set to \"Custom\"." + } + }, + "cycleEndDateTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. Reporting cycle end date in the zulu time format (e.g. 2023-12-01T00:00:00.000Z). Required if cycleType is set to \"Custom\"." + } + }, + "status": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. Target cost status." + } + }, + "target": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Lab target cost (e.g. 100). The target cost will appear in the \"Cost trend\" chart to allow tracking lab spending relative to the target cost for the current reporting cycleSetting the target cost to 0 will disable all thresholds." + } + }, + "currencyCode": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The currency code of the cost. Default is \"USD\"." + } + }, + "thresholdValue25DisplayOnChart": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. Target Cost threshold at 25% display on chart. Indicates whether this threshold will be displayed on cost charts." + } + }, + "thresholdValue25SendNotificationWhenExceeded": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. Target cost threshold at 25% send notification when exceeded. Indicates whether notifications will be sent when this threshold is exceeded." + } + }, + "thresholdValue50DisplayOnChart": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. Target Cost threshold at 50% display on chart. Indicates whether this threshold will be displayed on cost charts." + } + }, + "thresholdValue50SendNotificationWhenExceeded": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. Target cost threshold at 50% send notification when exceeded. Indicates whether notifications will be sent when this threshold is exceeded." + } + }, + "thresholdValue75DisplayOnChart": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. Target Cost threshold at 75% display on chart. Indicates whether this threshold will be displayed on cost charts." + } + }, + "thresholdValue75SendNotificationWhenExceeded": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. Target cost threshold at 75% send notification when exceeded. Indicates whether notifications will be sent when this threshold is exceeded." + } + }, + "thresholdValue100DisplayOnChart": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. Target Cost threshold at 100% display on chart. Indicates whether this threshold will be displayed on cost charts." + } + }, + "thresholdValue100SendNotificationWhenExceeded": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. Target cost threshold at 100% send notification when exceeded. Indicates whether notifications will be sent when this threshold is exceeded." + } + }, + "thresholdValue125DisplayOnChart": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. Target Cost threshold at 125% display on chart. Indicates whether this threshold will be displayed on cost charts." + } + }, + "thresholdValue125SendNotificationWhenExceeded": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. Target cost threshold at 125% send notification when exceeded. Indicates whether notifications will be sent when this threshold is exceeded." + } + } + }, + "nullable": true + }, + "notificationChannelType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "allowedValues": [ + "autoShutdown", + "costThreshold" + ], + "metadata": { + "description": "Required. The name of the notification channel." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The tags of the notification channel." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the notification." + } + }, + "events": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The list of event for which this notification is enabled. Can be \"AutoShutdown\" or \"Cost\"." + } + }, + "emailRecipient": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The email recipient to send notifications to (can be a list of semi-colon separated email addresses). Required if \"webHookUrl\" is empty." + } + }, + "webHookUrl": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The webhook URL to which the notification will be sent. Required if \"emailRecipient\" is empty." + } + }, + "notificationLocale": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The locale to use when sending a notification (fallback for unsupported languages is EN)." + } + } + } + }, + "nullable": true + }, + "policiesType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the policy." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the policy." + } + }, + "evaluatorType": { + "type": "string", + "allowedValues": [ + "AllowedValuesPolicy", + "MaxValuePolicy" + ], + "metadata": { + "description": "Required. The evaluator type of the policy (i.e. AllowedValuesPolicy, MaxValuePolicy)." + } + }, + "factData": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The fact data of the policy." + } + }, + "factName": { + "type": "string", + "allowedValues": [ + "EnvironmentTemplate", + "GalleryImage", + "LabPremiumVmCount", + "LabTargetCost", + "LabVmCount", + "LabVmSize", + "ScheduleEditPermission", + "UserOwnedLabPremiumVmCount", + "UserOwnedLabVmCount", + "UserOwnedLabVmCountInSubnet" + ], + "metadata": { + "description": "Required. The fact name of the policy." + } + }, + "status": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. The status of the policy. Default is \"Enabled\"." + } + }, + "threshold": { + "type": "string", + "metadata": { + "description": "Required. The threshold of the policy (i.e. a number for MaxValuePolicy, and a JSON array of values for AllowedValuesPolicy)." + } + } + } + }, + "nullable": true + }, + "scheduleType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "allowedValues": [ + "LabVmAutoStart", + "LabVmsShutdown" + ], + "metadata": { + "description": "Required. The name of the schedule." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The tags of the schedule." + } + }, + "taskType": { + "type": "string", + "allowedValues": [ + "LabVmsShutdownTask", + "LabVmsStartupTask" + ], + "metadata": { + "description": "Required. The task type of the schedule (e.g. LabVmsShutdownTask, LabVmsStartupTask)." + } + }, + "dailyRecurrence": { + "$ref": "#/definitions/dailyRecurrenceType", + "nullable": true, + "metadata": { + "description": "Optional. The daily recurrence of the schedule." + } + }, + "hourlyRecurrence": { + "$ref": "#/definitions/hourlyRecurrenceType", + "nullable": true, + "metadata": { + "description": "Optional. If the schedule will occur multiple times a day, specify the hourly recurrence." + } + }, + "weeklyRecurrence": { + "$ref": "#/definitions/weeklyRecurrenceType", + "nullable": true, + "metadata": { + "description": "Optional. If the schedule will occur only some days of the week, specify the weekly recurrence." + } + }, + "status": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. The status of the schedule (i.e. Enabled, Disabled). Default is \"Enabled\"." + } + }, + "targetResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID to which the schedule belongs." + } + }, + "timeZoneId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The time zone ID of the schedule. Defaults to \"Pacific Standard time\"." + } + }, + "notificationSettings": { + "$ref": "#/definitions/notificationSettingsType", + "nullable": true, + "metadata": { + "description": "Optional. The notification settings for the schedule." + } + } + } + }, + "nullable": true + }, + "allowedSubnetType": { + "type": "object", + "properties": { + "allowPublicIp": { + "type": "string", + "allowedValues": [ + "Allow", + "Default", + "Deny" + ], + "nullable": true, + "metadata": { + "description": "Optional. The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny))." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the allowed subnet." + } + }, + "labSubnetName": { + "type": "string", + "metadata": { + "description": "Required. The name of the subnet as seen in the lab." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "virtualnetwork/main.bicep" + } + } + }, + "dailyRecurrenceType": { + "type": "object", + "properties": { + "time": { + "type": "string", + "metadata": { + "description": "Required. The time of day the schedule will occur." + } + } + }, + "nullable": true, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "schedule/main.bicep" + } + } + }, + "hourlyRecurrenceType": { + "type": "object", + "properties": { + "minute": { + "type": "int", + "metadata": { + "description": "Required. Minutes of the hour the schedule will run." + } + } + }, + "nullable": true, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "schedule/main.bicep" + } + } + }, + "notificationSettingsType": { + "type": "object", + "properties": { + "emailRecipient": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The email recipient to send notifications to (can be a list of semi-colon separated email addresses). Required if \"webHookUrl\" is empty." + } + }, + "notificationLocale": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The locale to use when sending a notification (fallback for unsupported languages is EN)." + } + }, + "status": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. If notifications are enabled for this schedule (i.e. Enabled, Disabled). Default is Disabled." + } + }, + "timeInMinutes": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Time in minutes before event at which notification will be sent. Default is 30 minutes if status is Enabled and not specified." + } + }, + "webHookUrl": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The webhook URL to which the notification will be sent. Required if \"emailRecipient\" is empty." + } + } + }, + "nullable": true, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "schedule/main.bicep" + } + } + }, + "subnetOverrideType": { + "type": "object", + "properties": { + "labSubnetName": { + "type": "string", + "metadata": { + "description": "Required. The name given to the subnet within the lab." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the subnet." + } + }, + "sharedPublicIpAddressConfiguration": { + "type": "object", + "properties": { + "allowedPorts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "backendPort": { + "type": "int", + "metadata": { + "description": "Required. Backend port of the target virtual machine." + } + }, + "transportProtocol": { + "type": "string", + "allowedValues": [ + "Tcp", + "Udp" + ], + "metadata": { + "description": "Required. Protocol type of the port." + } + } + } + }, + "metadata": { + "description": "Required. Backend ports that virtual machines on this subnet are allowed to expose." + } + } + }, + "metadata": { + "description": "Optional. The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny))." + } + }, + "useInVmCreationPermission": { + "type": "string", + "allowedValues": [ + "Allow", + "Default", + "Deny" + ], + "nullable": true, + "metadata": { + "description": "Optional. Indicates whether this subnet can be used during virtual machine creation (i.e. Allow, Deny)." + } + }, + "usePublicIpAddressPermission": { + "type": "string", + "allowedValues": [ + "Allow", + "Default", + "Deny" + ], + "nullable": true, + "metadata": { + "description": "Optional. Indicates whether public IP addresses can be assigned to virtual machines on this subnet (i.e. Allow, Deny)." + } + }, + "virtualNetworkPoolName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The virtual network pool associated with this subnet." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "virtualnetwork/main.bicep" + } + } + }, + "weeklyRecurrenceType": { + "type": "object", + "properties": { + "time": { + "type": "string", + "metadata": { + "description": "Required. The time of day the schedule will occur." + } + }, + "weekdays": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The days of the week for which the schedule is set (e.g. Sunday, Monday, Tuesday, etc.)." + } + } + }, + "nullable": true, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "schedule/main.bicep" + } + } } }, "parameters": { @@ -307,43 +1078,37 @@ } }, "virtualnetworks": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/virtualNetworkType", "metadata": { "description": "Optional. Virtual networks to create for the lab." } }, "policies": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/policiesType", "metadata": { "description": "Optional. Policies to create for the lab." } }, "schedules": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/scheduleType", "metadata": { "description": "Optional. Schedules to create for the lab." } }, "notificationchannels": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/notificationChannelType", "metadata": { "description": "Conditional. Notification Channels to create for the lab. Required if the schedules property \"notificationSettingsStatus\" is set to \"Enabled." } }, "artifactsources": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/artifactsourcesType", "metadata": { "description": "Optional. Artifact sources to create for the lab." } }, "costs": { - "type": "object", - "defaultValue": {}, + "$ref": "#/definitions/costsType", "metadata": { "description": "Optional. Costs to create for the lab." } @@ -466,7 +1231,7 @@ "lab_virtualNetworks": { "copy": { "name": "lab_virtualNetworks", - "count": "[length(parameters('virtualnetworks'))]" + "count": "[length(coalesce(parameters('virtualnetworks'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", @@ -481,17 +1246,23 @@ "value": "[parameters('name')]" }, "name": { - "value": "[parameters('virtualnetworks')[copyIndex()].name]" + "value": "[coalesce(parameters('virtualnetworks'), createArray())[copyIndex()].name]" }, "tags": { - "value": "[coalesce(tryGet(parameters('virtualnetworks')[copyIndex()], 'tags'), parameters('tags'))]" + "value": "[coalesce(tryGet(coalesce(parameters('virtualnetworks'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" }, "externalProviderResourceId": { - "value": "[parameters('virtualnetworks')[copyIndex()].externalProviderResourceId]" + "value": "[coalesce(parameters('virtualnetworks'), createArray())[copyIndex()].externalProviderResourceId]" + }, + "description": { + "value": "[tryGet(coalesce(parameters('virtualnetworks'), createArray())[copyIndex()], 'description')]" + }, + "allowedSubnets": { + "value": "[tryGet(coalesce(parameters('virtualnetworks'), createArray())[copyIndex()], 'allowedSubnets')]" }, - "description": "[if(contains(parameters('virtualnetworks')[copyIndex()], 'description'), createObject('value', parameters('virtualnetworks')[copyIndex()].description), createObject('value', ''))]", - "allowedSubnets": "[if(contains(parameters('virtualnetworks')[copyIndex()], 'allowedSubnets'), createObject('value', parameters('virtualnetworks')[copyIndex()].allowedSubnets), createObject('value', createArray()))]", - "subnetOverrides": "[if(contains(parameters('virtualnetworks')[copyIndex()], 'subnetOverrides'), createObject('value', parameters('virtualnetworks')[copyIndex()].subnetOverrides), createObject('value', createArray()))]" + "subnetOverrides": { + "value": "[tryGet(coalesce(parameters('virtualnetworks'), createArray())[copyIndex()], 'subnetOverrides')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -501,12 +1272,132 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "1030641179919111475" + "templateHash": "18309816581107210302" }, "name": "DevTest Lab Virtual Networks", - "description": "This module deploys a DevTest Lab Virtual Network.\n\nLab virtual machines must be deployed into a virtual network. This resource type allows configuring the virtual network and subnet settings used for the lab virtual machines.", + "description": "This module deploys a DevTest Lab Virtual Network.\r\n\r\nLab virtual machines must be deployed into a virtual network. This resource type allows configuring the virtual network and subnet settings used for the lab virtual machines.", "owner": "Azure/module-maintainers" }, + "definitions": { + "allowedSubnetType": { + "type": "object", + "properties": { + "allowPublicIp": { + "type": "string", + "allowedValues": [ + "Allow", + "Default", + "Deny" + ], + "nullable": true, + "metadata": { + "description": "Optional. The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny))." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the allowed subnet." + } + }, + "labSubnetName": { + "type": "string", + "metadata": { + "description": "Required. The name of the subnet as seen in the lab." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "subnetOverrideType": { + "type": "object", + "properties": { + "labSubnetName": { + "type": "string", + "metadata": { + "description": "Required. The name given to the subnet within the lab." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the subnet." + } + }, + "sharedPublicIpAddressConfiguration": { + "type": "object", + "properties": { + "allowedPorts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "backendPort": { + "type": "int", + "metadata": { + "description": "Required. Backend port of the target virtual machine." + } + }, + "transportProtocol": { + "type": "string", + "allowedValues": [ + "Tcp", + "Udp" + ], + "metadata": { + "description": "Required. Protocol type of the port." + } + } + } + }, + "metadata": { + "description": "Required. Backend ports that virtual machines on this subnet are allowed to expose." + } + } + }, + "metadata": { + "description": "Optional. The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny))." + } + }, + "useInVmCreationPermission": { + "type": "string", + "allowedValues": [ + "Allow", + "Default", + "Deny" + ], + "nullable": true, + "metadata": { + "description": "Optional. Indicates whether this subnet can be used during virtual machine creation (i.e. Allow, Deny)." + } + }, + "usePublicIpAddressPermission": { + "type": "string", + "allowedValues": [ + "Allow", + "Default", + "Deny" + ], + "nullable": true, + "metadata": { + "description": "Optional. Indicates whether public IP addresses can be assigned to virtual machines on this subnet (i.e. Allow, Deny)." + } + }, + "virtualNetworkPoolName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The virtual network pool associated with this subnet." + } + } + }, + "metadata": { + "__bicep_export!": true + } + } + }, "parameters": { "labName": { "type": "string", @@ -610,7 +1501,7 @@ "lab_policies": { "copy": { "name": "lab_policies", - "count": "[length(parameters('policies'))]" + "count": "[length(coalesce(parameters('policies'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", @@ -625,19 +1516,25 @@ "value": "[parameters('name')]" }, "name": { - "value": "[parameters('policies')[copyIndex()].name]" + "value": "[coalesce(parameters('policies'), createArray())[copyIndex()].name]" + }, + "description": { + "value": "[tryGet(coalesce(parameters('policies'), createArray())[copyIndex()], 'description')]" }, - "description": "[if(contains(parameters('policies')[copyIndex()], 'description'), createObject('value', parameters('policies')[copyIndex()].description), createObject('value', ''))]", "evaluatorType": { - "value": "[parameters('policies')[copyIndex()].evaluatorType]" + "value": "[coalesce(parameters('policies'), createArray())[copyIndex()].evaluatorType]" + }, + "factData": { + "value": "[tryGet(coalesce(parameters('policies'), createArray())[copyIndex()], 'factData')]" }, - "factData": "[if(contains(parameters('policies')[copyIndex()], 'factData'), createObject('value', parameters('policies')[copyIndex()].factData), createObject('value', ''))]", "factName": { - "value": "[parameters('policies')[copyIndex()].factName]" + "value": "[coalesce(parameters('policies'), createArray())[copyIndex()].factName]" + }, + "status": { + "value": "[coalesce(tryGet(coalesce(parameters('policies'), createArray())[copyIndex()], 'status'), 'Enabled')]" }, - "status": "[if(contains(parameters('policies')[copyIndex()], 'status'), createObject('value', parameters('policies')[copyIndex()].status), createObject('value', 'Enabled'))]", "threshold": { - "value": "[parameters('policies')[copyIndex()].threshold]" + "value": "[coalesce(parameters('policies'), createArray())[copyIndex()].threshold]" } }, "template": { @@ -647,10 +1544,10 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "12174587300527865958" + "templateHash": "10307787353498465860" }, "name": "DevTest Lab Policy Sets Policies", - "description": "This module deploys a DevTest Lab Policy Sets Policy.\n\nDevTest lab policies are used to modify the lab settings such as only allowing certain VM Size SKUs, marketplace image types, number of VMs allowed per user and other settings.", + "description": "This module deploys a DevTest Lab Policy Sets Policy.\r\n\r\nDevTest lab policies are used to modify the lab settings such as only allowing certain VM Size SKUs, marketplace image types, number of VMs allowed per user and other settings.", "owner": "Azure/module-maintainers" }, "parameters": { @@ -732,9 +1629,9 @@ "apiVersion": "2018-09-15", "name": "[format('{0}/{1}/{2}', parameters('labName'), 'default', parameters('name'))]", "properties": { - "description": "[parameters('description')]", + "description": "[coalesce(parameters('description'), '')]", "evaluatorType": "[parameters('evaluatorType')]", - "factData": "[parameters('factData')]", + "factData": "[coalesce(parameters('factData'), '')]", "factName": "[parameters('factName')]", "status": "[parameters('status')]", "threshold": "[parameters('threshold')]" @@ -773,7 +1670,7 @@ "lab_schedules": { "copy": { "name": "lab_schedules", - "count": "[length(parameters('schedules'))]" + "count": "[length(coalesce(parameters('schedules'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", @@ -788,22 +1685,35 @@ "value": "[parameters('name')]" }, "name": { - "value": "[parameters('schedules')[copyIndex()].name]" + "value": "[coalesce(parameters('schedules'), createArray())[copyIndex()].name]" }, "tags": { - "value": "[coalesce(tryGet(parameters('schedules')[copyIndex()], 'tags'), parameters('tags'))]" + "value": "[coalesce(tryGet(coalesce(parameters('schedules'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" }, "taskType": { - "value": "[parameters('schedules')[copyIndex()].taskType]" - }, - "dailyRecurrence": "[if(contains(parameters('schedules')[copyIndex()], 'dailyRecurrence'), createObject('value', parameters('schedules')[copyIndex()].dailyRecurrence), createObject('value', createObject()))]", - "hourlyRecurrence": "[if(contains(parameters('schedules')[copyIndex()], 'hourlyRecurrence'), createObject('value', parameters('schedules')[copyIndex()].hourlyRecurrence), createObject('value', createObject()))]", - "weeklyRecurrence": "[if(contains(parameters('schedules')[copyIndex()], 'weeklyRecurrence'), createObject('value', parameters('schedules')[copyIndex()].weeklyRecurrence), createObject('value', createObject()))]", - "status": "[if(contains(parameters('schedules')[copyIndex()], 'status'), createObject('value', parameters('schedules')[copyIndex()].status), createObject('value', 'Enabled'))]", - "targetResourceId": "[if(contains(parameters('schedules')[copyIndex()], 'targetResourceId'), createObject('value', parameters('schedules')[copyIndex()].targetResourceId), createObject('value', ''))]", - "timeZoneId": "[if(contains(parameters('schedules')[copyIndex()], 'timeZoneId'), createObject('value', parameters('schedules')[copyIndex()].timeZoneId), createObject('value', 'Pacific Standard time'))]", - "notificationSettingsStatus": "[if(contains(parameters('schedules')[copyIndex()], 'notificationSettingsStatus'), createObject('value', parameters('schedules')[copyIndex()].notificationSettingsStatus), createObject('value', 'Disabled'))]", - "notificationSettingsTimeInMinutes": "[if(contains(parameters('schedules')[copyIndex()], 'notificationSettingsTimeInMinutes'), createObject('value', parameters('schedules')[copyIndex()].notificationSettingsTimeInMinutes), createObject('value', 30))]" + "value": "[coalesce(parameters('schedules'), createArray())[copyIndex()].taskType]" + }, + "dailyRecurrence": { + "value": "[tryGet(coalesce(parameters('schedules'), createArray())[copyIndex()], 'dailyRecurrence')]" + }, + "hourlyRecurrence": { + "value": "[tryGet(coalesce(parameters('schedules'), createArray())[copyIndex()], 'hourlyRecurrence')]" + }, + "weeklyRecurrence": { + "value": "[tryGet(coalesce(parameters('schedules'), createArray())[copyIndex()], 'weeklyRecurrence')]" + }, + "status": { + "value": "[coalesce(tryGet(coalesce(parameters('schedules'), createArray())[copyIndex()], 'status'), 'Enabled')]" + }, + "targetResourceId": { + "value": "[tryGet(coalesce(parameters('schedules'), createArray())[copyIndex()], 'targetResourceId')]" + }, + "timeZoneId": { + "value": "[coalesce(tryGet(coalesce(parameters('schedules'), createArray())[copyIndex()], 'timeZoneId'), 'Pacific Standard time')]" + }, + "notificationSettings": { + "value": "[tryGet(coalesce(parameters('schedules'), createArray())[copyIndex()], 'notificationSettings')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -813,12 +1723,116 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "12081847452316418446" + "templateHash": "9010276477624635732" }, "name": "DevTest Lab Schedules", - "description": "This module deploys a DevTest Lab Schedule.\n\nLab schedules are used to modify the settings for auto-shutdown, auto-start for lab virtual machines.", + "description": "This module deploys a DevTest Lab Schedule.\r\n\r\nLab schedules are used to modify the settings for auto-shutdown, auto-start for lab virtual machines.", "owner": "Azure/module-maintainers" }, + "definitions": { + "dailyRecurrenceType": { + "type": "object", + "properties": { + "time": { + "type": "string", + "metadata": { + "description": "Required. The time of day the schedule will occur." + } + } + }, + "nullable": true, + "metadata": { + "__bicep_export!": true + } + }, + "hourlyRecurrenceType": { + "type": "object", + "properties": { + "minute": { + "type": "int", + "metadata": { + "description": "Required. Minutes of the hour the schedule will run." + } + } + }, + "nullable": true, + "metadata": { + "__bicep_export!": true + } + }, + "weeklyRecurrenceType": { + "type": "object", + "properties": { + "time": { + "type": "string", + "metadata": { + "description": "Required. The time of day the schedule will occur." + } + }, + "weekdays": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The days of the week for which the schedule is set (e.g. Sunday, Monday, Tuesday, etc.)." + } + } + }, + "nullable": true, + "metadata": { + "__bicep_export!": true + } + }, + "notificationSettingsType": { + "type": "object", + "properties": { + "emailRecipient": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The email recipient to send notifications to (can be a list of semi-colon separated email addresses). Required if \"webHookUrl\" is empty." + } + }, + "notificationLocale": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The locale to use when sending a notification (fallback for unsupported languages is EN)." + } + }, + "status": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. If notifications are enabled for this schedule (i.e. Enabled, Disabled). Default is Disabled." + } + }, + "timeInMinutes": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Time in minutes before event at which notification will be sent. Default is 30 minutes if status is Enabled and not specified." + } + }, + "webHookUrl": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The webhook URL to which the notification will be sent. Required if \"emailRecipient\" is empty." + } + } + }, + "nullable": true, + "metadata": { + "__bicep_export!": true + } + } + }, "parameters": { "labName": { "type": "string", @@ -854,22 +1868,19 @@ } }, "dailyRecurrence": { - "type": "object", - "defaultValue": {}, + "$ref": "#/definitions/dailyRecurrenceType", "metadata": { "description": "Optional. If the schedule will occur once each day of the week, specify the daily recurrence." } }, "hourlyRecurrence": { - "type": "object", - "defaultValue": {}, + "$ref": "#/definitions/hourlyRecurrenceType", "metadata": { "description": "Optional. If the schedule will occur multiple times a day, specify the hourly recurrence." } }, "weeklyRecurrence": { - "type": "object", - "defaultValue": {}, + "$ref": "#/definitions/weeklyRecurrenceType", "metadata": { "description": "Optional. If the schedule will occur only some days of the week, specify the weekly recurrence." } @@ -887,7 +1898,7 @@ }, "targetResourceId": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Optional. The resource ID to which the schedule belongs." } @@ -899,22 +1910,10 @@ "description": "Optional. The time zone ID (e.g. Pacific Standard time)." } }, - "notificationSettingsStatus": { - "type": "string", - "defaultValue": "Disabled", - "allowedValues": [ - "Enabled", - "Disabled" - ], - "metadata": { - "description": "Optional. If notifications are enabled for this schedule (i.e. Enabled, Disabled)." - } - }, - "notificationSettingsTimeInMinutes": { - "type": "int", - "defaultValue": 30, + "notificationSettings": { + "$ref": "#/definitions/notificationSettingsType", "metadata": { - "description": "Optional. Time in minutes before event at which notification will be sent. Optional if \"notificationSettingsStatus\" is set to \"Enabled\". Default is 30 minutes." + "description": "Optional. The notification settings for the schedule." } } }, @@ -932,13 +1931,13 @@ "tags": "[parameters('tags')]", "properties": { "taskType": "[parameters('taskType')]", - "dailyRecurrence": "[if(not(empty(parameters('dailyRecurrence'))), parameters('dailyRecurrence'), null())]", - "hourlyRecurrence": "[if(not(empty(parameters('hourlyRecurrence'))), parameters('hourlyRecurrence'), null())]", - "weeklyRecurrence": "[if(not(empty(parameters('weeklyRecurrence'))), parameters('weeklyRecurrence'), null())]", + "dailyRecurrence": "[parameters('dailyRecurrence')]", + "hourlyRecurrence": "[parameters('hourlyRecurrence')]", + "weeklyRecurrence": "[parameters('weeklyRecurrence')]", "status": "[parameters('status')]", - "targetResourceId": "[if(not(empty(parameters('targetResourceId'))), parameters('targetResourceId'), null())]", + "targetResourceId": "[parameters('targetResourceId')]", "timeZoneId": "[parameters('timeZoneId')]", - "notificationSettings": "[if(equals(parameters('notificationSettingsStatus'), 'Enabled'), createObject('status', parameters('notificationSettingsStatus'), 'timeInMinutes', parameters('notificationSettingsTimeInMinutes')), createObject())]" + "notificationSettings": "[parameters('notificationSettings')]" }, "dependsOn": [ "lab" @@ -977,7 +1976,7 @@ "lab_notificationChannels": { "copy": { "name": "lab_notificationChannels", - "count": "[length(parameters('notificationchannels'))]" + "count": "[length(coalesce(parameters('notificationchannels'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", @@ -992,18 +1991,26 @@ "value": "[parameters('name')]" }, "name": { - "value": "[parameters('notificationchannels')[copyIndex()].name]" + "value": "[coalesce(parameters('notificationchannels'), createArray())[copyIndex()].name]" }, "tags": { - "value": "[coalesce(tryGet(parameters('notificationchannels')[copyIndex()], 'tags'), parameters('tags'))]" + "value": "[coalesce(tryGet(coalesce(parameters('notificationchannels'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "description": { + "value": "[tryGet(coalesce(parameters('notificationchannels'), createArray())[copyIndex()], 'description')]" }, - "description": "[if(contains(parameters('notificationchannels')[copyIndex()], 'description'), createObject('value', parameters('notificationchannels')[copyIndex()].description), createObject('value', ''))]", "events": { - "value": "[parameters('notificationchannels')[copyIndex()].events]" + "value": "[coalesce(parameters('notificationchannels'), createArray())[copyIndex()].events]" + }, + "emailRecipient": { + "value": "[tryGet(coalesce(parameters('notificationchannels'), createArray())[copyIndex()], 'emailRecipient')]" }, - "emailRecipient": "[if(contains(parameters('notificationchannels')[copyIndex()], 'emailRecipient'), createObject('value', parameters('notificationchannels')[copyIndex()].emailRecipient), createObject('value', ''))]", - "webHookUrl": "[if(contains(parameters('notificationchannels')[copyIndex()], 'webhookUrl'), createObject('value', parameters('notificationchannels')[copyIndex()].webhookUrl), createObject('value', ''))]", - "notificationLocale": "[if(contains(parameters('notificationchannels')[copyIndex()], 'notificationLocale'), createObject('value', parameters('notificationchannels')[copyIndex()].notificationLocale), createObject('value', 'en'))]" + "webHookUrl": { + "value": "[tryGet(coalesce(parameters('notificationchannels'), createArray())[copyIndex()], 'webHookUrl')]" + }, + "notificationLocale": { + "value": "[coalesce(tryGet(coalesce(parameters('notificationchannels'), createArray())[copyIndex()], 'notificationLocale'), 'en')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -1013,10 +2020,10 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "7230564820615005386" + "templateHash": "95632191903979650" }, "name": "DevTest Lab Notification Channels", - "description": "This module deploys a DevTest Lab Notification Channel.\n\nNotification channels are used by the schedule resource type in order to send notifications or events to email addresses and/or webhooks.", + "description": "This module deploys a DevTest Lab Notification Channel.\r\n\r\nNotification channels are used by the schedule resource type in order to send notifications or events to email addresses and/or webhooks.", "owner": "Azure/module-maintainers" }, "parameters": { @@ -1059,7 +2066,7 @@ }, "emailRecipient": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Conditional. The email recipient to send notifications to (can be a list of semi-colon separated email addresses). Required if \"webHookUrl\" is empty." } @@ -1092,8 +2099,16 @@ "name": "[format('{0}/{1}', parameters('labName'), parameters('name'))]", "tags": "[parameters('tags')]", "properties": { + "copy": [ + { + "name": "events", + "count": "[length(parameters('events'))]", + "input": { + "eventName": "[parameters('events')[copyIndex('events')]]" + } + } + ], "description": "[parameters('description')]", - "events": "[parameters('events')]", "emailRecipient": "[parameters('emailRecipient')]", "webHookUrl": "[parameters('webHookUrl')]", "notificationLocale": "[parameters('notificationLocale')]" @@ -1135,7 +2150,7 @@ "lab_artifactSources": { "copy": { "name": "lab_artifactSources", - "count": "[length(parameters('artifactsources'))]" + "count": "[length(coalesce(parameters('artifactsources'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", @@ -1150,19 +2165,34 @@ "value": "[parameters('name')]" }, "name": { - "value": "[parameters('artifactsources')[copyIndex()].name]" + "value": "[coalesce(parameters('artifactsources'), createArray())[copyIndex()].name]" }, "tags": { - "value": "[coalesce(tryGet(parameters('artifactsources')[copyIndex()], 'tags'), parameters('tags'))]" - }, - "displayName": "[if(contains(parameters('artifactsources')[copyIndex()], 'displayName'), createObject('value', parameters('artifactsources')[copyIndex()].displayName), createObject('value', parameters('artifactsources')[copyIndex()].name))]", - "branchRef": "[if(contains(parameters('artifactsources')[copyIndex()], 'branchRef'), createObject('value', parameters('artifactsources')[copyIndex()].branchRef), createObject('value', ''))]", - "folderPath": "[if(contains(parameters('artifactsources')[copyIndex()], 'folderPath'), createObject('value', parameters('artifactsources')[copyIndex()].folderPath), createObject('value', ''))]", - "armTemplateFolderPath": "[if(contains(parameters('artifactsources')[copyIndex()], 'armTemplateFolderPath'), createObject('value', parameters('artifactsources')[copyIndex()].armTemplateFolderPath), createObject('value', ''))]", - "sourceType": "[if(contains(parameters('artifactsources')[copyIndex()], 'sourceType'), createObject('value', parameters('artifactsources')[copyIndex()].sourceType), createObject('value', ''))]", - "status": "[if(contains(parameters('artifactsources')[copyIndex()], 'status'), createObject('value', parameters('artifactsources')[copyIndex()].status), createObject('value', 'Enabled'))]", + "value": "[coalesce(tryGet(coalesce(parameters('artifactsources'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "displayName": { + "value": "[coalesce(tryGet(coalesce(parameters('artifactsources'), createArray())[copyIndex()], 'displayName'), coalesce(parameters('artifactsources'), createArray())[copyIndex()].name)]" + }, + "branchRef": { + "value": "[tryGet(coalesce(parameters('artifactsources'), createArray())[copyIndex()], 'branchRef')]" + }, + "folderPath": { + "value": "[tryGet(coalesce(parameters('artifactsources'), createArray())[copyIndex()], 'folderPath')]" + }, + "armTemplateFolderPath": { + "value": "[tryGet(coalesce(parameters('artifactsources'), createArray())[copyIndex()], 'armTemplateFolderPath')]" + }, + "sourceType": { + "value": "[tryGet(coalesce(parameters('artifactsources'), createArray())[copyIndex()], 'sourceType')]" + }, + "status": { + "value": "[coalesce(tryGet(coalesce(parameters('artifactsources'), createArray())[copyIndex()], 'status'), 'Enabled')]" + }, "uri": { - "value": "[parameters('artifactsources')[copyIndex()].uri]" + "value": "[coalesce(parameters('artifactsources'), createArray())[copyIndex()].uri]" + }, + "securityToken": { + "value": "[tryGet(coalesce(parameters('artifactsources'), createArray())[copyIndex()], 'securityToken')]" } }, "template": { @@ -1173,10 +2203,10 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "15552685312013632487" + "templateHash": "13904061272597362111" }, "name": "DevTest Lab Artifact Sources", - "description": "This module deploys a DevTest Lab Artifact Source.\n\nAn artifact source allows you to create custom artifacts for the VMs in the lab, or use Azure Resource Manager templates to create a custom test environment. You must add a private Git repository for the artifacts or Resource Manager templates that your team creates. The repository can be hosted on GitHub or on Azure DevOps Services.", + "description": "This module deploys a DevTest Lab Artifact Source.\r\n\r\nAn artifact source allows you to create custom artifacts for the VMs in the lab, or use Azure Resource Manager templates to create a custom test environment. You must add a private Git repository for the artifacts or Resource Manager templates that your team creates. The repository can be hosted on GitHub or on Azure DevOps Services.", "owner": "Azure/module-maintainers" }, "parameters": { @@ -1208,37 +2238,36 @@ }, "branchRef": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Optional. The artifact source's branch reference (e.g. main or master)." } }, "folderPath": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Conditional. The folder containing artifacts. At least one folder path is required. Required if \"armTemplateFolderPath\" is empty." } }, "armTemplateFolderPath": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Conditional. The folder containing Azure Resource Manager templates. Required if \"folderPath\" is empty." } }, "securityToken": { "type": "securestring", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Optional. The security token to authenticate to the artifact source." } }, "sourceType": { "type": "string", - "defaultValue": "", + "nullable": true, "allowedValues": [ - "", "GitHub", "StorageAccount", "VsoGit" @@ -1279,11 +2308,11 @@ "tags": "[parameters('tags')]", "properties": { "displayName": "[parameters('displayName')]", - "branchRef": "[if(not(empty(parameters('branchRef'))), parameters('branchRef'), null())]", - "folderPath": "[if(not(empty(parameters('folderPath'))), parameters('folderPath'), null())]", - "armTemplateFolderPath": "[if(not(empty(parameters('armTemplateFolderPath'))), parameters('armTemplateFolderPath'), null())]", - "securityToken": "[if(not(empty(parameters('securityToken'))), parameters('securityToken'), null())]", - "sourceType": "[if(not(empty(parameters('sourceType'))), parameters('sourceType'), null())]", + "branchRef": "[parameters('branchRef')]", + "folderPath": "[parameters('folderPath')]", + "armTemplateFolderPath": "[parameters('armTemplateFolderPath')]", + "securityToken": "[parameters('securityToken')]", + "sourceType": "[parameters('sourceType')]", "status": "[parameters('status')]", "uri": "[parameters('uri')]" }, @@ -1338,24 +2367,54 @@ "tags": { "value": "[coalesce(tryGet(parameters('costs'), 'tags'), parameters('tags'))]" }, - "currencyCode": "[if(contains(parameters('costs'), 'currencyCode'), createObject('value', parameters('costs').currencyCode), createObject('value', 'USD'))]", + "currencyCode": { + "value": "[coalesce(tryGet(parameters('costs'), 'currencyCode'), 'USD')]" + }, "cycleType": { "value": "[parameters('costs').cycleType]" }, - "cycleStartDateTime": "[if(contains(parameters('costs'), 'cycleStartDateTime'), createObject('value', parameters('costs').cycleStartDateTime), createObject('value', ''))]", - "cycleEndDateTime": "[if(contains(parameters('costs'), 'cycleEndDateTime'), createObject('value', parameters('costs').cycleEndDateTime), createObject('value', ''))]", - "status": "[if(contains(parameters('costs'), 'status'), createObject('value', parameters('costs').status), createObject('value', 'Enabled'))]", - "target": "[if(contains(parameters('costs'), 'target'), createObject('value', parameters('costs').target), createObject('value', 0))]", - "thresholdValue25DisplayOnChart": "[if(contains(parameters('costs'), 'thresholdValue25DisplayOnChart'), createObject('value', parameters('costs').thresholdValue25DisplayOnChart), createObject('value', 'Disabled'))]", - "thresholdValue25SendNotificationWhenExceeded": "[if(contains(parameters('costs'), 'thresholdValue25SendNotificationWhenExceeded'), createObject('value', parameters('costs').thresholdValue25SendNotificationWhenExceeded), createObject('value', 'Disabled'))]", - "thresholdValue50DisplayOnChart": "[if(contains(parameters('costs'), 'thresholdValue50DisplayOnChart'), createObject('value', parameters('costs').thresholdValue50DisplayOnChart), createObject('value', 'Disabled'))]", - "thresholdValue50SendNotificationWhenExceeded": "[if(contains(parameters('costs'), 'thresholdValue50SendNotificationWhenExceeded'), createObject('value', parameters('costs').thresholdValue50SendNotificationWhenExceeded), createObject('value', 'Disabled'))]", - "thresholdValue75DisplayOnChart": "[if(contains(parameters('costs'), 'thresholdValue75DisplayOnChart'), createObject('value', parameters('costs').thresholdValue75DisplayOnChart), createObject('value', 'Disabled'))]", - "thresholdValue75SendNotificationWhenExceeded": "[if(contains(parameters('costs'), 'thresholdValue75SendNotificationWhenExceeded'), createObject('value', parameters('costs').thresholdValue75SendNotificationWhenExceeded), createObject('value', 'Disabled'))]", - "thresholdValue100DisplayOnChart": "[if(contains(parameters('costs'), 'thresholdValue100DisplayOnChart'), createObject('value', parameters('costs').thresholdValue100DisplayOnChart), createObject('value', 'Disabled'))]", - "thresholdValue100SendNotificationWhenExceeded": "[if(contains(parameters('costs'), 'thresholdValue100SendNotificationWhenExceeded'), createObject('value', parameters('costs').thresholdValue100SendNotificationWhenExceeded), createObject('value', 'Disabled'))]", - "thresholdValue125DisplayOnChart": "[if(contains(parameters('costs'), 'thresholdValue125DisplayOnChart'), createObject('value', parameters('costs').thresholdValue125DisplayOnChart), createObject('value', 'Disabled'))]", - "thresholdValue125SendNotificationWhenExceeded": "[if(contains(parameters('costs'), 'thresholdValue125SendNotificationWhenExceeded'), createObject('value', parameters('costs').thresholdValue125SendNotificationWhenExceeded), createObject('value', 'Disabled'))]" + "cycleStartDateTime": { + "value": "[tryGet(parameters('costs'), 'cycleStartDateTime')]" + }, + "cycleEndDateTime": { + "value": "[tryGet(parameters('costs'), 'cycleEndDateTime')]" + }, + "status": { + "value": "[coalesce(tryGet(parameters('costs'), 'status'), 'Enabled')]" + }, + "target": { + "value": "[coalesce(tryGet(parameters('costs'), 'target'), 0)]" + }, + "thresholdValue25DisplayOnChart": { + "value": "[coalesce(tryGet(parameters('costs'), 'thresholdValue25DisplayOnChart'), 'Disabled')]" + }, + "thresholdValue25SendNotificationWhenExceeded": { + "value": "[coalesce(tryGet(parameters('costs'), 'thresholdValue25SendNotificationWhenExceeded'), 'Disabled')]" + }, + "thresholdValue50DisplayOnChart": { + "value": "[coalesce(tryGet(parameters('costs'), 'thresholdValue50DisplayOnChart'), 'Disabled')]" + }, + "thresholdValue50SendNotificationWhenExceeded": { + "value": "[coalesce(tryGet(parameters('costs'), 'thresholdValue50SendNotificationWhenExceeded'), 'Disabled')]" + }, + "thresholdValue75DisplayOnChart": { + "value": "[coalesce(tryGet(parameters('costs'), 'thresholdValue75DisplayOnChart'), 'Disabled')]" + }, + "thresholdValue75SendNotificationWhenExceeded": { + "value": "[coalesce(tryGet(parameters('costs'), 'thresholdValue75SendNotificationWhenExceeded'), 'Disabled')]" + }, + "thresholdValue100DisplayOnChart": { + "value": "[coalesce(tryGet(parameters('costs'), 'thresholdValue100DisplayOnChart'), 'Disabled')]" + }, + "thresholdValue100SendNotificationWhenExceeded": { + "value": "[coalesce(tryGet(parameters('costs'), 'thresholdValue100SendNotificationWhenExceeded'), 'Disabled')]" + }, + "thresholdValue125DisplayOnChart": { + "value": "[coalesce(tryGet(parameters('costs'), 'thresholdValue125DisplayOnChart'), 'Disabled')]" + }, + "thresholdValue125SendNotificationWhenExceeded": { + "value": "[coalesce(tryGet(parameters('costs'), 'thresholdValue125SendNotificationWhenExceeded'), 'Disabled')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -1365,10 +2424,10 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "16348143074487982445" + "templateHash": "7509251296299887127" }, "name": "DevTest Lab Costs", - "description": "This module deploys a DevTest Lab Cost.\n\nManage lab costs by setting a spending target that can be viewed in the Monthly Estimated Cost Trend chart. DevTest Labs can send a notification when spending reaches the specified target threshold.", + "description": "This module deploys a DevTest Lab Cost.\r\n\r\nManage lab costs by setting a spending target that can be viewed in the Monthly Estimated Cost Trend chart. DevTest Labs can send a notification when spending reaches the specified target threshold.", "owner": "Azure/module-maintainers" }, "parameters": { @@ -1397,14 +2456,14 @@ }, "cycleStartDateTime": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Conditional. Reporting cycle start date in the zulu time format (e.g. 2023-12-01T00:00:00.000Z). Required if cycleType is set to \"Custom\"." } }, "cycleEndDateTime": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Conditional. Reporting cycle end date in the zulu time format (e.g. 2023-12-01T00:00:00.000Z). Required if cycleType is set to \"Custom\"." } diff --git a/avm/res/dev-test-lab/lab/notificationchannel/README.md b/avm/res/dev-test-lab/lab/notificationchannel/README.md index 5ab3d187aa..9fee767321 100644 --- a/avm/res/dev-test-lab/lab/notificationchannel/README.md +++ b/avm/res/dev-test-lab/lab/notificationchannel/README.md @@ -71,7 +71,6 @@ The email recipient to send notifications to (can be a list of semi-colon separa - Required: No - Type: string -- Default: `''` ### Parameter: `labName` diff --git a/avm/res/dev-test-lab/lab/notificationchannel/main.bicep b/avm/res/dev-test-lab/lab/notificationchannel/main.bicep index 2e97b66ddd..84fa6aa9f8 100644 --- a/avm/res/dev-test-lab/lab/notificationchannel/main.bicep +++ b/avm/res/dev-test-lab/lab/notificationchannel/main.bicep @@ -24,7 +24,7 @@ param description string = '' param events array = [] @sys.description('Conditional. The email recipient to send notifications to (can be a list of semi-colon separated email addresses). Required if "webHookUrl" is empty.') -param emailRecipient string = '' +param emailRecipient string? @sys.description('Conditional. The webhook URL to which the notification will be sent. Required if "emailRecipient" is empty.') param webHookUrl string = '' @@ -42,7 +42,11 @@ resource notificationChannel 'Microsoft.DevTestLab/labs/notificationchannels@201 tags: tags properties: { description: description - events: events + events: [ + for event in events: { + eventName: event + } + ] emailRecipient: emailRecipient webHookUrl: webHookUrl notificationLocale: notificationLocale diff --git a/avm/res/dev-test-lab/lab/notificationchannel/main.json b/avm/res/dev-test-lab/lab/notificationchannel/main.json index f7dbaa7d25..36963586b6 100644 --- a/avm/res/dev-test-lab/lab/notificationchannel/main.json +++ b/avm/res/dev-test-lab/lab/notificationchannel/main.json @@ -6,10 +6,10 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "7230564820615005386" + "templateHash": "95632191903979650" }, "name": "DevTest Lab Notification Channels", - "description": "This module deploys a DevTest Lab Notification Channel.\n\nNotification channels are used by the schedule resource type in order to send notifications or events to email addresses and/or webhooks.", + "description": "This module deploys a DevTest Lab Notification Channel.\r\n\r\nNotification channels are used by the schedule resource type in order to send notifications or events to email addresses and/or webhooks.", "owner": "Azure/module-maintainers" }, "parameters": { @@ -52,7 +52,7 @@ }, "emailRecipient": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Conditional. The email recipient to send notifications to (can be a list of semi-colon separated email addresses). Required if \"webHookUrl\" is empty." } @@ -85,8 +85,16 @@ "name": "[format('{0}/{1}', parameters('labName'), parameters('name'))]", "tags": "[parameters('tags')]", "properties": { + "copy": [ + { + "name": "events", + "count": "[length(parameters('events'))]", + "input": { + "eventName": "[parameters('events')[copyIndex('events')]]" + } + } + ], "description": "[parameters('description')]", - "events": "[parameters('events')]", "emailRecipient": "[parameters('emailRecipient')]", "webHookUrl": "[parameters('webHookUrl')]", "notificationLocale": "[parameters('notificationLocale')]" diff --git a/avm/res/dev-test-lab/lab/policyset/policy/main.bicep b/avm/res/dev-test-lab/lab/policyset/policy/main.bicep index c9dbe3dba8..a4a53a50c4 100644 --- a/avm/res/dev-test-lab/lab/policyset/policy/main.bicep +++ b/avm/res/dev-test-lab/lab/policyset/policy/main.bicep @@ -60,9 +60,9 @@ resource policy 'Microsoft.DevTestLab/labs/policysets/policies@2018-09-15' = { name: name parent: lab::policySets properties: { - description: description + description: description ?? '' evaluatorType: evaluatorType - factData: factData + factData: factData ?? '' factName: factName status: status threshold: threshold diff --git a/avm/res/dev-test-lab/lab/policyset/policy/main.json b/avm/res/dev-test-lab/lab/policyset/policy/main.json index 3a63fb4c36..35370542da 100644 --- a/avm/res/dev-test-lab/lab/policyset/policy/main.json +++ b/avm/res/dev-test-lab/lab/policyset/policy/main.json @@ -5,10 +5,10 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "12174587300527865958" + "templateHash": "10307787353498465860" }, "name": "DevTest Lab Policy Sets Policies", - "description": "This module deploys a DevTest Lab Policy Sets Policy.\n\nDevTest lab policies are used to modify the lab settings such as only allowing certain VM Size SKUs, marketplace image types, number of VMs allowed per user and other settings.", + "description": "This module deploys a DevTest Lab Policy Sets Policy.\r\n\r\nDevTest lab policies are used to modify the lab settings such as only allowing certain VM Size SKUs, marketplace image types, number of VMs allowed per user and other settings.", "owner": "Azure/module-maintainers" }, "parameters": { @@ -90,9 +90,9 @@ "apiVersion": "2018-09-15", "name": "[format('{0}/{1}/{2}', parameters('labName'), 'default', parameters('name'))]", "properties": { - "description": "[parameters('description')]", + "description": "[coalesce(parameters('description'), '')]", "evaluatorType": "[parameters('evaluatorType')]", - "factData": "[parameters('factData')]", + "factData": "[coalesce(parameters('factData'), '')]", "factName": "[parameters('factName')]", "status": "[parameters('status')]", "threshold": "[parameters('threshold')]" diff --git a/avm/res/dev-test-lab/lab/schedule/README.md b/avm/res/dev-test-lab/lab/schedule/README.md index dda473fdde..2358f8c145 100644 --- a/avm/res/dev-test-lab/lab/schedule/README.md +++ b/avm/res/dev-test-lab/lab/schedule/README.md @@ -39,8 +39,7 @@ Lab schedules are used to modify the settings for auto-shutdown, auto-start for | :-- | :-- | :-- | | [`dailyRecurrence`](#parameter-dailyrecurrence) | object | If the schedule will occur once each day of the week, specify the daily recurrence. | | [`hourlyRecurrence`](#parameter-hourlyrecurrence) | object | If the schedule will occur multiple times a day, specify the hourly recurrence. | -| [`notificationSettingsStatus`](#parameter-notificationsettingsstatus) | string | If notifications are enabled for this schedule (i.e. Enabled, Disabled). | -| [`notificationSettingsTimeInMinutes`](#parameter-notificationsettingstimeinminutes) | int | Time in minutes before event at which notification will be sent. Optional if "notificationSettingsStatus" is set to "Enabled". Default is 30 minutes. | +| [`notificationSettings`](#parameter-notificationsettings) | object | The notification settings for the schedule. | | [`status`](#parameter-status) | string | The status of the schedule (i.e. Enabled, Disabled). | | [`tags`](#parameter-tags) | object | Tags of the resource. | | [`targetResourceId`](#parameter-targetresourceid) | string | The resource ID to which the schedule belongs. | @@ -88,7 +87,19 @@ If the schedule will occur once each day of the week, specify the daily recurren - Required: No - Type: object -- Default: `{}` + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`time`](#parameter-dailyrecurrencetime) | string | The time of day the schedule will occur. | + +### Parameter: `dailyRecurrence.time` + +The time of day the schedule will occur. + +- Required: Yes +- Type: string ### Parameter: `hourlyRecurrence` @@ -96,15 +107,69 @@ If the schedule will occur multiple times a day, specify the hourly recurrence. - Required: No - Type: object -- Default: `{}` -### Parameter: `notificationSettingsStatus` +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`minute`](#parameter-hourlyrecurrenceminute) | int | Minutes of the hour the schedule will run. | + +### Parameter: `hourlyRecurrence.minute` + +Minutes of the hour the schedule will run. + +- Required: Yes +- Type: int + +### Parameter: `notificationSettings` + +The notification settings for the schedule. + +- Required: No +- Type: object + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`emailRecipient`](#parameter-notificationsettingsemailrecipient) | string | The email recipient to send notifications to (can be a list of semi-colon separated email addresses). Required if "webHookUrl" is empty. | +| [`webHookUrl`](#parameter-notificationsettingswebhookurl) | string | The webhook URL to which the notification will be sent. Required if "emailRecipient" is empty. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`notificationLocale`](#parameter-notificationsettingsnotificationlocale) | string | The locale to use when sending a notification (fallback for unsupported languages is EN). | +| [`status`](#parameter-notificationsettingsstatus) | string | If notifications are enabled for this schedule (i.e. Enabled, Disabled). Default is Disabled. | +| [`timeInMinutes`](#parameter-notificationsettingstimeinminutes) | int | Time in minutes before event at which notification will be sent. Default is 30 minutes if status is Enabled and not specified. | + +### Parameter: `notificationSettings.emailRecipient` + +The email recipient to send notifications to (can be a list of semi-colon separated email addresses). Required if "webHookUrl" is empty. + +- Required: No +- Type: string + +### Parameter: `notificationSettings.webHookUrl` + +The webhook URL to which the notification will be sent. Required if "emailRecipient" is empty. + +- Required: No +- Type: string + +### Parameter: `notificationSettings.notificationLocale` + +The locale to use when sending a notification (fallback for unsupported languages is EN). + +- Required: No +- Type: string + +### Parameter: `notificationSettings.status` -If notifications are enabled for this schedule (i.e. Enabled, Disabled). +If notifications are enabled for this schedule (i.e. Enabled, Disabled). Default is Disabled. - Required: No - Type: string -- Default: `'Disabled'` - Allowed: ```Bicep [ @@ -113,13 +178,12 @@ If notifications are enabled for this schedule (i.e. Enabled, Disabled). ] ``` -### Parameter: `notificationSettingsTimeInMinutes` +### Parameter: `notificationSettings.timeInMinutes` -Time in minutes before event at which notification will be sent. Optional if "notificationSettingsStatus" is set to "Enabled". Default is 30 minutes. +Time in minutes before event at which notification will be sent. Default is 30 minutes if status is Enabled and not specified. - Required: No - Type: int -- Default: `30` ### Parameter: `status` @@ -149,7 +213,6 @@ The resource ID to which the schedule belongs. - Required: No - Type: string -- Default: `''` ### Parameter: `timeZoneId` @@ -165,7 +228,27 @@ If the schedule will occur only some days of the week, specify the weekly recurr - Required: No - Type: object -- Default: `{}` + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`time`](#parameter-weeklyrecurrencetime) | string | The time of day the schedule will occur. | +| [`weekdays`](#parameter-weeklyrecurrenceweekdays) | array | The days of the week for which the schedule is set (e.g. Sunday, Monday, Tuesday, etc.). | + +### Parameter: `weeklyRecurrence.time` + +The time of day the schedule will occur. + +- Required: Yes +- Type: string + +### Parameter: `weeklyRecurrence.weekdays` + +The days of the week for which the schedule is set (e.g. Sunday, Monday, Tuesday, etc.). + +- Required: Yes +- Type: array ## Outputs diff --git a/avm/res/dev-test-lab/lab/schedule/main.bicep b/avm/res/dev-test-lab/lab/schedule/main.bicep index c71be08e40..fea3cc182f 100644 --- a/avm/res/dev-test-lab/lab/schedule/main.bicep +++ b/avm/res/dev-test-lab/lab/schedule/main.bicep @@ -25,13 +25,13 @@ param taskType string param tags object? @sys.description('Optional. If the schedule will occur once each day of the week, specify the daily recurrence.') -param dailyRecurrence object = {} +param dailyRecurrence dailyRecurrenceType @sys.description('Optional. If the schedule will occur multiple times a day, specify the hourly recurrence.') -param hourlyRecurrence object = {} +param hourlyRecurrence hourlyRecurrenceType @sys.description('Optional. If the schedule will occur only some days of the week, specify the weekly recurrence.') -param weeklyRecurrence object = {} +param weeklyRecurrence weeklyRecurrenceType @allowed([ 'Enabled' @@ -41,20 +41,13 @@ param weeklyRecurrence object = {} param status string = 'Enabled' @sys.description('Optional. The resource ID to which the schedule belongs.') -param targetResourceId string = '' +param targetResourceId string? @sys.description('Optional. The time zone ID (e.g. Pacific Standard time).') param timeZoneId string = 'Pacific Standard time' -@allowed([ - 'Enabled' - 'Disabled' -]) -@sys.description('Optional. If notifications are enabled for this schedule (i.e. Enabled, Disabled).') -param notificationSettingsStatus string = 'Disabled' - -@sys.description('Optional. Time in minutes before event at which notification will be sent. Optional if "notificationSettingsStatus" is set to "Enabled". Default is 30 minutes.') -param notificationSettingsTimeInMinutes int = 30 +@sys.description('Optional. The notification settings for the schedule.') +param notificationSettings notificationSettingsType resource lab 'Microsoft.DevTestLab/labs@2018-09-15' existing = { name: labName @@ -66,18 +59,13 @@ resource schedule 'Microsoft.DevTestLab/labs/schedules@2018-09-15' = { tags: tags properties: { taskType: taskType - dailyRecurrence: !empty(dailyRecurrence) ? dailyRecurrence : null - hourlyRecurrence: !empty(hourlyRecurrence) ? hourlyRecurrence : null - weeklyRecurrence: !empty(weeklyRecurrence) ? weeklyRecurrence : null + dailyRecurrence: dailyRecurrence + hourlyRecurrence: hourlyRecurrence + weeklyRecurrence: weeklyRecurrence status: status - targetResourceId: !empty(targetResourceId) ? targetResourceId : null + targetResourceId: targetResourceId timeZoneId: timeZoneId - notificationSettings: notificationSettingsStatus == 'Enabled' - ? { - status: notificationSettingsStatus - timeInMinutes: notificationSettingsTimeInMinutes - } - : {} + notificationSettings: notificationSettings } } @@ -89,3 +77,46 @@ output resourceId string = schedule.id @sys.description('The name of the resource group the schedule was created in.') output resourceGroupName string = resourceGroup().name + +// =============== // +// Definitions // +// =============== // + +@export() +type dailyRecurrenceType = { + @sys.description('Required. The time of day the schedule will occur.') + time: string +}? + +@export() +type hourlyRecurrenceType = { + @sys.description('Required. Minutes of the hour the schedule will run.') + minute: int +}? + +@export() +type weeklyRecurrenceType = { + @sys.description('Required. The time of day the schedule will occur.') + time: string + + @sys.description('Required. The days of the week for which the schedule is set (e.g. Sunday, Monday, Tuesday, etc.).') + weekdays: string[] +}? + +@export() +type notificationSettingsType = { + @description('Conditional. The email recipient to send notifications to (can be a list of semi-colon separated email addresses). Required if "webHookUrl" is empty.') + emailRecipient: string? + + @description('Optional. The locale to use when sending a notification (fallback for unsupported languages is EN).') + notificationLocale: string? + + @sys.description('Optional. If notifications are enabled for this schedule (i.e. Enabled, Disabled). Default is Disabled.') + status: 'Disabled' | 'Enabled'? + + @sys.description('Optional. Time in minutes before event at which notification will be sent. Default is 30 minutes if status is Enabled and not specified.') + timeInMinutes: int? + + @description('Conditional. The webhook URL to which the notification will be sent. Required if "emailRecipient" is empty.') + webHookUrl: string? +}? diff --git a/avm/res/dev-test-lab/lab/schedule/main.json b/avm/res/dev-test-lab/lab/schedule/main.json index 7b20dd6385..2b95e09f31 100644 --- a/avm/res/dev-test-lab/lab/schedule/main.json +++ b/avm/res/dev-test-lab/lab/schedule/main.json @@ -6,12 +6,116 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "12081847452316418446" + "templateHash": "9010276477624635732" }, "name": "DevTest Lab Schedules", - "description": "This module deploys a DevTest Lab Schedule.\n\nLab schedules are used to modify the settings for auto-shutdown, auto-start for lab virtual machines.", + "description": "This module deploys a DevTest Lab Schedule.\r\n\r\nLab schedules are used to modify the settings for auto-shutdown, auto-start for lab virtual machines.", "owner": "Azure/module-maintainers" }, + "definitions": { + "dailyRecurrenceType": { + "type": "object", + "properties": { + "time": { + "type": "string", + "metadata": { + "description": "Required. The time of day the schedule will occur." + } + } + }, + "nullable": true, + "metadata": { + "__bicep_export!": true + } + }, + "hourlyRecurrenceType": { + "type": "object", + "properties": { + "minute": { + "type": "int", + "metadata": { + "description": "Required. Minutes of the hour the schedule will run." + } + } + }, + "nullable": true, + "metadata": { + "__bicep_export!": true + } + }, + "weeklyRecurrenceType": { + "type": "object", + "properties": { + "time": { + "type": "string", + "metadata": { + "description": "Required. The time of day the schedule will occur." + } + }, + "weekdays": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The days of the week for which the schedule is set (e.g. Sunday, Monday, Tuesday, etc.)." + } + } + }, + "nullable": true, + "metadata": { + "__bicep_export!": true + } + }, + "notificationSettingsType": { + "type": "object", + "properties": { + "emailRecipient": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The email recipient to send notifications to (can be a list of semi-colon separated email addresses). Required if \"webHookUrl\" is empty." + } + }, + "notificationLocale": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The locale to use when sending a notification (fallback for unsupported languages is EN)." + } + }, + "status": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. If notifications are enabled for this schedule (i.e. Enabled, Disabled). Default is Disabled." + } + }, + "timeInMinutes": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Time in minutes before event at which notification will be sent. Default is 30 minutes if status is Enabled and not specified." + } + }, + "webHookUrl": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The webhook URL to which the notification will be sent. Required if \"emailRecipient\" is empty." + } + } + }, + "nullable": true, + "metadata": { + "__bicep_export!": true + } + } + }, "parameters": { "labName": { "type": "string", @@ -47,22 +151,19 @@ } }, "dailyRecurrence": { - "type": "object", - "defaultValue": {}, + "$ref": "#/definitions/dailyRecurrenceType", "metadata": { "description": "Optional. If the schedule will occur once each day of the week, specify the daily recurrence." } }, "hourlyRecurrence": { - "type": "object", - "defaultValue": {}, + "$ref": "#/definitions/hourlyRecurrenceType", "metadata": { "description": "Optional. If the schedule will occur multiple times a day, specify the hourly recurrence." } }, "weeklyRecurrence": { - "type": "object", - "defaultValue": {}, + "$ref": "#/definitions/weeklyRecurrenceType", "metadata": { "description": "Optional. If the schedule will occur only some days of the week, specify the weekly recurrence." } @@ -80,7 +181,7 @@ }, "targetResourceId": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Optional. The resource ID to which the schedule belongs." } @@ -92,22 +193,10 @@ "description": "Optional. The time zone ID (e.g. Pacific Standard time)." } }, - "notificationSettingsStatus": { - "type": "string", - "defaultValue": "Disabled", - "allowedValues": [ - "Enabled", - "Disabled" - ], - "metadata": { - "description": "Optional. If notifications are enabled for this schedule (i.e. Enabled, Disabled)." - } - }, - "notificationSettingsTimeInMinutes": { - "type": "int", - "defaultValue": 30, + "notificationSettings": { + "$ref": "#/definitions/notificationSettingsType", "metadata": { - "description": "Optional. Time in minutes before event at which notification will be sent. Optional if \"notificationSettingsStatus\" is set to \"Enabled\". Default is 30 minutes." + "description": "Optional. The notification settings for the schedule." } } }, @@ -125,13 +214,13 @@ "tags": "[parameters('tags')]", "properties": { "taskType": "[parameters('taskType')]", - "dailyRecurrence": "[if(not(empty(parameters('dailyRecurrence'))), parameters('dailyRecurrence'), null())]", - "hourlyRecurrence": "[if(not(empty(parameters('hourlyRecurrence'))), parameters('hourlyRecurrence'), null())]", - "weeklyRecurrence": "[if(not(empty(parameters('weeklyRecurrence'))), parameters('weeklyRecurrence'), null())]", + "dailyRecurrence": "[parameters('dailyRecurrence')]", + "hourlyRecurrence": "[parameters('hourlyRecurrence')]", + "weeklyRecurrence": "[parameters('weeklyRecurrence')]", "status": "[parameters('status')]", - "targetResourceId": "[if(not(empty(parameters('targetResourceId'))), parameters('targetResourceId'), null())]", + "targetResourceId": "[parameters('targetResourceId')]", "timeZoneId": "[parameters('timeZoneId')]", - "notificationSettings": "[if(equals(parameters('notificationSettingsStatus'), 'Enabled'), createObject('status', parameters('notificationSettingsStatus'), 'timeInMinutes', parameters('notificationSettingsTimeInMinutes')), createObject())]" + "notificationSettings": "[parameters('notificationSettings')]" }, "dependsOn": [ "lab" diff --git a/avm/res/dev-test-lab/lab/tests/e2e/max/main.test.bicep b/avm/res/dev-test-lab/lab/tests/e2e/max/main.test.bicep index dcab1da10f..65d06f3e05 100644 --- a/avm/res/dev-test-lab/lab/tests/e2e/max/main.test.bicep +++ b/avm/res/dev-test-lab/lab/tests/e2e/max/main.test.bicep @@ -226,8 +226,10 @@ module testDeployment '../../../main.bicep' = [ dailyRecurrence: { time: '0000' } - notificationSettingsStatus: 'Enabled' - notificationSettingsTimeInMinutes: 30 + notificationSettings: { + status: 'Enabled' + timeInMinutes: 30 + } } { name: 'LabVmAutoStart' @@ -251,9 +253,7 @@ module testDeployment '../../../main.bicep' = [ name: 'autoShutdown' description: 'Integration configured for auto-shutdown' events: [ - { - eventName: 'AutoShutdown' - } + 'AutoShutdown' ] emailRecipient: 'mail@contosodtlmail.com' webHookUrl: 'https://webhook.contosotest.com' @@ -262,9 +262,7 @@ module testDeployment '../../../main.bicep' = [ { name: 'costThreshold' events: [ - { - eventName: 'Cost' - } + 'Cost' ] webHookUrl: 'https://webhook.contosotest.com' } @@ -273,10 +271,9 @@ module testDeployment '../../../main.bicep' = [ { name: 'Public Repo' displayName: 'Public Artifact Repo' - status: 'Disabled' + status: 'Enabled' uri: 'https://github.com/Azure/azure-devtestlab.git' sourceType: 'GitHub' - branchRef: 'master' folderPath: '/Artifacts' } { @@ -287,12 +284,28 @@ module testDeployment '../../../main.bicep' = [ sourceType: 'GitHub' branchRef: 'master' armTemplateFolderPath: '/Environments' + tags: { + 'hidden-title': 'This is visible in the resource name' + resourceType: 'DevTest Lab' + labName: '${namePrefix}${serviceShort}001' + } + } + { + name: 'Private Repo' + displayName: 'Private Artifact Repo' + status: 'Disabled' + uri: 'https://github.com/Azure/azure-devtestlab.git' + folderPath: '/Artifacts' + armTemplateFolderPath: '/ArmTemplates' + branchRef: 'main' + securityToken: guid(baseTime) } ] costs: { status: 'Enabled' cycleType: 'CalendarMonth' target: 450 + currencyCode: 'AUD' thresholdValue100DisplayOnChart: 'Enabled' thresholdValue100SendNotificationWhenExceeded: 'Enabled' } diff --git a/avm/res/dev-test-lab/lab/version.json b/avm/res/dev-test-lab/lab/version.json index 1c035df49f..b3d560b1ad 100644 --- a/avm/res/dev-test-lab/lab/version.json +++ b/avm/res/dev-test-lab/lab/version.json @@ -1,7 +1,7 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.2", + "version": "0.3", "pathFilters": [ "./main.json" ] -} \ No newline at end of file +} diff --git a/avm/res/dev-test-lab/lab/virtualnetwork/main.bicep b/avm/res/dev-test-lab/lab/virtualnetwork/main.bicep index b73bab89b6..07e8f88432 100644 --- a/avm/res/dev-test-lab/lab/virtualnetwork/main.bicep +++ b/avm/res/dev-test-lab/lab/virtualnetwork/main.bicep @@ -49,3 +49,49 @@ output resourceId string = virtualNetwork.id @sys.description('The name of the resource group the lab virtual network was created in.') output resourceGroupName string = resourceGroup().name + +// =============== // +// Definitions // +// =============== // + +@export() +type allowedSubnetType = { + @sys.description('Optional. The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny)).') + allowPublicIp: 'Allow' | 'Deny' | 'Default'? + + @sys.description('Required. The resource ID of the allowed subnet.') + resourceId: string + + @sys.description('Required. The name of the subnet as seen in the lab.') + labSubnetName: string +} + +@export() +type subnetOverrideType = { + @sys.description('Required. The name given to the subnet within the lab.') + labSubnetName: string + + @sys.description('Required. The resource ID of the subnet.') + resourceId: string + + @sys.description('Optional. The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny)).') + sharedPublicIpAddressConfiguration: { + @sys.description('Required. Backend ports that virtual machines on this subnet are allowed to expose.') + allowedPorts: { + @sys.description('Required. Backend port of the target virtual machine.') + backendPort: int + + @sys.description('Required. Protocol type of the port.') + transportProtocol: 'Tcp' | 'Udp' + }[] + } + + @sys.description('Optional. Indicates whether this subnet can be used during virtual machine creation (i.e. Allow, Deny).') + useInVmCreationPermission: 'Allow' | 'Deny' | 'Default'? + + @sys.description('Optional. Indicates whether public IP addresses can be assigned to virtual machines on this subnet (i.e. Allow, Deny).') + usePublicIpAddressPermission: 'Allow' | 'Deny' | 'Default'? + + @sys.description('Optional. The virtual network pool associated with this subnet.') + virtualNetworkPoolName: string? +} diff --git a/avm/res/dev-test-lab/lab/virtualnetwork/main.json b/avm/res/dev-test-lab/lab/virtualnetwork/main.json index 608e44f7a1..1eab7d68eb 100644 --- a/avm/res/dev-test-lab/lab/virtualnetwork/main.json +++ b/avm/res/dev-test-lab/lab/virtualnetwork/main.json @@ -6,12 +6,132 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "1030641179919111475" + "templateHash": "18309816581107210302" }, "name": "DevTest Lab Virtual Networks", - "description": "This module deploys a DevTest Lab Virtual Network.\n\nLab virtual machines must be deployed into a virtual network. This resource type allows configuring the virtual network and subnet settings used for the lab virtual machines.", + "description": "This module deploys a DevTest Lab Virtual Network.\r\n\r\nLab virtual machines must be deployed into a virtual network. This resource type allows configuring the virtual network and subnet settings used for the lab virtual machines.", "owner": "Azure/module-maintainers" }, + "definitions": { + "allowedSubnetType": { + "type": "object", + "properties": { + "allowPublicIp": { + "type": "string", + "allowedValues": [ + "Allow", + "Default", + "Deny" + ], + "nullable": true, + "metadata": { + "description": "Optional. The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny))." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the allowed subnet." + } + }, + "labSubnetName": { + "type": "string", + "metadata": { + "description": "Required. The name of the subnet as seen in the lab." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "subnetOverrideType": { + "type": "object", + "properties": { + "labSubnetName": { + "type": "string", + "metadata": { + "description": "Required. The name given to the subnet within the lab." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the subnet." + } + }, + "sharedPublicIpAddressConfiguration": { + "type": "object", + "properties": { + "allowedPorts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "backendPort": { + "type": "int", + "metadata": { + "description": "Required. Backend port of the target virtual machine." + } + }, + "transportProtocol": { + "type": "string", + "allowedValues": [ + "Tcp", + "Udp" + ], + "metadata": { + "description": "Required. Protocol type of the port." + } + } + } + }, + "metadata": { + "description": "Required. Backend ports that virtual machines on this subnet are allowed to expose." + } + } + }, + "metadata": { + "description": "Optional. The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny))." + } + }, + "useInVmCreationPermission": { + "type": "string", + "allowedValues": [ + "Allow", + "Default", + "Deny" + ], + "nullable": true, + "metadata": { + "description": "Optional. Indicates whether this subnet can be used during virtual machine creation (i.e. Allow, Deny)." + } + }, + "usePublicIpAddressPermission": { + "type": "string", + "allowedValues": [ + "Allow", + "Default", + "Deny" + ], + "nullable": true, + "metadata": { + "description": "Optional. Indicates whether public IP addresses can be assigned to virtual machines on this subnet (i.e. Allow, Deny)." + } + }, + "virtualNetworkPoolName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The virtual network pool associated with this subnet." + } + } + }, + "metadata": { + "__bicep_export!": true + } + } + }, "parameters": { "labName": { "type": "string",