From 1c8d1c23aee68d79dd5a226dd7f23bd098cda371 Mon Sep 17 00:00:00 2001 From: Luke Snoddy <37806411+lsnoddy@users.noreply.github.com> Date: Sat, 21 Dec 2024 12:18:02 -0700 Subject: [PATCH] feat: avm.res.service-fabric.cluster additional parameter validation (#3925) ## Description Add a new test case to validate the clientCertificateThumbprints and sCertificateCommonNames parameters together. A new test case was required as this parameter combination could not be used with parameter sets in the other test cases. Also converted all instances of the "contains" keyword to use the safe access operator. Closes #2346 ## Pipeline Reference | Pipeline | | -------- | [![avm.res.service-fabric.cluster](https://github.com/lsnoddy/bicep-registry-modules/actions/workflows/avm.res.service-fabric.cluster.yml/badge.svg?branch=users%2Flsnoddy%2Fservice-fabric-cluster-testcase)](https://github.com/lsnoddy/bicep-registry-modules/actions/workflows/avm.res.service-fabric.cluster.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. - [ ] 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/service-fabric/cluster/README.md | 189 +++++++++++++++++- .../cluster/application-type/main.json | 9 +- avm/res/service-fabric/cluster/main.bicep | 142 +++++-------- avm/res/service-fabric/cluster/main.json | 65 +++--- .../cluster/tests/e2e/cert/main.test.bicep | 82 ++++++++ avm/res/service-fabric/cluster/version.json | 4 +- 6 files changed, 349 insertions(+), 142 deletions(-) create mode 100644 avm/res/service-fabric/cluster/tests/e2e/cert/main.test.bicep diff --git a/avm/res/service-fabric/cluster/README.md b/avm/res/service-fabric/cluster/README.md index de02d4aa07..b68c0f4304 100644 --- a/avm/res/service-fabric/cluster/README.md +++ b/avm/res/service-fabric/cluster/README.md @@ -27,11 +27,188 @@ The following section provides usage examples for the module, which were used to >**Note**: To reference the module, please use the following syntax `br/public:avm/res/service-fabric/cluster:`. -- [Using only defaults](#example-1-using-only-defaults) -- [Using large parameter set](#example-2-using-large-parameter-set) -- [WAF-aligned](#example-3-waf-aligned) +- [Using client and server certificate parameter set](#example-1-using-client-and-server-certificate-parameter-set) +- [Using only defaults](#example-2-using-only-defaults) +- [Using large parameter set](#example-3-using-large-parameter-set) +- [WAF-aligned](#example-4-waf-aligned) -### Example 1: _Using only defaults_ +### Example 1: _Using client and server certificate parameter set_ + +This instance deploys the module with client and server certificates using thumbprints and common names. + + +
+ +via Bicep module + +```bicep +module cluster 'br/public:avm/res/service-fabric/cluster:' = { + name: 'clusterDeployment' + params: { + // Required parameters + managementEndpoint: 'https://sfccrt001.westeurope.cloudapp.azure.com:19080' + name: 'sfccrt001' + nodeTypes: [ + { + applicationPorts: { + endPort: 30000 + startPort: 20000 + } + clientConnectionEndpointPort: 19000 + durabilityLevel: 'Bronze' + ephemeralPorts: { + endPort: 65534 + startPort: 49152 + } + httpGatewayEndpointPort: 19080 + isPrimary: true + name: 'Node01' + } + ] + reliabilityLevel: 'None' + // Non-required parameters + certificateCommonNames: { + commonNames: [ + { + certificateCommonName: 'certcommon' + certificateIssuerThumbprint: '0AC113D5E1D94C401DDEB0EE2B1B96CC130' + } + ] + x509StoreName: 'My' + } + clientCertificateThumbprints: [ + { + certificateThumbprint: 'D945B0AC4BDF78D31FB6F09CF375E0B9DC7BBBBE' + isAdmin: true + } + ] + location: '' + } +} +``` + +
+

+ +

+ +via JSON parameters file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "managementEndpoint": { + "value": "https://sfccrt001.westeurope.cloudapp.azure.com:19080" + }, + "name": { + "value": "sfccrt001" + }, + "nodeTypes": { + "value": [ + { + "applicationPorts": { + "endPort": 30000, + "startPort": 20000 + }, + "clientConnectionEndpointPort": 19000, + "durabilityLevel": "Bronze", + "ephemeralPorts": { + "endPort": 65534, + "startPort": 49152 + }, + "httpGatewayEndpointPort": 19080, + "isPrimary": true, + "name": "Node01" + } + ] + }, + "reliabilityLevel": { + "value": "None" + }, + // Non-required parameters + "certificateCommonNames": { + "value": { + "commonNames": [ + { + "certificateCommonName": "certcommon", + "certificateIssuerThumbprint": "0AC113D5E1D94C401DDEB0EE2B1B96CC130" + } + ], + "x509StoreName": "My" + } + }, + "clientCertificateThumbprints": { + "value": [ + { + "certificateThumbprint": "D945B0AC4BDF78D31FB6F09CF375E0B9DC7BBBBE", + "isAdmin": true + } + ] + }, + "location": { + "value": "" + } + } +} +``` + +
+

+ +

+ +via Bicep parameters file + +```bicep-params +using 'br/public:avm/res/service-fabric/cluster:' + +// Required parameters +param managementEndpoint = 'https://sfccrt001.westeurope.cloudapp.azure.com:19080' +param name = 'sfccrt001' +param nodeTypes = [ + { + applicationPorts: { + endPort: 30000 + startPort: 20000 + } + clientConnectionEndpointPort: 19000 + durabilityLevel: 'Bronze' + ephemeralPorts: { + endPort: 65534 + startPort: 49152 + } + httpGatewayEndpointPort: 19080 + isPrimary: true + name: 'Node01' + } +] +param reliabilityLevel = 'None' +// Non-required parameters +param certificateCommonNames = { + commonNames: [ + { + certificateCommonName: 'certcommon' + certificateIssuerThumbprint: '0AC113D5E1D94C401DDEB0EE2B1B96CC130' + } + ] + x509StoreName: 'My' +} +param clientCertificateThumbprints = [ + { + certificateThumbprint: 'D945B0AC4BDF78D31FB6F09CF375E0B9DC7BBBBE' + isAdmin: true + } +] +param location = '' +``` + +
+

+ +### Example 2: _Using only defaults_ This instance deploys the module with the minimum set of required parameters. @@ -169,7 +346,7 @@ param location = ''

-### Example 2: _Using large parameter set_ +### Example 3: _Using large parameter set_ This instance deploys the module with most of its features enabled. @@ -768,7 +945,7 @@ param vmImage = 'Linux'

-### Example 3: _WAF-aligned_ +### Example 4: _WAF-aligned_ This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework. diff --git a/avm/res/service-fabric/cluster/application-type/main.json b/avm/res/service-fabric/cluster/application-type/main.json index b9a413bced..ca487cacbd 100644 --- a/avm/res/service-fabric/cluster/application-type/main.json +++ b/avm/res/service-fabric/cluster/application-type/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "8632559494589711330" + "version": "0.32.4.45862", + "templateHash": "4770865885039578272" }, "name": "Service Fabric Cluster Application Types", "description": "This module deploys a Service Fabric Cluster Application Type.", @@ -45,10 +45,7 @@ "type": "Microsoft.ServiceFabric/clusters/applicationTypes", "apiVersion": "2021-06-01", "name": "[format('{0}/{1}', parameters('serviceFabricClusterName'), parameters('name'))]", - "tags": "[parameters('tags')]", - "dependsOn": [ - "serviceFabricCluster" - ] + "tags": "[parameters('tags')]" } }, "outputs": { diff --git a/avm/res/service-fabric/cluster/main.bicep b/avm/res/service-fabric/cluster/main.bicep index 41089bc7ac..1ca73c25c4 100644 --- a/avm/res/service-fabric/cluster/main.bicep +++ b/avm/res/service-fabric/cluster/main.bicep @@ -151,8 +151,8 @@ var clientCertificateThumbprintsVar = [ var fabricSettingsVar = [ for fabricSetting in fabricSettings: { - name: contains(fabricSetting, 'name') ? fabricSetting.name : null - parameters: contains(fabricSetting, 'parameters') ? fabricSetting.parameters : null + name: fabricSetting.?name + parameters: fabricSetting.?parameters } ] @@ -160,42 +160,36 @@ var fnodeTypesVar = [ for nodeType in nodeTypes: { applicationPorts: contains(nodeType, 'applicationPorts') ? { - endPort: contains(nodeType.applicationPorts, 'endPort') ? nodeType.applicationPorts.endPort : null - startPort: contains(nodeType.applicationPorts, 'startPort') ? nodeType.applicationPorts.startPort : null + endPort: nodeType.applicationPorts.?endPort + startPort: nodeType.applicationPorts.?startPort } : null - capacities: contains(nodeType, 'capacities') ? nodeType.capacities : null - clientConnectionEndpointPort: contains(nodeType, 'clientConnectionEndpointPort') - ? nodeType.clientConnectionEndpointPort - : null - durabilityLevel: contains(nodeType, 'durabilityLevel') ? nodeType.durabilityLevel : null + capacities: nodeType.?capacities + clientConnectionEndpointPort: nodeType.?clientConnectionEndpointPort + durabilityLevel: nodeType.?durabilityLevel ephemeralPorts: contains(nodeType, 'ephemeralPorts') ? { - endPort: contains(nodeType.ephemeralPorts, 'endPort') ? nodeType.ephemeralPorts.endPort : null - startPort: contains(nodeType.ephemeralPorts, 'startPort') ? nodeType.ephemeralPorts.startPort : null + endPort: nodeType.ephemeralPorts.?endPort + startPort: nodeType.ephemeralPorts.?startPort } : null - httpGatewayEndpointPort: contains(nodeType, 'httpGatewayEndpointPort') ? nodeType.httpGatewayEndpointPort : null - isPrimary: contains(nodeType, 'isPrimary') ? nodeType.isPrimary : null - isStateless: contains(nodeType, 'isStateless') ? nodeType.isStateless : null - multipleAvailabilityZones: contains(nodeType, 'multipleAvailabilityZones') - ? nodeType.multipleAvailabilityZones - : null - name: contains(nodeType, 'name') ? nodeType.name : 'Node00' - placementProperties: contains(nodeType, 'placementProperties') ? nodeType.placementProperties : null - reverseProxyEndpointPort: contains(nodeType, 'reverseProxyEndpointPort') ? nodeType.reverseProxyEndpointPort : null - vmInstanceCount: contains(nodeType, 'vmInstanceCount') ? nodeType.vmInstanceCount : 1 + httpGatewayEndpointPort: nodeType.?httpGatewayEndpointPort + isPrimary: nodeType.?isPrimary + isStateless: nodeType.?isStateless + multipleAvailabilityZones: nodeType.?multipleAvailabilityZones + name: nodeType.?name ?? 'Node00' + placementProperties: nodeType.?placementProperties + reverseProxyEndpointPort: nodeType.?reverseProxyEndpointPort + vmInstanceCount: nodeType.?vmInstanceCount ?? 1 } ] var notificationsVar = [ for notification in notifications: { - isEnabled: contains(notification, 'isEnabled') ? notification.isEnabled : false - notificationCategory: contains(notification, 'notificationCategory') - ? notification.notificationCategory - : 'WaveProgress' - notificationLevel: contains(notification, 'notificationLevel') ? notification.notificationLevel : 'All' - notificationTargets: contains(notification, 'notificationTargets') ? notification.notificationTargets : [] + isEnabled: notification.?isEnabled ?? false + notificationCategory: notification.?notificationCategory ?? 'WaveProgress' + notificationLevel: notification.?notificationLevel ?? 'All' + notificationTargets: notification.?notificationTargets ?? [] } ] @@ -207,36 +201,20 @@ var upgradeDescriptionVar = union( maxPercentDeltaUnhealthyNodes: upgradeDescription.?maxPercentDeltaUnhealthyNodes ?? 0 maxPercentUpgradeDomainDeltaUnhealthyNodes: upgradeDescription.?maxPercentUpgradeDomainDeltaUnhealthyNodes ?? 0 } - forceRestart: contains(upgradeDescription, 'forceRestart') ? upgradeDescription.forceRestart : false - healthCheckRetryTimeout: contains(upgradeDescription, 'healthCheckRetryTimeout') - ? upgradeDescription.healthCheckRetryTimeout - : '00:45:00' - healthCheckStableDuration: contains(upgradeDescription, 'healthCheckStableDuration') - ? upgradeDescription.healthCheckStableDuration - : '00:01:00' - healthCheckWaitDuration: contains(upgradeDescription, 'healthCheckWaitDuration') - ? upgradeDescription.healthCheckWaitDuration - : '00:00:30' - upgradeDomainTimeout: contains(upgradeDescription, 'upgradeDomainTimeout') - ? upgradeDescription.upgradeDomainTimeout - : '02:00:00' - upgradeReplicaSetCheckTimeout: contains(upgradeDescription, 'upgradeReplicaSetCheckTimeout') - ? upgradeDescription.upgradeReplicaSetCheckTimeout - : '1.00:00:00' - upgradeTimeout: contains(upgradeDescription, 'upgradeTimeout') ? upgradeDescription.upgradeTimeout : '02:00:00' + forceRestart: upgradeDescription.?forceRestart ?? false + healthCheckRetryTimeout: upgradeDescription.?healthCheckRetryTimeout ?? '00:45:00' + healthCheckStableDuration: upgradeDescription.?healthCheckStableDuration ?? '00:01:00' + healthCheckWaitDuration: upgradeDescription.?healthCheckWaitDuration ?? '00:00:30' + upgradeDomainTimeout: upgradeDescription.?upgradeDomainTimeout ?? '02:00:00' + upgradeReplicaSetCheckTimeout: upgradeDescription.?upgradeReplicaSetCheckTimeout ?? '1.00:00:00' + upgradeTimeout: upgradeDescription.?upgradeTimeout ?? '02:00:00' }, contains(upgradeDescription, 'healthPolicy') ? { healthPolicy: { - applicationHealthPolicies: contains(upgradeDescription.healthPolicy, 'applicationHealthPolicies') - ? upgradeDescription.healthPolicy.applicationHealthPolicies - : {} - maxPercentUnhealthyApplications: contains(upgradeDescription.healthPolicy, 'maxPercentUnhealthyApplications') - ? upgradeDescription.healthPolicy.maxPercentUnhealthyApplications - : 0 - maxPercentUnhealthyNodes: contains(upgradeDescription.healthPolicy, 'maxPercentUnhealthyNodes') - ? upgradeDescription.healthPolicy.maxPercentUnhealthyNodes - : 0 + applicationHealthPolicies: upgradeDescription.healthPolicy.?applicationHealthPolicies ?? {} + maxPercentUnhealthyApplications: upgradeDescription.healthPolicy.?maxPercentUnhealthyApplications ?? 0 + maxPercentUnhealthyNodes: upgradeDescription.healthPolicy.?maxPercentUnhealthyNodes ?? 0 } } : {} @@ -298,26 +276,22 @@ resource serviceFabricCluster 'Microsoft.ServiceFabric/clusters@2021-06-01' = { } azureActiveDirectory: !empty(azureActiveDirectory) ? { - clientApplication: contains(azureActiveDirectory, 'clientApplication') - ? azureActiveDirectory.clientApplication - : null - clusterApplication: contains(azureActiveDirectory, 'clusterApplication') - ? azureActiveDirectory.clusterApplication - : null - tenantId: contains(azureActiveDirectory, 'tenantId') ? azureActiveDirectory.tenantId : null + clientApplication: azureActiveDirectory.?clientApplication + clusterApplication: azureActiveDirectory.?clusterApplication + tenantId: azureActiveDirectory.?tenantId } : null certificate: !empty(certificate) ? { thumbprint: certificate.?thumbprint ?? '' - thumbprintSecondary: certificate.?thumbprintSecondary ?? null - x509StoreName: certificate.?x509StoreName ?? null + thumbprintSecondary: certificate.?thumbprintSecondary + x509StoreName: certificate.?x509StoreName } : null certificateCommonNames: !empty(certificateCommonNames) ? { commonNames: certificateCommonNames.?commonNames ?? [] - x509StoreName: certificateCommonNames.?x509StoreName ?? null + x509StoreName: certificateCommonNames.?x509StoreName } : null clientCertificateCommonNames: clientCertificateCommonNamesVar @@ -325,24 +299,12 @@ resource serviceFabricCluster 'Microsoft.ServiceFabric/clusters@2021-06-01' = { clusterCodeVersion: clusterCodeVersion diagnosticsStorageAccountConfig: !empty(diagnosticsStorageAccountConfig) ? { - blobEndpoint: contains(diagnosticsStorageAccountConfig, 'blobEndpoint') - ? diagnosticsStorageAccountConfig.blobEndpoint - : null - protectedAccountKeyName: contains(diagnosticsStorageAccountConfig, 'protectedAccountKeyName') - ? diagnosticsStorageAccountConfig.protectedAccountKeyName - : null - protectedAccountKeyName2: contains(diagnosticsStorageAccountConfig, 'protectedAccountKeyName2') - ? diagnosticsStorageAccountConfig.protectedAccountKeyName2 - : null - queueEndpoint: contains(diagnosticsStorageAccountConfig, 'queueEndpoint') - ? diagnosticsStorageAccountConfig.queueEndpoint - : null - storageAccountName: contains(diagnosticsStorageAccountConfig, 'storageAccountName') - ? diagnosticsStorageAccountConfig.storageAccountName - : null - tableEndpoint: contains(diagnosticsStorageAccountConfig, 'tableEndpoint') - ? diagnosticsStorageAccountConfig.tableEndpoint - : null + blobEndpoint: diagnosticsStorageAccountConfig.?blobEndpoint + protectedAccountKeyName: diagnosticsStorageAccountConfig.?protectedAccountKeyName + protectedAccountKeyName2: diagnosticsStorageAccountConfig.?protectedAccountKeyName2 + queueEndpoint: diagnosticsStorageAccountConfig.?queueEndpoint + storageAccountName: diagnosticsStorageAccountConfig.?storageAccountName + tableEndpoint: diagnosticsStorageAccountConfig.?tableEndpoint } : null eventStoreServiceEnabled: eventStoreServiceEnabled @@ -354,23 +316,15 @@ resource serviceFabricCluster 'Microsoft.ServiceFabric/clusters@2021-06-01' = { reliabilityLevel: !empty(reliabilityLevel) ? reliabilityLevel : 'None' reverseProxyCertificate: !empty(reverseProxyCertificate) ? { - thumbprint: contains(reverseProxyCertificate, 'thumbprint') ? reverseProxyCertificate.thumbprint : null - thumbprintSecondary: contains(reverseProxyCertificate, 'thumbprintSecondary') - ? reverseProxyCertificate.thumbprintSecondary - : null - x509StoreName: contains(reverseProxyCertificate, 'x509StoreName') - ? reverseProxyCertificate.x509StoreName - : null + thumbprint: reverseProxyCertificate.?thumbprint + thumbprintSecondary: reverseProxyCertificate.?thumbprintSecondary + x509StoreName: reverseProxyCertificate.?x509StoreName } : null reverseProxyCertificateCommonNames: !empty(reverseProxyCertificateCommonNames) ? { - commonNames: contains(reverseProxyCertificateCommonNames, 'commonNames') - ? reverseProxyCertificateCommonNames.commonNames - : null - x509StoreName: contains(reverseProxyCertificateCommonNames, 'x509StoreName') - ? reverseProxyCertificateCommonNames.x509StoreName - : null + commonNames: reverseProxyCertificateCommonNames.?commonNames + x509StoreName: reverseProxyCertificateCommonNames.?x509StoreName } : null sfZonalUpgradeMode: !empty(sfZonalUpgradeMode) ? sfZonalUpgradeMode : null diff --git a/avm/res/service-fabric/cluster/main.json b/avm/res/service-fabric/cluster/main.json index 3f0955fb78..82372f8878 100644 --- a/avm/res/service-fabric/cluster/main.json +++ b/avm/res/service-fabric/cluster/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "11160466068108868601" + "version": "0.32.4.45862", + "templateHash": "5756653663103242994" }, "name": "Service Fabric Clusters", "description": "This module deploys a Service Fabric Cluster.", @@ -528,37 +528,37 @@ "name": "fabricSettingsVar", "count": "[length(parameters('fabricSettings'))]", "input": { - "name": "[if(contains(parameters('fabricSettings')[copyIndex('fabricSettingsVar')], 'name'), parameters('fabricSettings')[copyIndex('fabricSettingsVar')].name, null())]", - "parameters": "[if(contains(parameters('fabricSettings')[copyIndex('fabricSettingsVar')], 'parameters'), parameters('fabricSettings')[copyIndex('fabricSettingsVar')].parameters, null())]" + "name": "[tryGet(parameters('fabricSettings')[copyIndex('fabricSettingsVar')], 'name')]", + "parameters": "[tryGet(parameters('fabricSettings')[copyIndex('fabricSettingsVar')], 'parameters')]" } }, { "name": "fnodeTypesVar", "count": "[length(parameters('nodeTypes'))]", "input": { - "applicationPorts": "[if(contains(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'applicationPorts'), createObject('endPort', if(contains(parameters('nodeTypes')[copyIndex('fnodeTypesVar')].applicationPorts, 'endPort'), parameters('nodeTypes')[copyIndex('fnodeTypesVar')].applicationPorts.endPort, null()), 'startPort', if(contains(parameters('nodeTypes')[copyIndex('fnodeTypesVar')].applicationPorts, 'startPort'), parameters('nodeTypes')[copyIndex('fnodeTypesVar')].applicationPorts.startPort, null())), null())]", - "capacities": "[if(contains(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'capacities'), parameters('nodeTypes')[copyIndex('fnodeTypesVar')].capacities, null())]", - "clientConnectionEndpointPort": "[if(contains(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'clientConnectionEndpointPort'), parameters('nodeTypes')[copyIndex('fnodeTypesVar')].clientConnectionEndpointPort, null())]", - "durabilityLevel": "[if(contains(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'durabilityLevel'), parameters('nodeTypes')[copyIndex('fnodeTypesVar')].durabilityLevel, null())]", - "ephemeralPorts": "[if(contains(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'ephemeralPorts'), createObject('endPort', if(contains(parameters('nodeTypes')[copyIndex('fnodeTypesVar')].ephemeralPorts, 'endPort'), parameters('nodeTypes')[copyIndex('fnodeTypesVar')].ephemeralPorts.endPort, null()), 'startPort', if(contains(parameters('nodeTypes')[copyIndex('fnodeTypesVar')].ephemeralPorts, 'startPort'), parameters('nodeTypes')[copyIndex('fnodeTypesVar')].ephemeralPorts.startPort, null())), null())]", - "httpGatewayEndpointPort": "[if(contains(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'httpGatewayEndpointPort'), parameters('nodeTypes')[copyIndex('fnodeTypesVar')].httpGatewayEndpointPort, null())]", - "isPrimary": "[if(contains(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'isPrimary'), parameters('nodeTypes')[copyIndex('fnodeTypesVar')].isPrimary, null())]", - "isStateless": "[if(contains(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'isStateless'), parameters('nodeTypes')[copyIndex('fnodeTypesVar')].isStateless, null())]", - "multipleAvailabilityZones": "[if(contains(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'multipleAvailabilityZones'), parameters('nodeTypes')[copyIndex('fnodeTypesVar')].multipleAvailabilityZones, null())]", - "name": "[if(contains(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'name'), parameters('nodeTypes')[copyIndex('fnodeTypesVar')].name, 'Node00')]", - "placementProperties": "[if(contains(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'placementProperties'), parameters('nodeTypes')[copyIndex('fnodeTypesVar')].placementProperties, null())]", - "reverseProxyEndpointPort": "[if(contains(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'reverseProxyEndpointPort'), parameters('nodeTypes')[copyIndex('fnodeTypesVar')].reverseProxyEndpointPort, null())]", - "vmInstanceCount": "[if(contains(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'vmInstanceCount'), parameters('nodeTypes')[copyIndex('fnodeTypesVar')].vmInstanceCount, 1)]" + "applicationPorts": "[if(contains(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'applicationPorts'), createObject('endPort', tryGet(parameters('nodeTypes')[copyIndex('fnodeTypesVar')].applicationPorts, 'endPort'), 'startPort', tryGet(parameters('nodeTypes')[copyIndex('fnodeTypesVar')].applicationPorts, 'startPort')), null())]", + "capacities": "[tryGet(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'capacities')]", + "clientConnectionEndpointPort": "[tryGet(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'clientConnectionEndpointPort')]", + "durabilityLevel": "[tryGet(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'durabilityLevel')]", + "ephemeralPorts": "[if(contains(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'ephemeralPorts'), createObject('endPort', tryGet(parameters('nodeTypes')[copyIndex('fnodeTypesVar')].ephemeralPorts, 'endPort'), 'startPort', tryGet(parameters('nodeTypes')[copyIndex('fnodeTypesVar')].ephemeralPorts, 'startPort')), null())]", + "httpGatewayEndpointPort": "[tryGet(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'httpGatewayEndpointPort')]", + "isPrimary": "[tryGet(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'isPrimary')]", + "isStateless": "[tryGet(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'isStateless')]", + "multipleAvailabilityZones": "[tryGet(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'multipleAvailabilityZones')]", + "name": "[coalesce(tryGet(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'name'), 'Node00')]", + "placementProperties": "[tryGet(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'placementProperties')]", + "reverseProxyEndpointPort": "[tryGet(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'reverseProxyEndpointPort')]", + "vmInstanceCount": "[coalesce(tryGet(parameters('nodeTypes')[copyIndex('fnodeTypesVar')], 'vmInstanceCount'), 1)]" } }, { "name": "notificationsVar", "count": "[length(parameters('notifications'))]", "input": { - "isEnabled": "[if(contains(parameters('notifications')[copyIndex('notificationsVar')], 'isEnabled'), parameters('notifications')[copyIndex('notificationsVar')].isEnabled, false())]", - "notificationCategory": "[if(contains(parameters('notifications')[copyIndex('notificationsVar')], 'notificationCategory'), parameters('notifications')[copyIndex('notificationsVar')].notificationCategory, 'WaveProgress')]", - "notificationLevel": "[if(contains(parameters('notifications')[copyIndex('notificationsVar')], 'notificationLevel'), parameters('notifications')[copyIndex('notificationsVar')].notificationLevel, 'All')]", - "notificationTargets": "[if(contains(parameters('notifications')[copyIndex('notificationsVar')], 'notificationTargets'), parameters('notifications')[copyIndex('notificationsVar')].notificationTargets, createArray())]" + "isEnabled": "[coalesce(tryGet(parameters('notifications')[copyIndex('notificationsVar')], 'isEnabled'), false())]", + "notificationCategory": "[coalesce(tryGet(parameters('notifications')[copyIndex('notificationsVar')], 'notificationCategory'), 'WaveProgress')]", + "notificationLevel": "[coalesce(tryGet(parameters('notifications')[copyIndex('notificationsVar')], 'notificationLevel'), 'All')]", + "notificationTargets": "[coalesce(tryGet(parameters('notifications')[copyIndex('notificationsVar')], 'notificationTargets'), createArray())]" } }, { @@ -567,7 +567,7 @@ "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" } ], - "upgradeDescriptionVar": "[union(createObject('deltaHealthPolicy', createObject('applicationDeltaHealthPolicies', coalesce(tryGet(parameters('upgradeDescription'), 'applicationDeltaHealthPolicies'), createObject()), 'maxPercentDeltaUnhealthyApplications', coalesce(tryGet(parameters('upgradeDescription'), 'maxPercentDeltaUnhealthyApplications'), 0), 'maxPercentDeltaUnhealthyNodes', coalesce(tryGet(parameters('upgradeDescription'), 'maxPercentDeltaUnhealthyNodes'), 0), 'maxPercentUpgradeDomainDeltaUnhealthyNodes', coalesce(tryGet(parameters('upgradeDescription'), 'maxPercentUpgradeDomainDeltaUnhealthyNodes'), 0)), 'forceRestart', if(contains(parameters('upgradeDescription'), 'forceRestart'), parameters('upgradeDescription').forceRestart, false()), 'healthCheckRetryTimeout', if(contains(parameters('upgradeDescription'), 'healthCheckRetryTimeout'), parameters('upgradeDescription').healthCheckRetryTimeout, '00:45:00'), 'healthCheckStableDuration', if(contains(parameters('upgradeDescription'), 'healthCheckStableDuration'), parameters('upgradeDescription').healthCheckStableDuration, '00:01:00'), 'healthCheckWaitDuration', if(contains(parameters('upgradeDescription'), 'healthCheckWaitDuration'), parameters('upgradeDescription').healthCheckWaitDuration, '00:00:30'), 'upgradeDomainTimeout', if(contains(parameters('upgradeDescription'), 'upgradeDomainTimeout'), parameters('upgradeDescription').upgradeDomainTimeout, '02:00:00'), 'upgradeReplicaSetCheckTimeout', if(contains(parameters('upgradeDescription'), 'upgradeReplicaSetCheckTimeout'), parameters('upgradeDescription').upgradeReplicaSetCheckTimeout, '1.00:00:00'), 'upgradeTimeout', if(contains(parameters('upgradeDescription'), 'upgradeTimeout'), parameters('upgradeDescription').upgradeTimeout, '02:00:00')), if(contains(parameters('upgradeDescription'), 'healthPolicy'), createObject('healthPolicy', createObject('applicationHealthPolicies', if(contains(parameters('upgradeDescription').healthPolicy, 'applicationHealthPolicies'), parameters('upgradeDescription').healthPolicy.applicationHealthPolicies, createObject()), 'maxPercentUnhealthyApplications', if(contains(parameters('upgradeDescription').healthPolicy, 'maxPercentUnhealthyApplications'), parameters('upgradeDescription').healthPolicy.maxPercentUnhealthyApplications, 0), 'maxPercentUnhealthyNodes', if(contains(parameters('upgradeDescription').healthPolicy, 'maxPercentUnhealthyNodes'), parameters('upgradeDescription').healthPolicy.maxPercentUnhealthyNodes, 0))), createObject()))]", + "upgradeDescriptionVar": "[union(createObject('deltaHealthPolicy', createObject('applicationDeltaHealthPolicies', coalesce(tryGet(parameters('upgradeDescription'), 'applicationDeltaHealthPolicies'), createObject()), 'maxPercentDeltaUnhealthyApplications', coalesce(tryGet(parameters('upgradeDescription'), 'maxPercentDeltaUnhealthyApplications'), 0), 'maxPercentDeltaUnhealthyNodes', coalesce(tryGet(parameters('upgradeDescription'), 'maxPercentDeltaUnhealthyNodes'), 0), 'maxPercentUpgradeDomainDeltaUnhealthyNodes', coalesce(tryGet(parameters('upgradeDescription'), 'maxPercentUpgradeDomainDeltaUnhealthyNodes'), 0)), 'forceRestart', coalesce(tryGet(parameters('upgradeDescription'), 'forceRestart'), false()), 'healthCheckRetryTimeout', coalesce(tryGet(parameters('upgradeDescription'), 'healthCheckRetryTimeout'), '00:45:00'), 'healthCheckStableDuration', coalesce(tryGet(parameters('upgradeDescription'), 'healthCheckStableDuration'), '00:01:00'), 'healthCheckWaitDuration', coalesce(tryGet(parameters('upgradeDescription'), 'healthCheckWaitDuration'), '00:00:30'), 'upgradeDomainTimeout', coalesce(tryGet(parameters('upgradeDescription'), 'upgradeDomainTimeout'), '02:00:00'), 'upgradeReplicaSetCheckTimeout', coalesce(tryGet(parameters('upgradeDescription'), 'upgradeReplicaSetCheckTimeout'), '1.00:00:00'), 'upgradeTimeout', coalesce(tryGet(parameters('upgradeDescription'), 'upgradeTimeout'), '02:00:00')), if(contains(parameters('upgradeDescription'), 'healthPolicy'), createObject('healthPolicy', createObject('applicationHealthPolicies', coalesce(tryGet(parameters('upgradeDescription').healthPolicy, 'applicationHealthPolicies'), createObject()), 'maxPercentUnhealthyApplications', coalesce(tryGet(parameters('upgradeDescription').healthPolicy, 'maxPercentUnhealthyApplications'), 0), 'maxPercentUnhealthyNodes', coalesce(tryGet(parameters('upgradeDescription').healthPolicy, 'maxPercentUnhealthyNodes'), 0))), createObject()))]", "builtInRoleNames": { "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", @@ -608,13 +608,13 @@ "applicationTypeVersionsCleanupPolicy": { "maxUnusedVersionsToKeep": "[parameters('maxUnusedVersionsToKeep')]" }, - "azureActiveDirectory": "[if(not(empty(parameters('azureActiveDirectory'))), createObject('clientApplication', if(contains(parameters('azureActiveDirectory'), 'clientApplication'), parameters('azureActiveDirectory').clientApplication, null()), 'clusterApplication', if(contains(parameters('azureActiveDirectory'), 'clusterApplication'), parameters('azureActiveDirectory').clusterApplication, null()), 'tenantId', if(contains(parameters('azureActiveDirectory'), 'tenantId'), parameters('azureActiveDirectory').tenantId, null())), null())]", - "certificate": "[if(not(empty(parameters('certificate'))), createObject('thumbprint', coalesce(tryGet(parameters('certificate'), 'thumbprint'), ''), 'thumbprintSecondary', coalesce(tryGet(parameters('certificate'), 'thumbprintSecondary'), null()), 'x509StoreName', coalesce(tryGet(parameters('certificate'), 'x509StoreName'), null())), null())]", - "certificateCommonNames": "[if(not(empty(parameters('certificateCommonNames'))), createObject('commonNames', coalesce(tryGet(parameters('certificateCommonNames'), 'commonNames'), createArray()), 'x509StoreName', coalesce(tryGet(parameters('certificateCommonNames'), 'x509StoreName'), null())), null())]", + "azureActiveDirectory": "[if(not(empty(parameters('azureActiveDirectory'))), createObject('clientApplication', tryGet(parameters('azureActiveDirectory'), 'clientApplication'), 'clusterApplication', tryGet(parameters('azureActiveDirectory'), 'clusterApplication'), 'tenantId', tryGet(parameters('azureActiveDirectory'), 'tenantId')), null())]", + "certificate": "[if(not(empty(parameters('certificate'))), createObject('thumbprint', coalesce(tryGet(parameters('certificate'), 'thumbprint'), ''), 'thumbprintSecondary', tryGet(parameters('certificate'), 'thumbprintSecondary'), 'x509StoreName', tryGet(parameters('certificate'), 'x509StoreName')), null())]", + "certificateCommonNames": "[if(not(empty(parameters('certificateCommonNames'))), createObject('commonNames', coalesce(tryGet(parameters('certificateCommonNames'), 'commonNames'), createArray()), 'x509StoreName', tryGet(parameters('certificateCommonNames'), 'x509StoreName')), null())]", "clientCertificateCommonNames": "[variables('clientCertificateCommonNamesVar')]", "clientCertificateThumbprints": "[variables('clientCertificateThumbprintsVar')]", "clusterCodeVersion": "[parameters('clusterCodeVersion')]", - "diagnosticsStorageAccountConfig": "[if(not(empty(parameters('diagnosticsStorageAccountConfig'))), createObject('blobEndpoint', if(contains(parameters('diagnosticsStorageAccountConfig'), 'blobEndpoint'), parameters('diagnosticsStorageAccountConfig').blobEndpoint, null()), 'protectedAccountKeyName', if(contains(parameters('diagnosticsStorageAccountConfig'), 'protectedAccountKeyName'), parameters('diagnosticsStorageAccountConfig').protectedAccountKeyName, null()), 'protectedAccountKeyName2', if(contains(parameters('diagnosticsStorageAccountConfig'), 'protectedAccountKeyName2'), parameters('diagnosticsStorageAccountConfig').protectedAccountKeyName2, null()), 'queueEndpoint', if(contains(parameters('diagnosticsStorageAccountConfig'), 'queueEndpoint'), parameters('diagnosticsStorageAccountConfig').queueEndpoint, null()), 'storageAccountName', if(contains(parameters('diagnosticsStorageAccountConfig'), 'storageAccountName'), parameters('diagnosticsStorageAccountConfig').storageAccountName, null()), 'tableEndpoint', if(contains(parameters('diagnosticsStorageAccountConfig'), 'tableEndpoint'), parameters('diagnosticsStorageAccountConfig').tableEndpoint, null())), null())]", + "diagnosticsStorageAccountConfig": "[if(not(empty(parameters('diagnosticsStorageAccountConfig'))), createObject('blobEndpoint', tryGet(parameters('diagnosticsStorageAccountConfig'), 'blobEndpoint'), 'protectedAccountKeyName', tryGet(parameters('diagnosticsStorageAccountConfig'), 'protectedAccountKeyName'), 'protectedAccountKeyName2', tryGet(parameters('diagnosticsStorageAccountConfig'), 'protectedAccountKeyName2'), 'queueEndpoint', tryGet(parameters('diagnosticsStorageAccountConfig'), 'queueEndpoint'), 'storageAccountName', tryGet(parameters('diagnosticsStorageAccountConfig'), 'storageAccountName'), 'tableEndpoint', tryGet(parameters('diagnosticsStorageAccountConfig'), 'tableEndpoint')), null())]", "eventStoreServiceEnabled": "[parameters('eventStoreServiceEnabled')]", "fabricSettings": "[if(not(empty(parameters('fabricSettings'))), variables('fabricSettingsVar'), null())]", "infrastructureServiceManager": "[parameters('infrastructureServiceManager')]", @@ -622,8 +622,8 @@ "nodeTypes": "[if(not(empty(parameters('nodeTypes'))), variables('fnodeTypesVar'), createArray())]", "notifications": "[if(not(empty(parameters('notifications'))), variables('notificationsVar'), null())]", "reliabilityLevel": "[if(not(empty(parameters('reliabilityLevel'))), parameters('reliabilityLevel'), 'None')]", - "reverseProxyCertificate": "[if(not(empty(parameters('reverseProxyCertificate'))), createObject('thumbprint', if(contains(parameters('reverseProxyCertificate'), 'thumbprint'), parameters('reverseProxyCertificate').thumbprint, null()), 'thumbprintSecondary', if(contains(parameters('reverseProxyCertificate'), 'thumbprintSecondary'), parameters('reverseProxyCertificate').thumbprintSecondary, null()), 'x509StoreName', if(contains(parameters('reverseProxyCertificate'), 'x509StoreName'), parameters('reverseProxyCertificate').x509StoreName, null())), null())]", - "reverseProxyCertificateCommonNames": "[if(not(empty(parameters('reverseProxyCertificateCommonNames'))), createObject('commonNames', if(contains(parameters('reverseProxyCertificateCommonNames'), 'commonNames'), parameters('reverseProxyCertificateCommonNames').commonNames, null()), 'x509StoreName', if(contains(parameters('reverseProxyCertificateCommonNames'), 'x509StoreName'), parameters('reverseProxyCertificateCommonNames').x509StoreName, null())), null())]", + "reverseProxyCertificate": "[if(not(empty(parameters('reverseProxyCertificate'))), createObject('thumbprint', tryGet(parameters('reverseProxyCertificate'), 'thumbprint'), 'thumbprintSecondary', tryGet(parameters('reverseProxyCertificate'), 'thumbprintSecondary'), 'x509StoreName', tryGet(parameters('reverseProxyCertificate'), 'x509StoreName')), null())]", + "reverseProxyCertificateCommonNames": "[if(not(empty(parameters('reverseProxyCertificateCommonNames'))), createObject('commonNames', tryGet(parameters('reverseProxyCertificateCommonNames'), 'commonNames'), 'x509StoreName', tryGet(parameters('reverseProxyCertificateCommonNames'), 'x509StoreName')), null())]", "sfZonalUpgradeMode": "[if(not(empty(parameters('sfZonalUpgradeMode'))), parameters('sfZonalUpgradeMode'), null())]", "upgradeDescription": "[if(not(empty(parameters('upgradeDescription'))), variables('upgradeDescriptionVar'), null())]", "upgradeMode": "[if(not(empty(parameters('upgradeMode'))), parameters('upgradeMode'), null())]", @@ -702,8 +702,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "8632559494589711330" + "version": "0.32.4.45862", + "templateHash": "4770865885039578272" }, "name": "Service Fabric Cluster Application Types", "description": "This module deploys a Service Fabric Cluster Application Type.", @@ -742,10 +742,7 @@ "type": "Microsoft.ServiceFabric/clusters/applicationTypes", "apiVersion": "2021-06-01", "name": "[format('{0}/{1}', parameters('serviceFabricClusterName'), parameters('name'))]", - "tags": "[parameters('tags')]", - "dependsOn": [ - "serviceFabricCluster" - ] + "tags": "[parameters('tags')]" } }, "outputs": { diff --git a/avm/res/service-fabric/cluster/tests/e2e/cert/main.test.bicep b/avm/res/service-fabric/cluster/tests/e2e/cert/main.test.bicep new file mode 100644 index 0000000000..a3b818cd1e --- /dev/null +++ b/avm/res/service-fabric/cluster/tests/e2e/cert/main.test.bicep @@ -0,0 +1,82 @@ +targetScope = 'subscription' + +metadata name = 'Using client and server certificate parameter set' +metadata description = 'This instance deploys the module with client and server certificates using thumbprints and common names.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-servicefabric.clusters-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'sfccrt' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + location: resourceLocation + name: '${namePrefix}${serviceShort}001' + managementEndpoint: 'https://${namePrefix}${serviceShort}001.westeurope.cloudapp.azure.com:19080' + reliabilityLevel: 'None' + certificateCommonNames: { + commonNames: [ + { + certificateCommonName: 'certcommon' + certificateIssuerThumbprint: '0AC113D5E1D94C401DDEB0EE2B1B96CC130' + } + ] + x509StoreName: 'My' + } + clientCertificateThumbprints: [ + { + certificateThumbprint: 'D945B0AC4BDF78D31FB6F09CF375E0B9DC7BBBBE' + isAdmin: true + } + ] + nodeTypes: [ + { + applicationPorts: { + endPort: 30000 + startPort: 20000 + } + clientConnectionEndpointPort: 19000 + durabilityLevel: 'Bronze' + ephemeralPorts: { + endPort: 65534 + startPort: 49152 + } + httpGatewayEndpointPort: 19080 + isPrimary: true + name: 'Node01' + } + ] + } + } +] diff --git a/avm/res/service-fabric/cluster/version.json b/avm/res/service-fabric/cluster/version.json index 13669e6601..41fc8c654f 100644 --- a/avm/res/service-fabric/cluster/version.json +++ b/avm/res/service-fabric/cluster/version.json @@ -1,7 +1,7 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.4", + "version": "0.5", "pathFilters": [ "./main.json" ] -} \ No newline at end of file +}