diff --git a/.dockerignore b/.dockerignore index 1f52752..6a23c9e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -13,4 +13,7 @@ Environments/ tests/ roles/ Config/ -Agent/ \ No newline at end of file +Agent/ +*.dll +*.deps.json +*.pdb \ No newline at end of file diff --git a/.gitignore b/.gitignore index 51905a0..e32b95d 100644 --- a/.gitignore +++ b/.gitignore @@ -49,7 +49,9 @@ app_creds_real.py *.sln.docstates .vs/ .vscode - +*.dll +*.pdb +*.deps.json # Windows image file caches Thumbs.db ehthumbs.db diff --git a/Environments/ASE_SQLDB/LandingZone/NetworkParameters/virtualNetwork.json b/Environments/ASE_SQLDB/LandingZone/NetworkParameters/virtualNetwork.json index 4a1546e..5404365 100644 --- a/Environments/ASE_SQLDB/LandingZone/NetworkParameters/virtualNetwork.json +++ b/Environments/ASE_SQLDB/LandingZone/NetworkParameters/virtualNetwork.json @@ -26,5 +26,5 @@ ] } ], - "DnsServers": "reference(${Parameters.Organization}-shrdsvcs.ActiveDirectoryDomainServices.dnsServers)" + "DnsServers": "reference(${Parameters.Organization}-shrdsvcs.InstallActiveDirectoryDomainServices.dnsServers)" } \ No newline at end of file diff --git a/Environments/ASE_SQLDB/LandingZone/orchestration.json b/Environments/ASE_SQLDB/LandingZone/orchestration.json index 3d04d72..596d387 100644 --- a/Environments/ASE_SQLDB/LandingZone/orchestration.json +++ b/Environments/ASE_SQLDB/LandingZone/orchestration.json @@ -3,6 +3,7 @@ "ModuleConfigurations": [ { "Name": "SubscriptionCreation", + "Enabled": false, "Script": { "Command": "../../../Scripts/Subscription/NewSubscription.ps1", "Arguments": { @@ -51,6 +52,9 @@ "Name": "LogAnalytics", "ModuleDefinitionName": "LogAnalytics", "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.LogAnalytics.ResourceGroup}", + "DependsOn": [ + "DiagnosticStorageAccount" + ], "Deployment": { "OverrideParameters": { "logAnalyticsWorkspaceName": { @@ -74,6 +78,9 @@ { "Name": "AzureSecurityCenter", "ModuleDefinitionName": "AzureSecurityCenter", + "DependsOn": [ + "LogAnalytics" + ], "Deployment": { "OverrideParameters": { "workspaceId": { @@ -85,6 +92,9 @@ { "Name": "NISTControls", "ModuleDefinitionName": "NISTControls", + "DependsOn": [ + "LogAnalytics" + ], "Deployment": { "OverrideParameters": { "workspaceId": { @@ -100,6 +110,10 @@ "Name": "DefaultNSG", "ModuleDefinitionName": "NetworkSecurityGroups", "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.NetworkSecurityGroups.ResourceGroup}", + "DependsOn": [ + "DiagnosticStorageAccount", + "LogAnalytics" + ], "Deployment": { "OverrideParameters": { "workspaceId": { @@ -121,6 +135,7 @@ "Name": "DefaultRouteTable", "ModuleDefinitionName": "RouteTables", "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.RouteTables.ResourceGroup}", + "DependsOn": [], "Deployment": { "OverrideParameters": { "routeTableName": { @@ -134,8 +149,12 @@ }, { "Name": "VirtualNetwork", - "ModuleDefinitionName": "vNet", + "ModuleDefinitionName": "VirtualNetwork", "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.ResourceGroup}", + "DependsOn": [ + "DefaultNSG", + "DefaultRouteTable" + ], "Deployment": { "OverrideParameters": { "vnetName": { @@ -161,8 +180,11 @@ }, { "Name": "LocalVirtualNetworkPeering", - "ModuleDefinitionName": "vNetPeering", + "ModuleDefinitionName": "VirtualNetworkPeering", "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.ResourceGroup}", + "DependsOn": [ + "VirtualNetwork" + ], "Deployment": { "OverrideParameters": { "localVnetName": { @@ -179,9 +201,12 @@ }, { "Name": "RemoteVirtualNetworkPeering", - "ModuleDefinitionName": "vNetPeering", + "ModuleDefinitionName": "VirtualNetworkPeering", "Subscription": "SharedServices", "ResourceGroupName": "reference(${Parameters.Organization}-shrdsvcs.VirtualNetwork.vNetResourceGroup)", + "DependsOn": [ + "VirtualNetwork" + ], "Deployment": { "OverrideParameters": { "localVnetName": { diff --git a/Environments/ASE_SQLDB/LandingZone/parameters.json b/Environments/ASE_SQLDB/LandingZone/parameters.json index a7ca393..dcbe67a 100644 --- a/Environments/ASE_SQLDB/LandingZone/parameters.json +++ b/Environments/ASE_SQLDB/LandingZone/parameters.json @@ -5,12 +5,6 @@ "Subscription": "ASE_SQLDB", "ModuleConfigurationParameters": { "OnPremisesInformation": { - "ActiveDirectory": { - "PrimaryDomainControllerIP": "192.168.1.4", - "DomainName": "fontoso.com", - "ADSitename": "Cloud-Site", - "DomainAdminUserName": "fontoso" - }, "Network": { "AddressPrefix": "192.168.1.0/28" } diff --git a/Environments/NTier-IaaS/Archetype/definition.json b/Environments/NTier-IaaS/Archetype/definition.json new file mode 100644 index 0000000..dba4af8 --- /dev/null +++ b/Environments/NTier-IaaS/Archetype/definition.json @@ -0,0 +1,5 @@ +{ + "Subscriptions": "env(VDC_SUBSCRIPTIONS)", + "Parameters": "file(./parameters.json)", + "Orchestration": "file(./orchestration.json)" +} \ No newline at end of file diff --git a/Environments/NTier-IaaS/Archetype/orchestration.json b/Environments/NTier-IaaS/Archetype/orchestration.json new file mode 100644 index 0000000..66866cb --- /dev/null +++ b/Environments/NTier-IaaS/Archetype/orchestration.json @@ -0,0 +1,483 @@ +{ + "ModuleConfigurationsPath": "../../../Modules", + "ModuleConfigurations": [ + { + "Name": "KeyVault", + "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.KeyVault.ResourceGroup}", + "ModuleDefinitionName": "KeyVault", + "Deployment": { + "OverrideParameters": { + "keyVaultName": { + "value": "${Parameters.ModuleConfigurationParameters.KeyVault.Name}" + }, + "accessPolicies": { + "value": "${Parameters.ModuleConfigurationParameters.KeyVault.AccessPolicies}" + }, + "secretsObject": { + "value": { + "secrets": "${Parameters.ModuleConfigurationParameters.KeyVault.SecretsObject.Secrets}" + } + }, + "enableVaultForDeployment": { + "value": "${Parameters.ModuleConfigurationParameters.KeyVault.EnableVaultForDeployment}" + }, + "enableVaultForDiskEncryption": { + "value": "${Parameters.ModuleConfigurationParameters.KeyVault.EnableVaultForDiskEncryption}" + }, + "enableVaultForTemplateDeployment": { + "value": "${Parameters.ModuleConfigurationParameters.KeyVault.EnableVaultForTemplateDeployment}" + }, + "vaultSku": { + "value": "${Parameters.ModuleConfigurationParameters.KeyVault.Sku}" + }, + "diagnosticStorageAccountId": { + "value": "reference(DiagnosticStorageAccount.storageAccountResourceId)" + }, + "workspaceId": { + "value": "reference(LogAnalytics.logAnalyticsWorkspaceResourceId)" + }, + "networkAcls": { + "value": "${Parameters.ModuleConfigurationParameters.KeyVault.NetworkAcls}" + }, + "vNetId": { + "value": "reference(VirtualNetwork.vNetResourceId)" + } + } + } + }, + { + "Name": "WebAppLoadBalancer", + "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.WebAppLoadBalancer.ResourceGroup}", + "ModuleDefinitionName": "LoadBalancers", + "Deployment": { + "OverrideParameters": { + "loadBalancerName": { + "value": "${Parameters.ModuleConfigurationParameters.WebAppLoadBalancer.Name}" + }, + "loadBalancingRules": { + "value": "${Parameters.ModuleConfigurationParameters.WebAppLoadBalancer.LoadBalancingRules}" + }, + "probes": { + "value": "${Parameters.ModuleConfigurationParameters.WebAppLoadBalancer.Probes}" + }, + "vNetId": { + "value": "reference(VirtualNetwork.vNetResourceId)" + }, + "subnetName": { + "value": "${Parameters.ModuleConfigurationParameters.WebAppLoadBalancer.SubnetName}" + } + } + } + }, + { + "Name": "WebAppVMSS", + "ModuleDefinitionName": "VirtualMachineScaleSets", + "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.WebApp.ResourceGroup}", + "DependsOn": [ + "WebAppLoadBalancer", + "KeyVault" + ], + "Comments": "Creates Active Directory Domain Services VMs", + "Deployment": { + "OverrideParameters": { + "virtualMachineScaleSetsName": { + "value": "${Parameters.ModuleConfigurationParameters.WebApp.Name}" + }, + "virtualMachineScaleSetsSku": { + "value": "${Parameters.ModuleConfigurationParameters.WebApp.VMSKU}" + }, + "virtualMachineScaleSetsOSImage": { + "value": "${Parameters.ModuleConfigurationParameters.WebApp.OSImage}" + }, + "virtualMachineScaleSetsOSType": { + "value": "${Parameters.ModuleConfigurationParameters.WebApp.OSType}" + }, + "virtualMachineScaleSetsDataDisks": { + "value": "${Parameters.ModuleConfigurationParameters.WebApp.DataDisks}" + }, + "loadBalancerBackendPoolId": { + "value": "reference(WebLoadBalancer.loadBalancerResourceBackendPoolId)" + }, + "workspaceId": { + "value": "reference(LogAnalytics.logAnalyticsWorkspaceId)" + }, + "logAnalyticsWorkspacePrimarySharedKey": { + "value": "reference(LogAnalytics.logAnalyticsPrimarySharedKey)" + }, + "diagnosticsStorageAccountName": { + "value": "reference(DiagnosticStorageAccount.storageAccountName)" + }, + "diagnosticsStorageAccountSasToken": { + "value": "reference(DiagnosticStorageAccount.storageAccountSasToken)" + }, + "artifactsStorageAccountName": { + "value": "reference(${Parameters.Organization}-shrdsvcs.ArtifactsStorageAccount.storageAccountName)" + }, + "artifactsStorageAccountSasKey": { + "value": "reference(${Parameters.Organization}-shrdsvcs.ArtifactsStorageAccount.storageAccountSasToken)" + }, + "vNetId": { + "value": "reference(VirtualNetwork.vNetResourceId)" + }, + "subnetName": { + "value": "${Parameters.ModuleConfigurationParameters.WebApp.SubnetName}" + }, + "vmIPAddress": { + "value": "${Parameters.ModuleConfigurationParameters.WebApp.AddsIPAddressStart}" + }, + "applicationSecurityGroupId": { + "value": "reference(WebASG.applicationSecurityGroupResourceId)" + }, + "adminUsername": { + "value": "${Parameters.ModuleConfigurationParameters.WebApp.AdminUsername}" + }, + "adminPassword": { + "reference": "${Parameters.ModuleConfigurationParameters.WebApp.AdminPassword}" + }, + "domainAdminUsername": { + "value": "${Parameters.ModuleConfigurationParameters.WebApp.DomainAdminUsername}" + }, + "domainAdminPassword": { + "value": "${Parameters.ModuleConfigurationParameters.WebApp.DomainAdminPassword}" + }, + "domainName": { + "value": "${Parameters.ModuleConfigurationParameters.WebApp.DomainName}" + } + } + } + }, + { + "Name": "IISOnWebVMSS", + "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.WebApp.ResourceGroup}", + "ModuleDefinitionName": "InternetInformationServices", + "DependsOn":[ + "WebAppVMSS" + ], + "Deployment":{ + "OverrideParameters":{ + "virtualMachineScaleSetsName": { + "value": "${Parameters.ModuleConfigurationParameters.WebApp.Name}" + }, + "artifactsStorageAccountName": { + "value": "reference(${Parameters.Organization}-shrdsvcs.ArtifactsStorageAccount.storageAccountName)" + }, + "artifactsStorageAccountSasKey": { + "value": "reference(${Parameters.Organization}-shrdsvcs.ArtifactsStorageAccount.storageAccountSasToken)" + } + } + } + }, + { + "Name": "BusinessAppLoadBalancer", + "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.BusinessAppLoadBalancer.ResourceGroup}", + "ModuleDefinitionName": "LoadBalancers", + "Deployment": { + "OverrideParameters": { + "loadBalancerName": { + "value": "${Parameters.ModuleConfigurationParameters.BusinessAppLoadBalancer.Name}" + }, + "loadBalancingRules": { + "value": "${Parameters.ModuleConfigurationParameters.BusinessAppLoadBalancer.LoadBalancingRules}" + }, + "probes": { + "value": "${Parameters.ModuleConfigurationParameters.BusinessAppLoadBalancer.Probes}" + }, + "vNetId": { + "value": "reference(VirtualNetwork.vNetResourceId)" + }, + "subnetName": { + "value": "${Parameters.ModuleConfigurationParameters.BusinessAppLoadBalancer.SubnetName}" + } + } + } + }, + { + "Name": "BusinessAppVMSS", + "ModuleDefinitionName": "VirtualMachineScaleSets", + "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.BusinessApp.ResourceGroup}", + "DependsOn": [ + "BusinessAppLoadBalancer", + "KeyVault" + ], + "Comments": "Creates Active Directory Domain Services VMs", + "Deployment": { + "OverrideParameters": { + "virtualMachineScaleSetsName": { + "value": "${Parameters.ModuleConfigurationParameters.BusinessApp.Name}" + }, + "virtualMachineScaleSetsSku": { + "value": "${Parameters.ModuleConfigurationParameters.BusinessApp.VMSKU}" + }, + "virtualMachineScaleSetsOSImage": { + "value": "${Parameters.ModuleConfigurationParameters.BusinessApp.OSImage}" + }, + "virtualMachineScaleSetsOSType": { + "value": "${Parameters.ModuleConfigurationParameters.BusinessApp.OSType}" + }, + "virtualMachineScaleSetsDataDisks": { + "value": "${Parameters.ModuleConfigurationParameters.BusinessApp.DataDisks}" + }, + "loadBalancerBackendPoolId": { + "value": "reference(WebLoadBalancer.loadBalancerResourceBackendPoolId)" + }, + "workspaceId": { + "value": "reference(LogAnalytics.logAnalyticsWorkspaceId)" + }, + "logAnalyticsWorkspacePrimarySharedKey": { + "value": "reference(LogAnalytics.logAnalyticsPrimarySharedKey)" + }, + "diagnosticsStorageAccountName": { + "value": "reference(DiagnosticStorageAccount.storageAccountName)" + }, + "diagnosticsStorageAccountSasToken": { + "value": "reference(DiagnosticStorageAccount.storageAccountSasToken)" + }, + "artifactsStorageAccountName": { + "value": "reference(${Parameters.Organization}-shrdsvcs.ArtifactsStorageAccount.storageAccountName)" + }, + "artifactsStorageAccountSasKey": { + "value": "reference(${Parameters.Organization}-shrdsvcs.ArtifactsStorageAccount.storageAccountSasToken)" + }, + "vNetId": { + "value": "reference(VirtualNetwork.vNetResourceId)" + }, + "subnetName": { + "value": "${Parameters.ModuleConfigurationParameters.BusinessApp.SubnetName}" + }, + "vmIPAddress": { + "value": "${Parameters.ModuleConfigurationParameters.BusinessApp.AddsIPAddressStart}" + }, + "applicationSecurityGroupId": { + "value": "reference(BusinessASG.applicationSecurityGroupResourceId)" + }, + "adminUsername": { + "value": "${Parameters.ModuleConfigurationParameters.BusinessApp.AdminUsername}" + }, + "adminPassword": { + "reference": "${Parameters.ModuleConfigurationParameters.BusinessApp.AdminPassword}" + }, + "domainAdminUsername": { + "value": "${Parameters.ModuleConfigurationParameters.BusinessApp.DomainAdminUsername}" + }, + "domainAdminPassword": { + "value": "${Parameters.ModuleConfigurationParameters.BusinessApp.DomainAdminPassword}" + }, + "domainName": { + "value": "${Parameters.ModuleConfigurationParameters.BusinessApp.DomainName}" + } + } + } + }, + { + "Name": "IISOnBusinessVMSS", + "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.BusinessApp.ResourceGroup}", + "ModuleDefinitionName": "InternetInformationServices", + "DependsOn":[ + "BusinessAppVMSS" + ], + "Deployment":{ + "OverrideParameters":{ + "virtualMachineScaleSetsName": { + "value": "${Parameters.ModuleConfigurationParameters.BusinessApp.Name}" + }, + "artifactsStorageAccountName": { + "value": "reference(${Parameters.Organization}-shrdsvcs.ArtifactsStorageAccount.storageAccountName)" + }, + "artifactsStorageAccountSasKey": { + "value": "reference(${Parameters.Organization}-shrdsvcs.ArtifactsStorageAccount.storageAccountSasToken)" + } + } + } + }, + { + "Name": "SQLServerAlwaysOnLoadBalancer", + "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOnLoadBalancer.ResourceGroup}", + "ModuleDefinitionName": "LoadBalancers", + "Deployment": { + "OverrideParameters": { + "loadBalancerName": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOnLoadBalancer.Name}" + }, + "loadBalancingRules": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOnLoadBalancer.LoadBalancingRules}" + }, + "probes": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOnLoadBalancer.Probes}" + }, + "vNetId": { + "value": "reference(VirtualNetwork.vNetResourceId)" + }, + "loadBalancerIPAddress": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOnLoadBalancer.IPAddressStart}" + }, + "subnetName": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOnLoadBalancer.SubnetName}" + } + } + } + }, + { + "Name": "SQLServerAlwaysOnVMs", + "ModuleDefinitionName": "VirtualMachines", + "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.ResourceGroup}", + "DependsOn": [ + "KeyVault", + "SQLServerAlwaysOnLoadBalancer" + ], + "Comments": "Creates Active Directory Domain Services VMs", + "Deployment": { + "OverrideParameters": { + "virtualMachineName": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.Name}" + }, + "virtualMachineSize": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.VMSize}" + }, + "virtualMachineOSImage": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.OSImage}" + }, + "virtualMachineOSType": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.OSType}" + }, + "virtualMachineCount": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.VMCount}" + }, + "virtualMachineDataDisks": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.DataDisks}" + }, + "workspaceId": { + "value": "reference(LogAnalytics.logAnalyticsWorkspaceId)" + }, + "logAnalyticsWorkspaceId": { + "value": "reference(LogAnalytics.logAnalyticsWorkspaceResourceId)" + }, + "logAnalyticsWorkspacePrimarySharedKey": { + "value": "reference(LogAnalytics.logAnalyticsPrimarySharedKey)" + }, + "diagnosticsStorageAccountId": { + "value": "reference(DiagnosticStorageAccount.storageAccountResourceId)" + }, + "diagnosticsStorageAccountName": { + "value": "reference(DiagnosticStorageAccount.storageAccountName)" + }, + "diagnosticsStorageAccountSasToken": { + "value": "reference(DiagnosticStorageAccount.storageAccountSasToken)" + }, + "artifactsStorageAccountName": { + "value": "reference(${Parameters.Organization}-shrdsvcs.ArtifactsStorageAccount.storageAccountName)" + }, + "artifactsStorageAccountKey": { + "value": "reference(${Parameters.Organization}-shrdsvcs.ArtifactsStorageAccount.storageAccountAccessKey)" + }, + "artifactsStorageAccountSasKey": { + "value": "reference(${Parameters.Organization}-shrdsvcs.ArtifactsStorageAccount.storageAccountSasToken)" + }, + "vNetId": { + "value": "reference(VirtualNetwork.vNetResourceId)" + }, + "subnetName": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.SubnetName}" + }, + "vmIPAddress": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.IPAddressStart}" + }, + "applicationSecurityGroupId": { + "value": "reference(DataASG.applicationSecurityGroupResourceId)" + }, + "adminUsername": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.AdminUsername}" + }, + "adminPassword": { + "reference": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.AdminPassword}" + }, + "domainAdminUsername": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.DomainAdminUsername}" + }, + "domainAdminPassword": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.DomainAdminPassword}" + }, + "domainName": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.DomainName}" + } + } + } + }, + { + "Name": "CloudWitnessStorageAccount", + "ModuleDefinitionName": "StorageAccounts", + "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOnCloudWitness.ResourceGroup}", + "Comments": "Storage Account that is used as a Cloud Witness", + "Deployment": { + "Comments": "We need the 'update' module instance to lock this resource after the Virtual Network got created", + "OverrideParameters": { + "storageAccountName": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOnCloudWitness.Name}" + }, + "storageAccountSku": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOnCloudWitness.Sku}" + }, + "networkAcls": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOnCloudWitness.NetworkAcls}" + }, + "vNetId": { + "value": "reference(VirtualNetwork.vNetResourceId)" + } + } + } + }, + { + "Name": "InstallSQLServerAlwaysOn", + "ModuleDefinitionName": "VirtualMachines", + "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.ResourceGroup}", + "DependsOn": [ + "KeyVault", + "SQLServerAlwaysOnLoadBalancer", + "CloudWitnessStorageAccount" + ], + "Comments": "Creates Active Directory Domain Services VMs", + "Deployment": { + "OverrideParameters": { + "virtualMachineName": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.Name}" + }, + "adminUsername": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.Name}" + }, + "adminPassword": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.Name}" + }, + "clusterName": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.Name}" + }, + "artifactsStorageAccountName": { + "value": "reference(${Parameters.Organization}-shrdsvcs.ArtifactsStorageAccount.storageAccountName)" + }, + "artifactsStorageAccountKey": { + "value": "reference(${Parameters.Organization}-shrdsvcs.ArtifactsStorageAccount.storageAccountAccessKey)" + }, + "artifactsStorageAccountSasKey": { + "value": "reference(${Parameters.Organization}-shrdsvcs.ArtifactsStorageAccount.storageAccountSasToken)" + }, + "cloudWitnessStorageAccountName": { + "value": "reference(CloudWitnessStorageAccount.storageAccountName)" + }, + "cloudWitnessStorageAccountKey": { + "value": "reference(CloudWitnessStorageAccount.storageAccountName)" + }, + "sqlServerILB_IPAddress": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.Name}" + }, + "domainName": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.Name}" + }, + "domainAdminUsername": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.Name}" + }, + "domainAdminPassword": { + "value": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.Name}" + } + } + } + } + ] +} \ No newline at end of file diff --git a/Environments/NTier-IaaS/Archetype/parameters.json b/Environments/NTier-IaaS/Archetype/parameters.json new file mode 100644 index 0000000..a1acaf8 --- /dev/null +++ b/Environments/NTier-IaaS/Archetype/parameters.json @@ -0,0 +1,268 @@ +{ + "Organization": "env(ORGANIZATION_NAME)", + "DeploymentName": "ntier-iaas", + "InstanceName": "${Parameters.Organization}-${Parameters.DeploymentName}", + "Subscription": "NTier_IaaS", + "ModuleConfigurationParameters": { + "DeploymentUserId": "env(DEPLOYMENT_USER_ID)", + "OnPremisesInformation": { + "ActiveDirectory": { + "PrimaryDomainControllerIP": "192.168.1.4", + "DomainName": "fontoso.com", + "ADSitename": "Cloud-Site", + "DomainAdminUserName": "fontoso" + }, + "Network": { + "AddressPrefix": "192.168.1.0/28" + } + }, + "Comments": "Adding VirtualNetwork property, because KeyVault references a VirtualNetwork property references NetworkSecurityGroups and RouteTable, this is why these two properties are also included", + "RouteTables": "file(../LandingZone/NetworkParameters/routeTables.json)", + "NetworkSecurityGroups": "file(../LandingZone/NetworkParameters/networkSecurityGroups.json)", + "VirtualNetwork": "file(../LandingZone/NetworkParameters/virtualNetwork.json)", + "KeyVault": { + "Name": "${Parameters.InstanceName}-kv", + "ResourceGroup": "${Parameters.InstanceName}-keyvault-rg", + "Sku": "Premium", + "EnableVaultForDeployment": true, + "EnableVaultForDiskEncryption": true, + "EnableVaultForTemplateDeployment": true, + "AccessPolicies": [ + { + "tenantId": "${Parameters.TenantId}", + "objectId": "${Parameters.ModuleConfigurationParameters.DeploymentUserId}", + "permissions": { + "certificates": [ + "All" + ], + "keys": [ + "All" + ], + "secrets": [ + "All" + ] + } + } + ], + "SecretsObject": { + "Comments": "Creating an object so we can use a secretsobject parameter type in our ARM template", + "Secrets": [ + { + "secretName": "admin-user", + "secretValue": "env(ADMIN_USER_PWD)" + } + ] + }, + "NetworkAcls": { + "bypass": "AzureServices", + "defaultAction": "Deny", + "virtualNetworkRules": [ + { + "subnet": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.Subnets[0].Name}" + } + ], + "ipRules": [] + } + }, + "WebApp": { + "Name": "web-vmss", + "ResourceGroup": "${Parameters.InstanceName}-webapp-rg", + "VMSKU": { + "name": "Standard_DS3_v2", + "tier": "Standard", + "capacity": 5 + }, + "OSImage": { + "offer": "WindowsServer", + "publisher": "MicrosoftWindowsServer", + "sku": "2016-Datacenter" + }, + "OSType": "Windows", + "Kek": { + "Name": "WebAppKey", + "Comments": "Destination can be HSM or Software. Use HSM to create Production keys.", + "Destination": "HSM" + }, + "DomainName": "${Parameters.ModuleConfigurationParameters.ActiveDirectory.DomainName}", + "DomainAdminUsername": "env(DOMAIN_ADMIN_USERNAME)", + "DomainAdminPassword": "env(DOMAIN_ADMIN_USER_PWD)", + "AdminUsername": "${Parameters.ModuleConfigurationParameters.KeyVault.SecretsObject.Secrets[0].secretName}", + "AdminPassword": { + "keyVault": { + "id": "reference(KeyVault.keyVaultResourceId)" + }, + "secretName": "${Parameters.ModuleConfigurationParameters.KeyVault.SecretsObject.Secrets[0].secretName}" + }, + "SubnetName": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.Subnets[0].name}" + }, + "WebAppLoadBalancer": { + "Name": "web-lb", + "ResourceGroup": "${Parameters.ModuleConfigurationParameters.WebApp.ResourceGroup}", + "LoadBalancingRules": [ + { + "name": "LBRule", + "properties": { + "frontendPort": 80, + "backendPort": 80, + "enableFloatingIP": false, + "idleTimeoutInMinutes": 3, + "protocol": "TCP", + "enableTcpReset": false, + "loadDistribution": false, + "disableOutboundSnat": false, + "probeName": "tcpProbe" + } + } + ], + "Probes": [ + { + "name": "probe", + "properties": { + "protocol": "TCP", + "port": 80, + "requestPath": "/", + "intervalInSeconds": 10, + "numberOfProbes": 5 + } + } + ], + "SubnetName": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.Subnets[0].name}" + }, + "BusinessApp": { + "Name": "biz-vmss", + "ResourceGroup": "${Parameters.InstanceName}-biz-rg", + "VMSKU": { + "name": "Standard_DS3_v2", + "tier": "Standard", + "capacity": 5 + }, + "OSImage": { + "offer": "WindowsServer", + "publisher": "MicrosoftWindowsServer", + "sku": "2016-Datacenter" + }, + "OSType": "Windows", + "Kek": { + "Name": "WebAppKey", + "Comments": "Destination can be HSM or Software. Use HSM to create Production keys.", + "Destination": "HSM" + }, + "SubnetName": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.Subnets[0].name}" + }, + "BusinessAppLoadBalancer": { + "Name": "${Parameters.InstanceName}-biz-lb", + "ResourceGroup": "${Parameters.ModuleConfigurationParameters.BusinessApp.ResourceGroup}", + "LoadBalancingRules": [ + { + "name": "LBRule", + "properties": { + "frontendPort": 80, + "backendPort": 80, + "enableFloatingIP": false, + "idleTimeoutInMinutes": 3, + "protocol": "TCP", + "enableTcpReset": false, + "loadDistribution": false, + "disableOutboundSnat": false, + "probeName": "tcpProbe" + } + } + ], + "Probes": [ + { + "name": "probe", + "properties": { + "protocol": "TCP", + "port": 80, + "requestPath": "/", + "intervalInSeconds": 10, + "numberOfProbes": 5 + } + } + ], + "SubnetName": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.Subnets[0].name}" + }, + "SQLServerAlwaysOn": { + "Name": "sql-vm", + "ResourceGroup": "${Parameters.InstanceName}-data-rg", + "VMCount": 2, + "VMSize": "Standard_DS14_v2", + "OSType": "Windows", + "OSImage": { + "offer": "SQL2017-WS2016", + "publisher": "MicrosoftSQLServer", + "sku": "Enterprise" + }, + "IPAddressStart": "172.2.0.20", + "DomainName": "${Parameters.ModuleConfigurationParameters.ActiveDirectory.DomainName}", + "DomainAdminUsername": "env(DOMAIN_ADMIN_USERNAME)", + "DomainAdminPassword": "env(DOMAIN_ADMIN_USER_PWD)", + "AdminUsername": "${Parameters.ModuleConfigurationParameters.KeyVault.SecretsObject.Secrets[0].secretName}", + "AdminPassword": { + "keyVault": { + "id": "reference(KeyVault.keyVaultResourceId)" + }, + "secretName": "${Parameters.ModuleConfigurationParameters.KeyVault.SecretsObject.Secrets[0].secretName}" + }, + "DataDisks": [ + { + "size": 1023 + }, + { + "size": 1023 + } + ] + }, + "SQLServerAlwaysOnCloudWitness": { + "Name": "${Parameters.InstanceName}cwntierstrg", + "ResourceGroup": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.ResourceGroup}", + "Sku": "Standard_GRS", + "NetworkAcls": { + "bypass": "AzureServices", + "defaultAction": "Deny", + "virtualNetworkRules": [ + { + "subnet": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.Subnets[0].Name}" + } + ], + "ipRules": [] + }, + "Policies": { + "Effect": "Audit" + } + }, + "SQLServerAlwaysOnLoadBalancer": { + "Name": "${Parameters.InstanceName}-data-lb", + "ResourceGroup": "${Parameters.ModuleConfigurationParameters.SQLServerAlwaysOn.ResourceGroup}", + "IPAddressStart": "172.2.0.22", + "LoadBalancingRules": [ + { + "name": "LBRule", + "properties": { + "frontendPort": 1433, + "backendPort": 1433, + "enableFloatingIP": false, + "idleTimeoutInMinutes": 5, + "protocol": "TCP", + "enableTcpReset": false, + "loadDistribution": false, + "disableOutboundSnat": false, + "probeName": "tcpProbe" + } + } + ], + "Probes": [ + { + "name": "probe", + "properties": { + "protocol": "TCP", + "port": 1433, + "intervalInSeconds": 5, + "numberOfProbes": 2 + } + } + ], + "SubnetName": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.Subnets[0].name}" + } + } +} \ No newline at end of file diff --git a/Environments/NTier-IaaS/Archetype/pipeline.yml b/Environments/NTier-IaaS/Archetype/pipeline.yml new file mode 100644 index 0000000..3ba9091 --- /dev/null +++ b/Environments/NTier-IaaS/Archetype/pipeline.yml @@ -0,0 +1,336 @@ +variables: +- group: VDC_SECRETS +trigger: +- master +stages: +- stage: Validate + jobs: + - job: SetupValidationResourceGroup + pool: + name: 'vdc-self-hosted' + steps: + - task: AzurePowerShell@4 + displayName: "Setup Validation Resource Group" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ValidationResourceGroupSetup.ps1' + ScriptArguments: '-ResourceGroupName vdc-validation-rg -SetupResourceGroup' + azurePowerShellVersion: 'LatestVersion' + - job: KeyVault + pool: + name: 'vdc-self-hosted' + dependsOn: SetupValidationResourceGroup + steps: + - task: PowerShell@2 + displayName: "Pester Tests for Module - Key Vault" + inputs: + targetType: 'inline' + script: '# Write your powershell commands here. + + Invoke-Pester -Script "./Modules/KeyVault/2.0/Tests"; + + # Use the environment variables input below to pass secret variables to this script.' + pwsh: true + - task: AzurePowerShell@4 + displayName: "ARM Validation - Key Vault" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/Archetype/definition.json" -ModuleConfigurationName "KeyVault" -Validate' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: AppServiceEnvironments + pool: + name: 'vdc-self-hosted' + dependsOn: SetupValidationResourceGroup + steps: + - task: PowerShell@2 + displayName: "Pester Tests for Module - App Service Environments" + inputs: + targetType: 'inline' + script: '# Write your powershell commands here. + + Invoke-Pester -Script "./Modules/AppServiceEnvironments/2.0/Tests"; + + # Use the environment variables input below to pass secret variables to this script.' + pwsh: true + - task: AzurePowerShell@4 + displayName: "ARM Validation - App Service Environments" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/Archetype/definition.json" -ModuleConfigurationName "AppServiceEnvironments" -Validate' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: AppServicePlan + pool: + name: 'vdc-self-hosted' + dependsOn: SetupValidationResourceGroup + steps: + - task: PowerShell@2 + displayName: "Pester Tests for Module - App Service Plan" + inputs: + targetType: 'inline' + script: '# Write your powershell commands here. + + Invoke-Pester -Script "./Modules/AppServicePlan/2.0/Tests"; + + # Use the environment variables input below to pass secret variables to this script.' + pwsh: true + - task: AzurePowerShell@4 + displayName: "ARM Validation - App Service Plan" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/Archetype/definition.json" -ModuleConfigurationName "AppServicePlan" -Validate' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: AppServiceWebApp + pool: + name: 'vdc-self-hosted' + dependsOn: SetupValidationResourceGroup + steps: + - task: PowerShell@2 + displayName: "Pester Tests for Module - App Service WebApp" + inputs: + targetType: 'inline' + script: '# Write your powershell commands here. + + Invoke-Pester -Script "./Modules/AppServiceWebApp/2.0/Tests"; + + # Use the environment variables input below to pass secret variables to this script.' + pwsh: true + - task: AzurePowerShell@4 + displayName: "ARM Validation - App Service WebApp" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/Archetype/definition.json" -ModuleConfigurationName "AppServiceWebApp" -Validate' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: SQLDBServer + pool: + name: 'vdc-self-hosted' + dependsOn: SetupValidationResourceGroup + steps: + - task: PowerShell@2 + displayName: "Pester Tests for Module - SQLDB Server" + inputs: + targetType: 'inline' + script: '# Write your powershell commands here. + + Invoke-Pester -Script "./Modules/SQLDBServer/2.0/Tests"; + + # Use the environment variables input below to pass secret variables to this script.' + pwsh: true + - task: AzurePowerShell@4 + displayName: "ARM Validation - SQLDB Server" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/Archetype/definition.json" -ModuleConfigurationName "SQLDBServer" -Validate' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: SQLDatabase + pool: + name: 'vdc-self-hosted' + dependsOn: SetupValidationResourceGroup + steps: + - task: PowerShell@2 + displayName: "Pester Tests for Module - SQL Database" + inputs: + targetType: 'inline' + script: '# Write your powershell commands here. + + Invoke-Pester -Script "./Modules/SQLDatabase/2.0/Tests"; + + # Use the environment variables input below to pass secret variables to this script.' + pwsh: true + - task: AzurePowerShell@4 + displayName: "ARM Validation - SQL Database" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/Archetype/definition.json" -ModuleConfigurationName "SQLDatabase" -Validate' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: TearDownValidationResourceGroup + pool: + name: 'vdc-self-hosted' + dependsOn: [ KeyVault, AppServiceEnvironments, AppServicePlan, AppServiceWebApp, SQLDBServer, SQLDatabase ] + steps: + - task: AzurePowerShell@4 + displayName: "Teardown Validation Resource Group" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ValidationResourceGroupSetup.ps1' + ScriptArguments: '-TearDownResourceGroup' + azurePowerShellVersion: 'LatestVersion' +- stage: Deploy + jobs: + - job: KeyVault + pool: + name: 'vdc-self-hosted' + steps: + - task: AzurePowerShell@4 + displayName: "Key Vault" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/Archetype/definition.json" -ModuleConfigurationName "KeyVault"' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_SUBSCRIPTIONS:VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: AppServiceEnvironments + timeoutInMinutes: 0 + pool: + name: 'vdc-self-hosted' + steps: + - task: AzurePowerShell@4 + displayName: "App Service Environments" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/Archetype/definition.json" -ModuleConfigurationName "AppServiceEnvironments"' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_SUBSCRIPTIONS:VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: AppServicePlan + timeoutInMinutes: 0 + pool: + name: 'vdc-self-hosted' + dependsOn: AppServiceEnvironments + steps: + - task: AzurePowerShell@4 + displayName: "App Service Plan" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/Archetype/definition.json" -ModuleConfigurationName "AppServicePlan"' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_SUBSCRIPTIONS:VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: AppServiceWebApp + pool: + name: 'vdc-self-hosted' + dependsOn: AppServicePlan + steps: + - task: AzurePowerShell@4 + displayName: "App Service WebApp" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/Archetype/definition.json" -ModuleConfigurationName "AppServiceWebApp"' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_SUBSCRIPTIONS:VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: SQLDBServer + timeoutInMinutes: 0 + pool: + name: 'vdc-self-hosted' + dependsOn: KeyVault + steps: + - task: AzurePowerShell@4 + displayName: "SQLDB Server" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/Archetype/definition.json" -ModuleConfigurationName "SQLDBServer"' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_SUBSCRIPTIONS:VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: SQLDatabase + timeoutInMinutes: 0 + pool: + name: 'vdc-self-hosted' + dependsOn: SQLDBServer + steps: + - task: AzurePowerShell@4 + displayName: "SQLDatabase" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/Archetype/definition.json" -ModuleConfigurationName "SQLDatabase"' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_SUBSCRIPTIONS:VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) \ No newline at end of file diff --git a/Environments/NTier-IaaS/LandingZone/NetworkParameters/applicationSecurityGroups.json b/Environments/NTier-IaaS/LandingZone/NetworkParameters/applicationSecurityGroups.json new file mode 100644 index 0000000..bcf4de8 --- /dev/null +++ b/Environments/NTier-IaaS/LandingZone/NetworkParameters/applicationSecurityGroups.json @@ -0,0 +1,12 @@ +{ + "ResourceGroup": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.ResourceGroup}", + "Web": { + "Name": "web-asg" + }, + "Business": { + "Name": "business-asg" + }, + "Data": { + "Name": "data-asg" + } +} \ No newline at end of file diff --git a/Environments/NTier-IaaS/LandingZone/NetworkParameters/networkSecurityGroups.json b/Environments/NTier-IaaS/LandingZone/NetworkParameters/networkSecurityGroups.json new file mode 100644 index 0000000..8c3f6a7 --- /dev/null +++ b/Environments/NTier-IaaS/LandingZone/NetworkParameters/networkSecurityGroups.json @@ -0,0 +1,178 @@ +{ + "ResourceGroup": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.ResourceGroup}", + "Default": { + "Name": "default-nsg", + "Rules": [ + { + "name": "allow-azure-loadbalancer", + "properties": { + "access": "Allow", + "destinationAddressPrefixes": [], + "destinationAddressPrefix": "VirtualNetwork", + "destinationPortRange": "*", + "destinationPortRanges": [], + "destinationApplicationSecurityGroups": [], + "direction": "Inbound", + "priority": 100, + "protocol": "*", + "sourcePortRange": "*", + "sourcePortRanges": [], + "sourceAddressPrefix": "AzureLoadBalancer", + "sourceApplicationSecurityGroups": [] + } + }, + { + "name": "allow-vnet-to-web", + "properties": { + "access": "Allow", + "destinationAddressPrefixes": [], + "destinationAddressPrefix": "", + "destinationPortRange": "", + "destinationPortRanges": [ + "80", + "443" + ], + "destinationApplicationSecurityGroups": [ + { + "name": "${Parameters.ModuleConfigurationParameters.ApplicationSecurityGroups.Web.Name}" + } + ], + "direction": "Inbound", + "priority": 110, + "protocol": "*", + "sourcePortRange": "*", + "sourcePortRanges": [], + "sourceAddressPrefix": "VirtualNetwork", + "sourceApplicationSecurityGroups": [] + } + }, + { + "name": "allow-web-to-business", + "properties": { + "access": "Allow", + "destinationAddressPrefixes": [], + "destinationAddressPrefix": "", + "destinationPortRange": "", + "destinationPortRanges": [ + "80", + "443" + ], + "destinationApplicationSecurityGroups": [ + { + "name": "${Parameters.ModuleConfigurationParameters.ApplicationSecurityGroups.Business.Name}" + } + ], + "direction": "Inbound", + "priority": 120, + "protocol": "*", + "sourcePortRange": "*", + "sourcePortRanges": [], + "sourceAddressPrefix": "", + "sourceApplicationSecurityGroups": [ + { + "name": "${Parameters.ModuleConfigurationParameters.ApplicationSecurityGroups.Web.Name}" + } + ] + } + }, + { + "name": "allow-business-to-data", + "properties": { + "access": "Allow", + "destinationAddressPrefixes": [], + "destinationAddressPrefix": "", + "destinationPortRange": "1433", + "destinationPortRanges": [], + "destinationApplicationSecurityGroups": [ + { + "name": "${Parameters.ModuleConfigurationParameters.ApplicationSecurityGroups.Data.Name}" + } + ], + "direction": "Inbound", + "priority": 130, + "protocol": "*", + "sourcePortRange": "*", + "sourcePortRanges": [], + "sourceAddressPrefix": "", + "sourceApplicationSecurityGroups": [ + { + "name": "${Parameters.ModuleConfigurationParameters.ApplicationSecurityGroups.Business.Name}" + } + ] + } + }, + { + "name": "allow-rdp", + "properties": { + "access": "Allow", + "destinationAddressPrefixes": [], + "destinationAddressPrefix": "VirtualNetwork", + "destinationPortRange": "3389", + "destinationPortRanges": [], + "destinationApplicationSecurityGroups": [], + "direction": "Inbound", + "priority": 200, + "protocol": "Tcp", + "sourcePortRange": "*", + "sourcePortRanges": [], + "sourceAddressPrefix": "${Parameters.ModuleConfigurationParameters.SharedServices.Jumpbox_SubnetAddressPrefix}", + "sourceApplicationSecurityGroups": [] + } + }, + { + "name": "allow-ssh", + "properties": { + "access": "Allow", + "destinationAddressPrefixes": [], + "destinationAddressPrefix": "VirtualNetwork", + "destinationPortRange": "22", + "destinationPortRanges": [], + "destinationApplicationSecurityGroups": [], + "direction": "Inbound", + "priority": 210, + "protocol": "Tcp", + "sourcePortRange": "*", + "sourcePortRanges": [], + "sourceAddressPrefix": "${Parameters.ModuleConfigurationParameters.SharedServices.Jumpbox_SubnetAddressPrefix}", + "sourceApplicationSecurityGroups": [] + } + }, + { + "name": "deny-internet", + "properties": { + "access": "Deny", + "destinationAddressPrefixes": [], + "destinationAddressPrefix": "*", + "destinationPortRange": "*", + "destinationPortRanges": [], + "destinationApplicationSecurityGroups": [], + "direction": "Inbound", + "priority": 4095, + "protocol": "Tcp", + "sourcePortRange": "*", + "sourcePortRanges": [], + "sourceAddressPrefix": "Internet", + "sourceApplicationSecurityGroups": [] + } + }, + { + "name": "deny-vnet", + "properties": { + "access": "Deny", + "destinationAddressPrefixes": [], + "destinationAddressPrefix": "VirtualNetwork", + "destinationPortRange": "*", + "destinationPortRanges": [], + "destinationApplicationSecurityGroups": [], + "direction": "Inbound", + "priority": 4096, + "protocol": "Tcp", + "sourcePortRange": "*", + "sourcePortRanges": [], + "sourceAddressPrefix": "VirtualNetwork", + "sourceApplicationSecurityGroups": [] + } + } + ] + } +} \ No newline at end of file diff --git a/Environments/NTier-IaaS/LandingZone/NetworkParameters/routeTables.json b/Environments/NTier-IaaS/LandingZone/NetworkParameters/routeTables.json new file mode 100644 index 0000000..fe1fcae --- /dev/null +++ b/Environments/NTier-IaaS/LandingZone/NetworkParameters/routeTables.json @@ -0,0 +1,23 @@ +{ + "ResourceGroup": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.ResourceGroup}", + "Default": { + "Name": "default-udr", + "Routes": [ + { + "name": "default", + "properties": { + "addressPrefix": "0.0.0.0/0", + "nextHopIpAddress": "reference(${Parameters.Organization}-shrdsvcs.AzureFirewall.azureFirewallPrivateIp)", + "nextHopType": "VirtualAppliance" + } + }, + { + "name": "to-on-premises", + "properties": { + "addressPrefix": "${Parameters.ModuleConfigurationParameters.OnPremisesInformation.Network.AddressPrefix}", + "nextHopType": "VirtualNetworkGateway" + } + } + ] + } +} \ No newline at end of file diff --git a/Environments/NTier-IaaS/LandingZone/NetworkParameters/virtualNetwork.json b/Environments/NTier-IaaS/LandingZone/NetworkParameters/virtualNetwork.json new file mode 100644 index 0000000..f95b5a4 --- /dev/null +++ b/Environments/NTier-IaaS/LandingZone/NetworkParameters/virtualNetwork.json @@ -0,0 +1,30 @@ +{ + "Name": "${Parameters.InstanceName}-vnet", + "ResourceGroup": "${Parameters.InstanceName}-network-rg", + "AddressPrefixes": [ + "172.2.0.0/16" + ], + "EnableDdosProtection": false, + "Subnets": [ + { + "name": "default", + "addressPrefix": "172.2.0.0/24", + "networkSecurityGroupName": "${Parameters.ModuleConfigurationParameters.NetworkSecurityGroups.Default.Name}", + "routeTableName": "${Parameters.ModuleConfigurationParameters.RouteTables.Default.Name}", + "serviceEndpoints": [{ + "service": "Microsoft.EventHub" + }, + { + "service": "Microsoft.Sql" + }, + { + "service": "Microsoft.Storage" + }, + { + "service": "Microsoft.KeyVault" + } + ] + } + ], + "DnsServers": "reference(${Parameters.Organization}-shrdsvcs.InstallActiveDirectoryDomainServices.dnsServers)" +} \ No newline at end of file diff --git a/Environments/NTier-IaaS/LandingZone/definition.json b/Environments/NTier-IaaS/LandingZone/definition.json new file mode 100644 index 0000000..dba4af8 --- /dev/null +++ b/Environments/NTier-IaaS/LandingZone/definition.json @@ -0,0 +1,5 @@ +{ + "Subscriptions": "env(VDC_SUBSCRIPTIONS)", + "Parameters": "file(./parameters.json)", + "Orchestration": "file(./orchestration.json)" +} \ No newline at end of file diff --git a/Environments/NTier-IaaS/LandingZone/orchestration.json b/Environments/NTier-IaaS/LandingZone/orchestration.json new file mode 100644 index 0000000..be5d4b0 --- /dev/null +++ b/Environments/NTier-IaaS/LandingZone/orchestration.json @@ -0,0 +1,273 @@ +{ + "ModuleConfigurationsPath": "../../../Modules", + "ModuleConfigurations": [ + { + "Name": "SubscriptionCreation", + "Enabled": false, + "Script": { + "Command": "../../../Scripts/Subscription/NewSubscription.ps1", + "Arguments": { + "SubscriptionName": "vdc2-nist-workload10", + "Location": "West US 2", + "TenantId": "env(TENANT_ID)", + "OfferType": "MS-AZR-0017P" + }, + "UpdatePath": "Subscriptions.ASE_SQLDB" + } + }, + { + "Name": "DiagnosticStorageAccount", + "ModuleDefinitionName": "StorageAccounts", + "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.DiagnosticStorageAccount.ResourceGroup}", + "Comments": "Storage Account that is used to store Diagnostic information of VMs and Non-VM resources", + "Version": "2.0", + "Policies": { + "Comments": "Policies is Optional - If no object is specified, no Policies deployment will occur", + "OverrideParameters": { + "effect": { + "value": "${Parameters.ModuleConfigurationParameters.DiagnosticStorageAccount.Policies.Effect}" + }, + "resourceGroup": { + "value": "${Parameters.ModuleConfigurationParameters.DiagnosticStorageAccount.ResourceGroup}" + }, + "resourceGroupLocation": { + "value": "${Parameters.Location}" + } + } + }, + "Deployment": { + "Comments": "We need the 'update' module instance to lock this resource after the Virtual Network got created", + "TemplatePath": "../../../Modules/StorageAccounts/2.0/deploy.json", + "OverrideParameters": { + "storageAccountName": { + "value": "${Parameters.ModuleConfigurationParameters.DiagnosticStorageAccount.Name}" + }, + "storageAccountSku": { + "value": "${Parameters.ModuleConfigurationParameters.DiagnosticStorageAccount.Sku}" + } + } + } + }, + { + "Name": "LogAnalytics", + "ModuleDefinitionName": "LogAnalytics", + "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.LogAnalytics.ResourceGroup}", + "DependsOn": [ + "DiagnosticStorageAccount" + ], + "Deployment": { + "OverrideParameters": { + "logAnalyticsWorkspaceName": { + "value": "${Parameters.ModuleConfigurationParameters.LogAnalytics.Name}" + }, + "diagnosticStorageAccountName": { + "value": "reference(DiagnosticStorageAccount.storageAccountName)" + }, + "diagnosticStorageAccountId": { + "value": "reference(DiagnosticStorageAccount.storageAccountResourceId)" + }, + "diagnosticStorageAccountAccessKey": { + "value": "reference(DiagnosticStorageAccount.storageAccountAccessKey)" + }, + "location": { + "value": "${Parameters.ModuleConfigurationParameters.LogAnalytics.Location}" + } + } + } + }, + { + "Name": "AzureSecurityCenter", + "ModuleDefinitionName": "AzureSecurityCenter", + "DependsOn": [ + "LogAnalytics" + ], + "Deployment": { + "OverrideParameters": { + "workspaceId": { + "value": "reference(LogAnalytics.logAnalyticsWorkspaceResourceId)" + } + } + } + }, + { + "Name": "NISTControls", + "ModuleDefinitionName": "NISTControls", + "DependsOn": [ + "LogAnalytics" + ], + "Deployment": { + "OverrideParameters": { + "workspaceId": { + "value": "reference(LogAnalytics.logAnalyticsWorkspaceId)" + }, + "location": { + "value": "${Parameters.Location}" + } + } + } + }, + { + "Name": "WebASG", + "ModuleDefinitionName": "ApplicationSecurityGroups", + "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.ApplicationSecurityGroups.ResourceGroup}", + "DependsOn": [], + "Deployment": { + "OverrideParameters": { + "applicationSecurityGroupName": { + "value": "${Parameters.ModuleConfigurationParameters.ApplicationSecurityGroups.Web.Name}" + } + } + } + }, + { + "Name": "BusinessASG", + "ModuleDefinitionName": "ApplicationSecurityGroups", + "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.ApplicationSecurityGroups.ResourceGroup}", + "DependsOn": [], + "Deployment": { + "OverrideParameters": { + "applicationSecurityGroupName": { + "value": "${Parameters.ModuleConfigurationParameters.ApplicationSecurityGroups.Business.Name}" + } + } + } + }, + { + "Name": "DataASG", + "ModuleDefinitionName": "ApplicationSecurityGroups", + "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.ApplicationSecurityGroups.ResourceGroup}", + "DependsOn": [], + "Deployment": { + "OverrideParameters": { + "applicationSecurityGroupName": { + "value": "${Parameters.ModuleConfigurationParameters.ApplicationSecurityGroups.Data.Name}" + } + } + } + }, + { + "Name": "DefaultNSG", + "ModuleDefinitionName": "NetworkSecurityGroups", + "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.NetworkSecurityGroups.ResourceGroup}", + "DependsOn": [ + "WebASG", + "BusinessASG", + "DataASG", + "DiagnosticStorageAccount", + "LogAnalytics" + ], + "Deployment": { + "OverrideParameters": { + "workspaceId": { + "value": "reference(LogAnalytics.logAnalyticsWorkspaceResourceId)" + }, + "diagnosticStorageAccountId": { + "value": "reference(DiagnosticStorageAccount.storageAccountResourceId)" + }, + "networkSecurityGroupName": { + "value": "${Parameters.ModuleConfigurationParameters.NetworkSecurityGroups.Default.Name}" + }, + "networkSecurityGroupSecurityRules": { + "value": "${Parameters.ModuleConfigurationParameters.NetworkSecurityGroups.Default.Rules}" + } + } + } + }, + { + "Name": "DefaultRouteTable", + "ModuleDefinitionName": "RouteTables", + "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.RouteTables.ResourceGroup}", + "DependsOn": [], + "Deployment": { + "OverrideParameters": { + "routeTableName": { + "value": "${Parameters.ModuleConfigurationParameters.RouteTables.Default.Name}" + }, + "routes": { + "value": "${Parameters.ModuleConfigurationParameters.RouteTables.Default.Routes}" + } + } + } + }, + { + "Name": "VirtualNetwork", + "ModuleDefinitionName": "VirtualNetwork", + "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.ResourceGroup}", + "DependsOn": [ + "DefaultNSG", + "DefaultRouteTable" + ], + "Deployment": { + "OverrideParameters": { + "vnetName": { + "value": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.Name}" + }, + "vnetAddressPrefixes": { + "value": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.AddressPrefixes}" + }, + "dnsServers": { + "value": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.DnsServers}" + }, + "subnets": { + "value": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.Subnets}" + }, + "enableDdosProtection": { + "value": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.EnableDdosProtection}" + }, + "enableVmProtection": { + "value": false + } + } + } + }, + { + "Name": "LocalVirtualNetworkPeering", + "ModuleDefinitionName": "VirtualNetworkPeering", + "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.ResourceGroup}", + "DependsOn": [ + "VirtualNetwork" + ], + "Deployment": { + "OverrideParameters": { + "localVnetName": { + "value": "reference(VirtualNetwork.vNetName)" + }, + "peeringName": { + "value": "${Parameters.ModuleConfigurationParameters.VirtualNetworkPeering.LocalPeering.Name}" + }, + "remoteVirtualNetworkId": { + "value": "reference(${Parameters.Organization}-shrdsvcs.VirtualNetwork.vNetResourceId)" + } + } + } + }, + { + "Name": "RemoteVirtualNetworkPeering", + "ModuleDefinitionName": "VirtualNetworkPeering", + "Subscription": "SharedServices", + "ResourceGroupName": "reference(${Parameters.Organization}-shrdsvcs.VirtualNetwork.vNetResourceGroup)", + "DependsOn": [ + "VirtualNetwork" + ], + "Deployment": { + "OverrideParameters": { + "localVnetName": { + "value": "reference(${Parameters.Organization}-shrdsvcs.VirtualNetwork.vNetName)" + }, + "peeringName": { + "value": "${Parameters.ModuleConfigurationParameters.VirtualNetworkPeering.RemotePeering.Name}" + }, + "remoteVirtualNetworkId": { + "value": "reference(${Parameters.InstanceName}.VirtualNetwork.vNetResourceId)" + }, + "allowGatewayTransit": { + "value": "${Parameters.ModuleConfigurationParameters.VirtualNetworkPeering.RemotePeering.AllowGatewayTransit}" + }, + "useRemoteGateways": { + "value": "${Parameters.ModuleConfigurationParameters.VirtualNetworkPeering.RemotePeering.UseRemoteGateways}" + } + } + } + } + ] +} \ No newline at end of file diff --git a/Environments/NTier-IaaS/LandingZone/parameters.json b/Environments/NTier-IaaS/LandingZone/parameters.json new file mode 100644 index 0000000..7990356 --- /dev/null +++ b/Environments/NTier-IaaS/LandingZone/parameters.json @@ -0,0 +1,51 @@ +{ + "Organization": "env(ORGANIZATION_NAME)", + "DeploymentName": "ntier-iaas", + "InstanceName": "${Parameters.Organization}-${Parameters.DeploymentName}", + "Subscription": "NTier_IaaS", + "ModuleConfigurationParameters": { + "OnPremisesInformation": { + "Network": { + "AddressPrefix": "192.168.1.0/28" + } + }, + "SharedServices": { + "Jumpbox_SubnetAddressPrefix": "172.0.0.0/24" + }, + "DiagnosticStorageAccount": { + "Name": "${Parameters.Organization}asesqldiag01", + "ResourceGroup": "${Parameters.InstanceName}-diagnostics-rg", + "Sku": "Standard_GRS", + "NetworkAcls": { + "bypass": "AzureServices", + "defaultAction": "Deny", + "virtualNetworkRules": [{ + "subnet": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.Subnets[0].Name}" + }], + "ipRules": [] + }, + "Policies": { + "Effect": "Audit" + } + }, + "LogAnalytics": { + "Name": "${Parameters.InstanceName}-la", + "ResourceGroup": "${Parameters.InstanceName}-diagnostics-rg", + "Location": "${Parameters.Location}" + }, + "ApplicationSecurityGroups": "file(NetworkParameters/applicationSecurityGroups.json)", + "NetworkSecurityGroups": "file(NetworkParameters/networkSecurityGroups.json)", + "RouteTables": "file(NetworkParameters/routeTables.json)", + "VirtualNetwork": "file(NetworkParameters/virtualNetwork.json)", + "VirtualNetworkPeering": { + "LocalPeering": { + "Name": "${Parameters.DeploymentName}-to-sharedsvcs" + }, + "RemotePeering": { + "Name": "sharedsvcs-to-${Parameters.DeploymentName}", + "AllowGatewayTransit": true, + "UseRemoteGateways": false + } + } + } +} \ No newline at end of file diff --git a/Environments/NTier-IaaS/LandingZone/pipeline.yml b/Environments/NTier-IaaS/LandingZone/pipeline.yml new file mode 100644 index 0000000..58e4f4a --- /dev/null +++ b/Environments/NTier-IaaS/LandingZone/pipeline.yml @@ -0,0 +1,452 @@ +variables: +- group: VDC_SECRETS +trigger: +- master +stages: +- stage: Validate + jobs: + - job: SetupValidationResourceGroup + pool: + name: 'vdc-self-hosted' + steps: + - task: AzurePowerShell@4 + displayName: "Setup Validation Resource Group" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ValidationResourceGroupSetup.ps1' + ScriptArguments: '-ResourceGroupName vdc-validation-rg -SetupResourceGroup' + azurePowerShellVersion: 'LatestVersion' + - job: StorageAccounts + pool: + name: 'vdc-self-hosted' + dependsOn: SetupValidationResourceGroup + steps: + - task: PowerShell@2 + displayName: "Pester Tests for Module - Storage Accounts" + inputs: + targetType: 'inline' + script: '# Write your powershell commands here. + + Invoke-Pester -Script "./Modules/StorageAccounts/2.0/Tests"; + + # Use the environment variables input below to pass secret variables to this script.' + pwsh: true + - task: AzurePowerShell@4 + displayName: "ARM Validation - Diagnostic Storage Account" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/LandingZone/definition.json" -ModuleConfigurationName "DiagnosticStorageAccount" -Validate' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: LogAnalytics + pool: + name: 'vdc-self-hosted' + dependsOn: SetupValidationResourceGroup + steps: + - task: PowerShell@2 + displayName: "Pester Tests for Module - Log Analytics" + inputs: + targetType: 'inline' + script: '# Write your powershell commands here. + + Invoke-Pester -Script "./Modules/LogAnalytics/2.0/Tests"; + + # Use the environment variables input below to pass secret variables to this script.' + pwsh: true + - task: AzurePowerShell@4 + displayName: "ARM Validation - Log Analytics" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/LandingZone/definition.json" -ModuleConfigurationName "LogAnalytics" -Validate' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: AzureSecurityCenter + pool: + name: 'vdc-self-hosted' + dependsOn: SetupValidationResourceGroup + steps: + - task: PowerShell@2 + displayName: "Pester Tests for Module - Azure Security Center" + inputs: + targetType: 'inline' + script: '# Write your powershell commands here. + + Invoke-Pester -Script "./Modules/AzureSecurityCenter/2.0/Tests"; + + # Use the environment variables input below to pass secret variables to this script.' + pwsh: true + - task: AzurePowerShell@4 + displayName: "ARM Validation - Azure Security Center" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/LandingZone/definition.json" -ModuleConfigurationName "AzureSecurityCenter" -Validate' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: NISTControls + pool: + name: 'vdc-self-hosted' + dependsOn: SetupValidationResourceGroup + steps: + - task: PowerShell@2 + displayName: "Pester Tests for Module - NIST Controls" + inputs: + targetType: 'inline' + script: '# Write your powershell commands here. + + Invoke-Pester -Script "./Modules/NISTControls/2.0/Tests"; + + # Use the environment variables input below to pass secret variables to this script.' + pwsh: true + - task: AzurePowerShell@4 + displayName: "ARM Validation - NIST Controls" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/LandingZone/definition.json" -ModuleConfigurationName "NISTControls" -Validate' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: NetworkSecurityGroups + pool: + name: 'vdc-self-hosted' + dependsOn: SetupValidationResourceGroup + steps: + - task: PowerShell@2 + displayName: "Pester Tests for Module - Network Security Groups" + inputs: + targetType: 'inline' + script: '# Write your powershell commands here. + + Invoke-Pester -Script "./Modules/NetworkSecurityGroups/2.0/Tests"; + + # Use the environment variables input below to pass secret variables to this script.' + pwsh: true + - task: AzurePowerShell@4 + displayName: "ARM Validation - Default NSG" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/LandingZone/definition.json" -ModuleConfigurationName "DefaultNSG" -Validate' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: RouteTables + pool: + name: 'vdc-self-hosted' + dependsOn: SetupValidationResourceGroup + steps: + - task: PowerShell@2 + displayName: "Pester Tests for Module - Route Tables" + inputs: + targetType: 'inline' + script: '# Write your powershell commands here. + + Invoke-Pester -Script "./Modules/RouteTables/2.0/Tests"; + + # Use the environment variables input below to pass secret variables to this script.' + pwsh: true + - task: AzurePowerShell@4 + displayName: "ARM Validation - Default Route Table" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/LandingZone/definition.json" -ModuleConfigurationName "DefaultRouteTable" -Validate' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: vNet + pool: + name: 'vdc-self-hosted' + dependsOn: SetupValidationResourceGroup + steps: + - task: PowerShell@2 + displayName: "Pester Tests for Module - vNet" + inputs: + targetType: 'inline' + script: '# Write your powershell commands here. + + Invoke-Pester -Script "./Modules/vNet/2.0/Tests"; + + # Use the environment variables input below to pass secret variables to this script.' + pwsh: true + - task: AzurePowerShell@4 + displayName: "ARM Validation - Virtual Network" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/LandingZone/definition.json" -ModuleConfigurationName "VirtualNetwork" -Validate' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: VirtualNetworkPeering + pool: + name: 'vdc-self-hosted' + dependsOn: SetupValidationResourceGroup + steps: + - task: PowerShell@2 + displayName: "Pester Tests for Module - vNet Peering" + inputs: + targetType: 'inline' + script: '# Write your powershell commands here. + + Invoke-Pester -Script "./Modules/vNetPeering/2.0/Tests"; + + # Use the environment variables input below to pass secret variables to this script.' + pwsh: true + - task: AzurePowerShell@4 + displayName: "ARM Validation - Local Virtual Network Peering" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/LandingZone/definition.json" -ModuleConfigurationName "LocalVirtualNetworkPeering" -Validate' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: TearDownValidationResourceGroup + pool: + name: 'vdc-self-hosted' + dependsOn: [ StorageAccounts, LogAnalytics, AzureSecurityCenter, NISTControls, NetworkSecurityGroups, RouteTables, vNet, VirtualNetworkPeering ] + steps: + - task: AzurePowerShell@4 + displayName: "Teardown Validation Resource Group" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ValidationResourceGroupSetup.ps1' + ScriptArguments: '-TearDownResourceGroup' + azurePowerShellVersion: 'LatestVersion' +- stage: Deploy + jobs: + - job: DiagnosticStorageAccount + pool: + name: 'vdc-self-hosted' + steps: + - task: AzurePowerShell@4 + displayName: "Diagnostic Storage Account" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/LandingZone/definition.json" -ModuleConfigurationName "DiagnosticStorageAccount"' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_SUBSCRIPTIONS:VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: LogAnalytics + pool: + name: 'vdc-self-hosted' + dependsOn: DiagnosticStorageAccount + steps: + - task: AzurePowerShell@4 + displayName: "Log Analytics" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/LandingZone/definition.json" -ModuleConfigurationName "LogAnalytics"' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_SUBSCRIPTIONS:VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: AzureSecurityCenter + pool: + name: 'vdc-self-hosted' + dependsOn: LogAnalytics + steps: + - task: AzurePowerShell@4 + displayName: "Azure Security Center" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/LandingZone/definition.json" -ModuleConfigurationName "AzureSecurityCenter"' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_SUBSCRIPTIONS:VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: NISTControls + pool: + name: 'vdc-self-hosted' + dependsOn: LogAnalytics + steps: + - task: AzurePowerShell@4 + displayName: "NIST Controls" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/LandingZone/definition.json" -ModuleConfigurationName "NISTControls"' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_SUBSCRIPTIONS:VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: DefaultNetworkSecurityGroup + pool: + name: 'vdc-self-hosted' + dependsOn: [ DiagnosticStorageAccount, LogAnalytics ] + steps: + - task: AzurePowerShell@4 + displayName: "Default Network Security Group" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/LandingZone/definition.json" -ModuleConfigurationName "DefaultNSG"' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_SUBSCRIPTIONS:VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: DefaultRouteTable + pool: + name: 'vdc-self-hosted' + steps: + - task: AzurePowerShell@4 + displayName: "Default Route Table" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/LandingZone/definition.json" -ModuleConfigurationName "DefaultRouteTable"' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_SUBSCRIPTIONS:VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: VirtualNetwork + pool: + name: 'vdc-self-hosted' + dependsOn: [ DefaultNetworkSecurityGroup, DefaultRouteTable ] + steps: + - task: AzurePowerShell@4 + displayName: "Virtual Network" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/LandingZone/definition.json" -ModuleConfigurationName "VirtualNetwork"' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_SUBSCRIPTIONS:VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: WorkloadPeeringToSharedServices + pool: + name: 'vdc-self-hosted' + dependsOn: 'VirtualNetwork' + steps: + - task: AzurePowerShell@4 + displayName: "Workload Virtual Network Peering to Shared Services" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/LandingZone/definition.json" -ModuleConfigurationName "LocalVirtualNetworkPeering"' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_SUBSCRIPTIONS:VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) + - job: SharedServicesPeeringToWorkload + pool: + name: 'vdc-self-hosted' + dependsOn: 'VirtualNetwork' + steps: + - task: AzurePowerShell@4 + displayName: "Shared Services Virtual Network Peering to Workload" + inputs: + azureSubscription: 'vdc2-spoke1' + ScriptType: 'FilePath' + ScriptPath: 'Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1' + ScriptArguments: '-DefinitionPath "Environments/ASE_SQLDB/LandingZone/definition.json" -ModuleConfigurationName "RemoteVirtualNetworkPeering"' + azurePowerShellVersion: 'LatestVersion' + env: + VDC_SUBSCRIPTIONS: $(VDC_SUBSCRIPTIONS) + VDC_SUBSCRIPTIONS:VDC_TOOLKIT_SUBSCRIPTION: $(VDC_TOOLKIT_SUBSCRIPTION) + DEPLOYMENT_USER_ID: $(DEPLOYMENT_USER_ID) + ADMIN_USER_PWD: $(ADMIN_USER_PWD) + DOMAIN_ADMIN_USER_PWD: $(DOMAIN_ADMIN_USER_PWD) + TENANT_ID: $(TENANT_ID) \ No newline at end of file diff --git a/Environments/SharedServices/orchestration.json b/Environments/SharedServices/orchestration.json index f9bb4bf..de3235e 100644 --- a/Environments/SharedServices/orchestration.json +++ b/Environments/SharedServices/orchestration.json @@ -5,6 +5,7 @@ "Name": "DiagnosticStorageAccount", "ModuleDefinitionName": "StorageAccounts", "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.DiagnosticStorageAccount.ResourceGroup}", + "DependsOn": [], "Comments": "Storage Account that is used to store diagnostic information as raw logs", "Version": "2.0", "Policies": { @@ -354,10 +355,7 @@ { "Name": "WindowsJumpboxKek", "DependsOn": [ - "KeyVault", - "VirtualNetwork", - "DiagnosticStorageAccount", - "LogAnalytics" + "KeyVault" ], "Script": { "Command": "../../Modules/KeyVault/2.0/Scripts/create.keys.ps1", @@ -372,10 +370,7 @@ { "Name": "LinuxJumpboxKek", "DependsOn": [ - "KeyVault", - "VirtualNetwork", - "DiagnosticStorageAccount", - "LogAnalytics" + "KeyVault" ], "Script": { "Command": "../../Modules/KeyVault/2.0/Scripts/create.keys.ps1", @@ -390,10 +385,7 @@ { "Name": "ActiveDirectoryKek", "DependsOn": [ - "KeyVault", - "VirtualNetwork", - "DiagnosticStorageAccount", - "LogAnalytics" + "KeyVault" ], "Script": { "Command": "../../Modules/KeyVault/2.0/Scripts/create.keys.ps1", @@ -408,10 +400,7 @@ { "Name": "ActiveDirectoryDomainServicesKek", "DependsOn": [ - "KeyVault", - "VirtualNetwork", - "DiagnosticStorageAccount", - "LogAnalytics" + "KeyVault" ], "Script": { "Command": "../../Modules/KeyVault/2.0/Scripts/create.keys.ps1", @@ -429,20 +418,16 @@ "Updates": "KeyVault", "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.KeyVault.ResourceGroup}", "DependsOn": [ + "VirtualNetwork", "KeyVault", "WindowsJumpbox", "LinuxJumpbox", - "ActiveDirectoryVM", - "EncryptActiveDirectory", "InstallActiveDirectory", - "ActiveDirectoryDomainServicesVMs", - "EncryptActiveDirectoryDomainServices", "InstallActiveDirectoryDomainServices", "ActiveDirectoryDomainServicesKek", "WindowsJumpboxKek", "LinuxJumpboxKek", - "ActiveDirectoryKek", - "VirtualNetwork" + "ActiveDirectoryKek" ], "Deployment": { "OverrideParameters": { @@ -496,7 +481,8 @@ "DiagnosticStorageAccount", "LogAnalytics", "KeyVault", - "ArtifactsStorageAccount" + "ArtifactsStorageAccount", + "WindowsJumpbox" ], "Comments": "Creates Active Directory Domain Services VMs", "Deployment": { @@ -566,13 +552,8 @@ "ModuleDefinitionName": "VirtualMachineKeyEncryptionKey", "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.ActiveDirectory.ResourceGroup}", "DependsOn": [ - "VirtualNetwork", - "DiagnosticStorageAccount", - "LogAnalytics", - "EnableDnsServersOnVirtualNetwork", - "KeyVault", - "ArtifactsStorageAccount", - "ActiveDirectoryVM" + "ActiveDirectoryVM", + "ActiveDirectoryKek" ], "Comments": "Encrypts VMs using bitlocker", "Deployment": { @@ -600,12 +581,6 @@ "ModuleDefinitionName": "ActiveDirectory", "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.ActiveDirectory.ResourceGroup}", "DependsOn": [ - "VirtualNetwork", - "DiagnosticStorageAccount", - "LogAnalytics", - "KeyVault", - "ArtifactsStorageAccount", - "ActiveDirectoryVM", "EncryptActiveDirectory" ], "Comments": "Creates Active Directory Domain Services VMs", @@ -651,8 +626,6 @@ "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.ResourceGroup}", "DependsOn": [ "InstallActiveDirectory", - "ActiveDirectoryVM", - "EncryptActiveDirectory", "VirtualNetwork" ], "Deployment": { @@ -663,11 +636,21 @@ } } }, + { + "Name": "RegisterAzureBastionFeature", + "Enabled": false, + "Script": { + "Command": "../../Modules/AzureBastion/2.0/Scripts/RegisterProviderFeature.ps1", + "Arguments": {} + } + }, { "Name": "AzureBastion", + "Enabled": false, "ModuleDefinitionName": "AzureBastion", "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.AzureBastion.ResourceGroup}", "DependsOn": [ + "RegisterAzureBastionFeature", "VirtualNetwork" ], "Deployment": { @@ -675,6 +658,9 @@ "azureBastionName": { "value": "${Parameters.ModuleConfigurationParameters.AzureBastion.Name}" }, + "location": { + "value": "${Parameters.ModuleConfigurationParameters.AzureBastion.Location}" + }, "vNetId": { "value": "reference(VirtualNetwork.vNetResourceId)" } @@ -689,12 +675,9 @@ "VirtualNetwork", "DiagnosticStorageAccount", "LogAnalytics", - "InstallActiveDirectory", - "ActiveDirectoryVM", - "EncryptActiveDirectory", - "EnableDnsServersOnVirtualNetwork", "KeyVault", - "ArtifactsStorageAccount" + "ArtifactsStorageAccount", + "JumpboxASG" ], "Comments": "Creates Windows Jumpbox", "Deployment": { @@ -766,20 +749,12 @@ }, { "Name": "EncryptWindowsJumpbox", + "Enabled": false, "ModuleDefinitionName": "VirtualMachineKeyEncryptionKey", "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.Jumpbox.ResourceGroup}", "DependsOn": [ - "VirtualNetwork", - "DiagnosticStorageAccount", - "LogAnalytics", - "InstallActiveDirectory", - "ActiveDirectoryVM", - "EncryptActiveDirectory", - "EnableDnsServersOnVirtualNetwork", - "KeyVault", - "ArtifactsStorageAccount", - "ActiveDirectoryDomainServicesVMs", - "WindowsJumpbox" + "WindowsJumpbox", + "WindowsJumpboxKek" ], "Comments": "Encrypts VMs using bitlocker", "Deployment": { @@ -810,13 +785,9 @@ "VirtualNetwork", "DiagnosticStorageAccount", "LogAnalytics", - "InstallActiveDirectory", - "ActiveDirectoryVM", - "EncryptActiveDirectory", - "EnableDnsServersOnVirtualNetwork", "KeyVault", "ArtifactsStorageAccount", - "AzureBastion" + "JumpboxASG" ], "Comments": "Creates Linux Jumpbox", "Deployment": { @@ -889,18 +860,11 @@ { "Name": "EncryptLinuxJumpbox", "ModuleDefinitionName": "VirtualMachineKeyEncryptionKey", + "Enabled": false, "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.Jumpbox.ResourceGroup}", "DependsOn": [ - "VirtualNetwork", - "DiagnosticStorageAccount", - "LogAnalytics", - "InstallActiveDirectory", - "ActiveDirectoryVM", - "EncryptActiveDirectory", - "EnableDnsServersOnVirtualNetwork", - "KeyVault", - "ArtifactsStorageAccount", - "LinuxJumpbox" + "LinuxJumpbox", + "LinuxJumpboxKek" ], "Comments": "Encrypts VMs using bitlocker", "Deployment": { @@ -932,9 +896,10 @@ "DiagnosticStorageAccount", "LogAnalytics", "WindowsJumpbox", - "EnableDnsServersOnVirtualNetwork", "KeyVault", - "ArtifactsStorageAccount" + "ArtifactsStorageAccount", + "DomainControllerASG", + "EnableDnsServersOnVirtualNetwork" ], "Comments": "Creates Active Directory Domain Services VMs", "Deployment": { @@ -994,7 +959,7 @@ "value": "${Parameters.ModuleConfigurationParameters.ActiveDirectoryDomainServices.AddsIPAddressStart}" }, "applicationSecurityGroupId": { - "value": "reference(JumpboxASG.applicationSecurityGroupResourceId)" + "value": "reference(DomainControllerASG.applicationSecurityGroupResourceId)" }, "adminUsername": { "value": "${Parameters.ModuleConfigurationParameters.ActiveDirectoryDomainServices.AdminUsername}" @@ -1010,17 +975,8 @@ "ModuleDefinitionName": "VirtualMachineKeyEncryptionKey", "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.ActiveDirectoryDomainServices.ResourceGroup}", "DependsOn": [ - "VirtualNetwork", - "DiagnosticStorageAccount", - "LogAnalytics", - "InstallActiveDirectory", - "ActiveDirectoryVM", - "EncryptActiveDirectory", - "WindowsJumpbox", - "EnableDnsServersOnVirtualNetwork", - "KeyVault", - "ArtifactsStorageAccount", - "ActiveDirectoryDomainServicesVMs" + "ActiveDirectoryDomainServicesVMs", + "ActiveDirectoryDomainServicesKek" ], "Comments": "Encrypts Active Directory Domain Services VMs using bitlocker", "Deployment": { @@ -1051,18 +1007,7 @@ "ModuleDefinitionName": "ActiveDirectoryDomainServices", "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.ActiveDirectoryDomainServices.ResourceGroup}", "DependsOn": [ - "InstallActiveDirectory", - "ActiveDirectoryVM", - "EncryptActiveDirectory", - "EnableDnsServersOnVirtualNetwork", - "WindowsJumpbox", - "VirtualNetwork", - "DiagnosticStorageAccount", - "LogAnalytics", - "KeyVault", - "ArtifactsStorageAccount", - "EncryptActiveDirectoryDomainServices", - "ActiveDirectoryDomainServicesVMs" + "EncryptActiveDirectoryDomainServices" ], "Comments": "Installs Active Directory Domain Services", "Deployment": { diff --git a/Environments/SharedServices/parameters.json b/Environments/SharedServices/parameters.json index 36b8e17..f7d0e9f 100644 --- a/Environments/SharedServices/parameters.json +++ b/Environments/SharedServices/parameters.json @@ -77,7 +77,21 @@ "UK South", "West Central US", "West Europe", - "West US 2" + "West US 2", + "West US" + ] + }, + "AzureBastion": { + "ResourceGroup": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.ResourceGroup}", + "Name": "${Parameters.InstanceName}-bastion", + "Location": "West US", + "ListOfAllowedRegions": [ + "West US", + "East US", + "West Europe", + "South Central US", + "Australia East", + "Japan East" ] }, "ApplicationSecurityGroups": { @@ -382,7 +396,7 @@ "EnableVmProtection": false, "Subnets": [ { - "name": "${Parameters.DeploymentName}", + "name": "shrdsvcs", "addressPrefix": "172.0.0.0/24", "networkSecurityGroupName": "${Parameters.ModuleConfigurationParameters.NetworkSecurityGroups.SharedServices.Name}", "routeTableName": "${Parameters.ModuleConfigurationParameters.RouteTables.SharedServices.Name}", @@ -428,6 +442,13 @@ "networkSecurityGroupName": "", "routeTableName": "", "serviceEndpoints": [] + }, + { + "name": "AzureBastionSubnet", + "addressPrefix": "172.0.5.0/24", + "networkSecurityGroupName": "", + "routeTableName": "", + "serviceEndpoints": [] } ], "DnsServers": [ @@ -626,7 +647,7 @@ ] }, "KeyVault": { - "Name": "${Parameters.InstanceName}-kv03", + "Name": "${Parameters.InstanceName}-kv", "ResourceGroup": "${Parameters.InstanceName}-keyvault-rg", "Sku": "Premium", "EnableVaultForDeployment": true, @@ -672,7 +693,7 @@ "secretValue": "env(ADMIN_USER_PWD)" }, { - "secretName": "${Parameters.ModuleConfigurationParameters.ActiveDirectory.DomainAdminUserName}", + "secretName": "env(DOMAIN_ADMIN_USERNAME)", "secretValue": "env(DOMAIN_ADMIN_USER_PWD)" }, { @@ -693,10 +714,6 @@ } }, "ArtifactsStorageAccount": "file(../_Common/artifactsStorageAccount.json)", - "AzureBastion": { - "Name": "sharedsvcs-bastion", - "ResourceGroup": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.ResourceGroup}" - }, "Jumpbox": { "ResourceGroup": "${Parameters.InstanceName}-jumpbox-rg", "AdminUsername": "${Parameters.ModuleConfigurationParameters.KeyVault.SecretsObject.Secrets[0].secretName}", @@ -742,10 +759,10 @@ "ResourceGroup": "${Parameters.InstanceName}-adds-rg", "Comments": "Windows VM name cannot exceed 13 characters.", "PrimaryDomainControllerIP": "172.0.0.10", - "DomainName": "fontoso.com", + "DomainName": "contoso.com", "ADSitename": "Cloud-Site", - "CloudZone": "fontosocloud.com", - "DomainAdminUsername": "fontoso", + "CloudZone": "contosocloud.com", + "DomainAdminUsername": "env(DOMAIN_ADMIN_USERNAME)", "DomainAdminPassword": { "keyVault": { "id": "reference(KeyVault.keyVaultResourceId)" diff --git a/Environments/SharedServices_OnpremExtension/orchestration.json b/Environments/SharedServices_OnpremExtension/orchestration.json index f78487f..a583e39 100644 --- a/Environments/SharedServices_OnpremExtension/orchestration.json +++ b/Environments/SharedServices_OnpremExtension/orchestration.json @@ -325,7 +325,7 @@ "Subscription": "OnPremises", "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.OnPremisesInformation.VirtualNetworkGateway.ResourceGroup}", "DependsOn": [ - "VirtualNetworkGateway" + "LocalVirtualNetworkGatewayConnection" ], "Deployment": { "OverrideParameters": { @@ -456,10 +456,7 @@ { "Name": "WindowsJumpboxKek", "DependsOn": [ - "KeyVault", - "VirtualNetwork", - "DiagnosticStorageAccount", - "LogAnalytics" + "KeyVault" ], "Script": { "Command": "../../Modules/KeyVault/2.0/Scripts/create.keys.ps1", @@ -474,10 +471,7 @@ { "Name": "LinuxJumpboxKek", "DependsOn": [ - "KeyVault", - "VirtualNetwork", - "DiagnosticStorageAccount", - "LogAnalytics" + "KeyVault" ], "Script": { "Command": "../../Modules/KeyVault/2.0/Scripts/create.keys.ps1", @@ -492,10 +486,7 @@ { "Name": "ActiveDirectoryDomainServicesKek", "DependsOn": [ - "KeyVault", - "VirtualNetwork", - "DiagnosticStorageAccount", - "LogAnalytics" + "KeyVault" ], "Script": { "Command": "../../Modules/KeyVault/2.0/Scripts/create.keys.ps1", @@ -513,11 +504,10 @@ "Updates": "KeyVault", "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.KeyVault.ResourceGroup}", "DependsOn": [ + "VirtualNetwork", "KeyVault", "WindowsJumpbox", - "LinuxJumpbox", - "ActiveDirectoryDomainServicesVMs", - "EncryptActiveDirectoryDomainServices", + "LinuxJumpbox", "InstallActiveDirectoryDomainServices", "ActiveDirectoryDomainServicesKek", "WindowsJumpboxKek", @@ -566,11 +556,19 @@ } } }, + { + "Name": "RegisterAzureBastionFeature", + "Script": { + "Command": "../../Modules/AzureBastion/2.0/Scripts/RegisterProviderFeature.ps1", + "Arguments": {} + } + }, { "Name": "AzureBastion", "ModuleDefinitionName": "AzureBastion", "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.AzureBastion.ResourceGroup}", "DependsOn": [ + "RegisterAzureBastionFeature", "VirtualNetwork" ], "Deployment": { @@ -578,6 +576,9 @@ "azureBastionName": { "value": "${Parameters.ModuleConfigurationParameters.AzureBastion.Name}" }, + "location": { + "value": "${Parameters.ModuleConfigurationParameters.AzureBastion.Location}" + }, "vNetId": { "value": "reference(VirtualNetwork.vNetResourceId)" } @@ -592,11 +593,9 @@ "VirtualNetwork", "DiagnosticStorageAccount", "LogAnalytics", - "ActiveDirectoryDomainServicesVMs", - "EncryptActiveDirectoryDomainServices", - "InstallActiveDirectoryDomainServices", "KeyVault", - "ArtifactsStorageAccount" + "ArtifactsStorageAccount", + "JumpboxASG" ], "Comments": "Creates Windows Jumpbox", "Deployment": { @@ -670,16 +669,10 @@ "Name": "EncryptWindowsJumpbox", "ModuleDefinitionName": "VirtualMachineKeyEncryptionKey", "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.Jumpbox.ResourceGroup}", + "Enabled": false, "DependsOn": [ - "VirtualNetwork", - "DiagnosticStorageAccount", - "LogAnalytics", - "KeyVault", - "ArtifactsStorageAccount", - "ActiveDirectoryDomainServicesVMs", - "EncryptActiveDirectoryDomainServices", - "InstallActiveDirectoryDomainServices", - "WindowsJumpbox" + "WindowsJumpbox", + "WindowsJumpboxKek" ], "Comments": "Encrypts VMs using bitlocker", "Deployment": { @@ -712,9 +705,7 @@ "LogAnalytics", "KeyVault", "ArtifactsStorageAccount", - "ActiveDirectoryDomainServicesVMs", - "EncryptActiveDirectoryDomainServices", - "InstallActiveDirectoryDomainServices" + "JumpboxASG" ], "Comments": "Creates Linux Jumpbox", "Deployment": { @@ -788,16 +779,10 @@ "Name": "EncryptLinuxJumpbox", "ModuleDefinitionName": "VirtualMachineKeyEncryptionKey", "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.Jumpbox.ResourceGroup}", + "Enabled": false, "DependsOn": [ - "VirtualNetwork", - "DiagnosticStorageAccount", - "LogAnalytics", - "KeyVault", - "ArtifactsStorageAccount", "LinuxJumpbox", - "ActiveDirectoryDomainServicesVMs", - "EncryptActiveDirectoryDomainServices", - "InstallActiveDirectoryDomainServices" + "LinuxJumpboxKek" ], "Comments": "Encrypts VMs using bitlocker", "Deployment": { @@ -829,9 +814,9 @@ "DiagnosticStorageAccount", "LogAnalytics", "WindowsJumpbox", - "LinuxJumpbox", "KeyVault", - "ArtifactsStorageAccount" + "ArtifactsStorageAccount", + "DomainControllerASG" ], "Comments": "Creates Active Directory Domain Services VMs", "Deployment": { @@ -907,14 +892,8 @@ "ModuleDefinitionName": "VirtualMachineKeyEncryptionKey", "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.ActiveDirectoryDomainServices.ResourceGroup}", "DependsOn": [ - "VirtualNetwork", - "DiagnosticStorageAccount", - "LogAnalytics", - "KeyVault", - "ArtifactsStorageAccount", "ActiveDirectoryDomainServicesVMs", - "LinuxJumpbox", - "WindowsJumpbox" + "ActiveDirectoryDomainServicesKek" ], "Comments": "Encrypts Active Directory Domain Services VMs using bitlocker", "Deployment": { @@ -945,15 +924,7 @@ "ModuleDefinitionName": "ActiveDirectoryDomainServices", "ResourceGroupName": "${Parameters.ModuleConfigurationParameters.ActiveDirectoryDomainServices.ResourceGroup}", "DependsOn": [ - "WindowsJumpbox", - "LinuxJumpbox", - "VirtualNetwork", - "DiagnosticStorageAccount", - "LogAnalytics", - "KeyVault", - "ArtifactsStorageAccount", - "EncryptActiveDirectoryDomainServices", - "ActiveDirectoryDomainServicesVMs" + "EncryptActiveDirectoryDomainServices" ], "Comments": "Installs Active Directory Domain Services", "Deployment": { diff --git a/Environments/SharedServices_OnpremExtension/parameters.json b/Environments/SharedServices_OnpremExtension/parameters.json index 16e3dbd..052937e 100644 --- a/Environments/SharedServices_OnpremExtension/parameters.json +++ b/Environments/SharedServices_OnpremExtension/parameters.json @@ -11,14 +11,14 @@ "PrimaryDomainControllerIP": "192.168.1.4", "DomainName": "contoso.com", "ADSitename": "Cloud-Site", - "DomainAdminUserName": "contoso" + "DomainAdminUserName": "env(DOMAIN_ADMIN_USERNAME)" }, "Network": { "AddressPrefix": "192.168.1.0/28" }, "VirtualNetworkGateway": { - "Name": "jch-onprem-gw", - "ResourceGroup": "jch-onprem-net-rg" + "Name": "cnts-onprem-gw", + "ResourceGroup": "cnts-onprem-net-rg" }, "SubscriptionId": "${Subscriptions.OnPremises.SubscriptionId}" }, @@ -95,6 +95,19 @@ "West US 2" ] }, + "AzureBastion": { + "ResourceGroup": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.ResourceGroup}", + "Name": "${Parameters.InstanceName}-bastion", + "Location": "West US", + "ListOfAllowedRegions": [ + "West US", + "East US", + "West Europe", + "South Central US", + "Australia East", + "Japan East" + ] + }, "ApplicationSecurityGroups": { "ResourceGroup": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.ResourceGroup}", "Jumpbox": { @@ -473,7 +486,7 @@ "EnableVmProtection": false, "Subnets": [ { - "name": "${Parameters.DeploymentName}", + "name": "shrdsvcs", "addressPrefix": "172.0.0.0/24", "networkSecurityGroupName": "${Parameters.ModuleConfigurationParameters.NetworkSecurityGroups.SharedServices.Name}", "routeTableName": "${Parameters.ModuleConfigurationParameters.RouteTables.SharedServices.Name}", @@ -519,6 +532,13 @@ "networkSecurityGroupName": "", "routeTableName": "", "serviceEndpoints": [] + }, + { + "name": "AzureBastionSubnet", + "addressPrefix": "172.0.5.0/24", + "networkSecurityGroupName": "", + "routeTableName": "", + "serviceEndpoints": [] } ], "DnsServers": [ @@ -784,10 +804,6 @@ } }, "ArtifactsStorageAccount": "file(../_Common/artifactsStorageAccount.json)", - "AzureBastion": { - "Name": "sharedsvcs-bastion", - "ResourceGroup": "${Parameters.ModuleConfigurationParameters.VirtualNetwork.ResourceGroup}" - }, "Jumpbox": { "ResourceGroup": "${Parameters.InstanceName}-jumpbox-rg", "AdminUsername": "${Parameters.ModuleConfigurationParameters.KeyVault.SecretsObject.Secrets[0].secretName}", diff --git a/Environments/_Common/subscriptions.json b/Environments/_Common/subscriptions.json index 8ef0cb6..2b19a25 100644 --- a/Environments/_Common/subscriptions.json +++ b/Environments/_Common/subscriptions.json @@ -18,6 +18,12 @@ "SubscriptionId": "00000000000000000000000", "Location": "West US 2" }, + "NTier_IaaS": { + "Comments": "Workload subscription and tenant information", + "TenantId": "00000000000000000000000", + "SubscriptionId": "00000000000000000000000", + "Location": "West US 2" + }, "Artifacts": { "Comments": "Subscription and tenant information where the Artifacts Storage Account will reside", "TenantId": "00000000000000000000000", diff --git a/Modules/ActiveDirectoryDomainServices/2.0/Tests/module.tests.ps1 b/Modules/ActiveDirectoryDomainServices/2.0/Tests/module.tests.ps1 index 81921d1..c53557f 100644 --- a/Modules/ActiveDirectoryDomainServices/2.0/Tests/module.tests.ps1 +++ b/Modules/ActiveDirectoryDomainServices/2.0/Tests/module.tests.ps1 @@ -115,6 +115,11 @@ Describe "Template: $template - Active Directory Domain Services" -Tags Unit { | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` | Sort-Object -Property Name ` | ForEach-Object Name + if ($requiredParametersInTemplateFile.Count -gt $allParametersInParametersFile.Count) { + Write-Host "Mismatch found, parameters from parameter file are more than the expected in the template" + Write-Host "Required parameters are: $(ConvertTo-Json $requiredParametersInTemplateFile)" + Write-Host "Parameters from parameter file are: $(ConvertTo-Json $allParametersInParametersFile)" + } $requiredParametersInTemplateFile.Count | Should Not BeGreaterThan $allParametersInParametersFile.Count; } } diff --git a/Modules/ActiveDirectoryDomainServices/2.0/Tests/parameters.json b/Modules/ActiveDirectoryDomainServices/2.0/Tests/parameters.json index 3160aec..b5a4d87 100644 --- a/Modules/ActiveDirectoryDomainServices/2.0/Tests/parameters.json +++ b/Modules/ActiveDirectoryDomainServices/2.0/Tests/parameters.json @@ -14,15 +14,18 @@ "artifactsStorageAccountKey": { "value": "" }, - "addsAddressStart": { - "value": "11.4.0.46" - }, "domainName": { "value": "contoso.com" }, "primaryDCIP": { "value": "192.168.1.4" }, + "addsAddressStart": { + "value": "11.4.0.46" + }, + "addsInstallDriveLetter": { + "value": "F:" + }, "ADSitename": { "value": "Cloud-Site" }, diff --git a/Modules/AutomationAccounts/2.0/deploy.json b/Modules/AutomationAccounts/2.0/deploy.json index fca5f43..33f80e6 100644 --- a/Modules/AutomationAccounts/2.0/deploy.json +++ b/Modules/AutomationAccounts/2.0/deploy.json @@ -28,7 +28,8 @@ "UK South", "West Central US", "West Europe", - "West US 2" + "West US 2", + "West US" ], "metadata": { "description": "Required. Specifies the region for your Automation Account" diff --git a/Modules/AzureBastion/2.0/Scripts/RegisterProviderFeature.ps1 b/Modules/AzureBastion/2.0/Scripts/RegisterProviderFeature.ps1 new file mode 100644 index 0000000..d961aad --- /dev/null +++ b/Modules/AzureBastion/2.0/Scripts/RegisterProviderFeature.ps1 @@ -0,0 +1,15 @@ +$installed = Get-AzProviderFeature -ProviderNamespace Microsoft.Network | Where-Object -Property "FeatureName" -EQ "AllowBastionHost" + +if ($null -eq $installed) { + Register-AzProviderFeature -FeatureName AllowBastionHost -ProviderNamespace Microsoft.Network + Register-AzResourceProvider -ProviderNamespace Microsoft.Network +} + +While ($null -eq $installed) { + $installed = Get-AzProviderFeature -ProviderNamespace Microsoft.Network | Where-Object -Property "FeatureName" -EQ "AllowBastionHost" + $isInstalled = $null -ne $installed + Write-Host "Is installed: $isInstalled" + Start-Sleep -Seconds 20 +} + +Write-Host "Installation complete" \ No newline at end of file diff --git a/Modules/AzureBastion/2.0/deploy.json b/Modules/AzureBastion/2.0/deploy.json index 89310a6..4d2a76d 100644 --- a/Modules/AzureBastion/2.0/deploy.json +++ b/Modules/AzureBastion/2.0/deploy.json @@ -17,8 +17,16 @@ "location": { "type": "string", "defaultValue": "[resourceGroup().location]", + "allowedValues": [ + "West US", + "East US", + "West Europe", + "South Central US", + "Australia East", + "Japan East" + ], "metadata": { - "description": "Optional. Location for all resources." + "description": "Optional. Location for Azure Bastion, is currently limited to a small subset of regions." } } }, @@ -26,17 +34,20 @@ "resources": [ { "type": "Microsoft.Network/publicIPAddresses", - "apiVersion": "2018-08-01", + "apiVersion": "2019-02-01", "name": "[concat(parameters('azureBastionName'), '-pip')]", "location": "[parameters('location')]", + "sku": { + "name": "Standard" + }, "properties": { - "publicIPAllocationMethod": "Dynamic" + "publicIPAllocationMethod": "Static" } }, { "type": "Microsoft.Network/bastionHosts", "name": "[parameters('azureBastionName')]", - "apiVersion": "2019-04-01", + "apiVersion": "2018-10-01", "location": "[parameters('location')]", "dependsOn": [ "[concat(parameters('azureBastionName'), '-pip')]" @@ -44,14 +55,14 @@ "properties": { "ipConfigurations": [ { + "name": "IpConf", "properties": { "subnet": { "id": "[concat(parameters('vNetId'), '/subnets/AzureBastionSubnet')]" }, "publicIPAddress": { "id": "[resourceId('Microsoft.Network/publicIPAddresses', concat(parameters('azureBastionName'), '-pip'))]" - }, - "privateIPAllocationMethod": "Dynamic" + } } } ] diff --git a/Modules/InternetInformationServices/2.0/Pipeline/key.vault.azuredevops.ci.yaml b/Modules/InternetInformationServices/2.0/Pipeline/key.vault.azuredevops.ci.yaml new file mode 100644 index 0000000..535b9a4 --- /dev/null +++ b/Modules/InternetInformationServices/2.0/Pipeline/key.vault.azuredevops.ci.yaml @@ -0,0 +1,35 @@ +name: $(Build.DefinitionName)-$(SourceBranchName)-$(Date:yyyyMMdd).$(Rev:rr) + +variables: + ModuleName: KeyVault + ModuleVersion: 2.0 + RepoName: azure-devops + ModulePath: modules/$(ModuleName)/$(ModuleVersion) + ArtifactName: contents + +resources: + repositories: + - repository: main + type: git + name: '$(RepoName)' + +trigger: + branches: + include: + - master + - '*' + paths: + include: + - modules/KeyVault/2.0 + +jobs: +- job: BuildModule + displayName: Build Module + workspace: + clean: all + + steps: + - template: /azure-devops/ci/buildmodule.yaml + parameters: + ModulePath: $(ModulePath) + ArtifactName: $(ArtifactName) \ No newline at end of file diff --git a/Modules/InternetInformationServices/2.0/Policy/key.vault.arm.policies.json b/Modules/InternetInformationServices/2.0/Policy/key.vault.arm.policies.json new file mode 100644 index 0000000..9504f62 --- /dev/null +++ b/Modules/InternetInformationServices/2.0/Policy/key.vault.arm.policies.json @@ -0,0 +1,37 @@ +{ + "policies": [ + { + "name": "kvPermittedResourceTypes", + "description": "Policy to prevent the creation of unauthorized resource types within the resource group.", + "rules": { + "if": { + "not": { + "field": "type", + "in": [ + "Microsoft.KeyVault/vaults", + "Microsoft.Storage/storageAccounts" + ] + } + }, + "then": { + "effect": "audit" + } + } + }, + { + "name": "denyPublicIP", + "description": "Policy to prevent the creation of public ip resources", + "rules": { + "if": { + "field": "type", + "in": [ + "Microsoft.Network/publicIPAddresses" + ] + }, + "then": { + "effect": "audit" + } + } + } + ] +} \ No newline at end of file diff --git a/Modules/AzureBastion/2.0/Scripts/git_placeholder.md b/Modules/InternetInformationServices/2.0/RBAC/git_placeholder.md similarity index 100% rename from Modules/AzureBastion/2.0/Scripts/git_placeholder.md rename to Modules/InternetInformationServices/2.0/RBAC/git_placeholder.md diff --git a/Modules/InternetInformationServices/2.0/Scripts/create.keys.ps1 b/Modules/InternetInformationServices/2.0/Scripts/create.keys.ps1 new file mode 100644 index 0000000..0bcd2ac --- /dev/null +++ b/Modules/InternetInformationServices/2.0/Scripts/create.keys.ps1 @@ -0,0 +1,41 @@ +[CmdletBinding()] + param ( + [Parameter(Mandatory=$true)] + [string] + $VaultName, + [Parameter(Mandatory=$true)] + [string] + $KeyName, + [Parameter(Mandatory=$false)] + [string] + $Destination, + [Parameter(Mandatory=$false)] + [bool] + $ReplaceExistingKey = $false + ) +if ($null -eq $Destination) { + $Destination = "HSM"; +} + +try { + + $result = ` + (Get-AzKeyVaultKey ` + -VaultName $VaultName ` + -Name $KeyName ` + -ErrorAction SilentlyContinue).Id; + + if (($null -eq $result) -or ` + $ReplaceExistingKey) { + $result = (Add-AzKeyVaultKey ` + -VaultName $VaultName ` + -Name $KeyName ` + -Destination $Destination).Id; + } + + return $result +} +catch { + throw $_; +} + diff --git a/Modules/InternetInformationServices/2.0/Scripts/key.vault.backup.ps1 b/Modules/InternetInformationServices/2.0/Scripts/key.vault.backup.ps1 new file mode 100644 index 0000000..fe6aea6 --- /dev/null +++ b/Modules/InternetInformationServices/2.0/Scripts/key.vault.backup.ps1 @@ -0,0 +1,217 @@ +<# + .NOTES + ============================================================================================== + Copyright(c) Microsoft Corporation. All rights reserved. + + File: key.vault.backup.ps1 + + Purpose: Key Vault Backup Automation Script + + Version: 1.0.0.0 - 1st April 2019 - Azure Virtual Datacenter Development Team + ============================================================================================== + + .SYNOPSIS + Key Vault Backup Automation Script + + .DESCRIPTION + Key Vault Backup Automation Script + + Deployment steps of the script are outlined below. + 1) Set Azure KeyVault Parameters + 2) Setup Backup Directory + 3) Backup Key Vault Secrets + 4) Copy Files to Azure Files + 5) Create Backup Folder + 6) Upload Files + + .PARAMETER KeyVaultName + Specify the Azure Key Vault Name parameter. + + .PARAMETER KeyVaultResourceGroup + Specify the Key Vault Resource Group Name parameter. + + .PARAMETER StorageAccountName + Specify the Storage Account Name parameter. + + .PARAMETER StorageResourceGroup + Specify the Storage Resource Group Name parameter. + + .PARAMETER fileshareName + Specify the File Share Name parameter. + + .PARAMETER backupFolder + Specify the Backup Folder parameter. + + .PARAMETER tempRestoreFolder + Specify the Temp Restore Folder parameter. + + .EXAMPLE + Default: + C:\PS>.\key.vault.backup.ps1 ` + -KeyVaultName <"KeyVaultName"> ` + -KeyVaultResourceGroup <"KeyVaultResourceGroup> ` + -StorageAccountName <"StorageAccountName"> ` + -StorageResourceGroup <"StorageResourceGroup"> ` + -fileshareName <"fileshareName"> ` + -backupFolder <"backupFolder"> +#> + +#Requires -Version 5 +#Requires -Module AzureRM.KeyVault +#Requires -Module AzureRM.Storage +#Requires -Module Azure.Storage + +[CmdletBinding()] +param +( + [Parameter(Mandatory = $true)] + [string]$keyVaultName, + [Parameter(Mandatory = $true)] + [string]$keyVaultResourceGroup, + [Parameter(Mandatory = $true)] + [string]$StorageAccountName, + [Parameter(Mandatory = $true)] + [string]$StorageResourceGroup, + [Parameter(Mandatory = $true)] + [string]$fileshareName, + [string]$backupFolder = "$env:Temp\KeyVaultBackup" +) + +#region - Setup Backup Directory +if (test-path $backupFolder) +{ + $paramRemoveItem = @{ + Path = $backupFolder + Recurse = $true + Force = $true + } + Remove-Item @paramRemoveItem +} +#endregion + +#region - Backup Key Vault Secrets +$paramNewItem = @{ + ItemType = 'Directory' + Force = $true + Path = $backupFolder +} +New-Item @paramNewItem | Out-Null + +Write-Output "Starting backup of KeyVault to local directory" + +#region - Certificates +$paramGetAzureKeyVaultCertificate = @{ + VaultName = $keyvaultName +} +$certificates = Get-AzureKeyVaultCertificate @paramGetAzureKeyVaultCertificate + +foreach ($cert in $certificates) +{ + $paramBackupAzureKeyVaultCertificate = @{ + Name = $cert.name + VaultName = $keyvaultName + OutputFile = "$backupFolder\certificate-$($cert.name)" + } + Backup-AzureKeyVaultCertificate @paramBackupAzureKeyVaultCertificate | Out-Null +} +#endregion + +#region - Secrets +$paramGetAzureKeyVaultSecret = @{ + VaultName = $keyvaultName +} +$secrets = Get-AzureKeyVaultSecret @paramGetAzureKeyVaultSecret + +foreach ($secret in $secrets) +{ + #Exclude any secerets automatically generated when creating a cert, as these cannot be backed up + if (-not ($certificates.Name -contains $secret.name)) + { + $paramBackupAzureKeyVaultSecret = @{ + Name = $secret.name + VaultName = $keyvaultName + OutputFile = "$backupFolder\secret-$($secret.name)" + } + Backup-AzureKeyVaultSecret @paramBackupAzureKeyVaultSecret | Out-Null + } +} +#endregion + +#region - Keys +$paramGetAzureKeyVaultKey = @{ + VaultName = $keyvaultName +} +$keys = Get-AzureKeyVaultKey @paramGetAzureKeyVaultKey + +foreach ($kvkey in $keys) +{ + #Exclude any keys automatically generated when creating a cert, as these cannot be backed up + if (-not ($certificates.Name -contains $kvkey.name)) + { + $paramBackupAzureKeyVaultKey = @{ + Name = $kvkey.name + VaultName = $keyvaultName + OutputFile = "$backupFolder\key-$($kvkey.name)" + } + Backup-AzureKeyVaultKey @paramBackupAzureKeyVaultKey | Out-Null + } +} +#endregion + +Write-Output "Local file backup complete" +#endregion + +#region - Copy Files to Azure Files +Write-Output "Starting upload of backup to Azure Files" +$paramGetAzureRmStorageAccount = @{ + ResourceGroupName = $storageResourceGroup + Name = $storageAccountName +} +$storageAccount = Get-AzureRmStorageAccount @paramGetAzureRmStorageAccount +$files = Get-ChildItem $backupFolder +$backupFolderName = Split-Path $backupFolder -Leaf +#endregion + +#region - Create Backup Folder +$paramGetAzureStorageFile = @{ + Context = $storageAccount.Context + ShareName = $fileshareName + Path = $backupFolderName +} +$backupFolderTest = Get-AzureStorageFile @paramGetAzureStorageFile + +if (-not $backupFolderTest) +{ + $paramNewAzureStorageDirectory = @{ + Context = $storageAccount.Context + ShareName = $fileshareName + Path = $backupFolderName + } + New-AzureStorageDirectory @paramNewAzureStorageDirectory +} +#endregion + +#region - Upload Files +foreach ($file in $files) +{ + $paramSetAzureStorageFileContent = @{ + Context = $storageAccount.Context + ShareName = $fileshareName + Source = $file.FullName + Path = "$backupFolderName\$($file.name)" + Force = $true + } + Set-AzureStorageFileContent @paramSetAzureStorageFileContent +} + +$paramRemoveItem = @{ + Path = $backupFolder + Recurse = $true + Force = $true +} +Remove-Item @paramRemoveItem + +Write-Output "Upload complete" + +Write-Output "Backup Complete" +#endregion \ No newline at end of file diff --git a/Modules/InternetInformationServices/2.0/Scripts/key.vault.log.analytics.register.ps1 b/Modules/InternetInformationServices/2.0/Scripts/key.vault.log.analytics.register.ps1 new file mode 100644 index 0000000..5678e2b --- /dev/null +++ b/Modules/InternetInformationServices/2.0/Scripts/key.vault.log.analytics.register.ps1 @@ -0,0 +1,80 @@ +<# + .NOTES + ============================================================================================== + Copyright(c) Microsoft Corporation. All rights reserved. + + File: key.vault.log.analytics.register.ps1 + + Purpose: Register Key Vault with Log Analytics Deployment Automation Script + + Version: 1.0.0.0 - 1st April 2019 - Azure Virtual Datacenter Development Team + ============================================================================================== + + .SYNOPSIS + Register Key Vault with Log Analytics Deployment Automation Script + + .DESCRIPTION + Register Key Vault with Log Analytics Deployment Automation Script + + Deployment steps of the script are outlined below. + 1) Register Key Vault with Log Analytics + + .PARAMETER keyVaultName + Specify the key Vault Name parameter. + + .PARAMETER diagstorageAccountName + Specify the diagnostic Storage Account Name parameter. + + .PARAMETER omsWorkspaceName + Specify the Log Analytics Workspace Name parameter. + + .EXAMPLE + Default: + C:\PS>.\key.vault.log.analytics.register.ps1 ` + -keyVaultName <"keyVaultName"> ` + -diagstorageAccountName <"diagstorageAccountName"> ` + -omsWorkspaceName <"omsWorkspaceName"> +#> + +#Requires -Version 5 +#Requires -Module AzureRM.Resources +#Requires -Module AzureRM.Insights + +[CmdletBinding()] +param +( + [Parameter(Mandatory = $true)] + [string]$keyVaultName, + [Parameter(Mandatory = $true)] + [string]$diagstorageAccountName, + [Parameter(Mandatory = $true)] + [string]$omsWorkspaceName +) + +#region - Register Keyvault with Log Analytics +$paramGetAzureRmResource = @{ + ResourceName = $keyVaultName + ResourceType = "Microsoft.DocumentDb/databaseAccounts" +} +$KeyVault = Get-AzureRmResource @paramGetAzureRmResource + +$paramGetAzureRmResource = @{ + ResourceName = $diagstorageAccountName + ResourceType = "Microsoft.Storage/storageAccounts" +} +$StorageAccount = Get-AzureRmResource @paramGetAzureRmResource + +$paramGetAzureRmResource = @{ + ResourceName = $omsWorkspaceName + ResourceType = "Microsoft.OperationalInsights/workspaces" +} +$WorkspaceName = Get-AzureRmResource @paramGetAzureRmResource + +$paramSetAzureRmDiagnosticSetting = @{ + ResourceId = $KeyVault.ResourceId + StorageAccountId = $StorageAccount.Id + WorkspaceId = $WorkspaceName.Id + Enabled = $true +} +Set-AzureRmDiagnosticSetting @paramSetAzureRmDiagnosticSetting +#endregion \ No newline at end of file diff --git a/Modules/InternetInformationServices/2.0/Scripts/key.vault.restore.ps1 b/Modules/InternetInformationServices/2.0/Scripts/key.vault.restore.ps1 new file mode 100644 index 0000000..f123caa --- /dev/null +++ b/Modules/InternetInformationServices/2.0/Scripts/key.vault.restore.ps1 @@ -0,0 +1,187 @@ +<# + .NOTES + ============================================================================================== + Copyright(c) Microsoft Corporation. All rights reserved. + + File: key.vault.restore.ps1 + + Purpose: Key Vault Restore Automation Script + + Version: 1.0.0.0 - 1st April 2019 - Azure Virtual Datacenter Development Team + ============================================================================================== + + .SYNOPSIS + Key Vault Restore Automation Script + + .DESCRIPTION + Key Vault Restore Automation Script + + Deployment steps of the script are outlined below. + 1) Set Azure Key Vault Parameters + 2) Create temporary folder to download files + 3) Download files from Azure File Share + 4) Restore Key Vault Serects + + .PARAMETER KeyVaultName + Specify the Azure Key Vault Name parameter. + + .PARAMETER KeyVaultResourceGroup + Specify the Key Vault Resource Group Name parameter. + + .PARAMETER StorageAccountName + Specify the Storage Account Name parameter. + + .PARAMETER StorageResourceGroup + Specify the Storage Resource Group Name parameter. + + .PARAMETER fileshareName + Specify the File Share Name parameter. + + .PARAMETER backupFolder + Specify the Backup Folder parameter. + + .PARAMETER tempRestoreFolder + Specify the Temp Restore Folder parameter. + + .EXAMPLE + Default: + C:\PS>.\key.vault.restore.ps1 ` + -KeyVaultName <"KeyVaultName"> ` + -KeyVaultResourceGroup <"KeyVaultResourceGroup> ` + -StorageAccountName <"StorageAccountName"> ` + -StorageResourceGroup <"StorageResourceGroup"> ` + -fileshareName <"fileshareName"> ` + -backupFolder <"backupFolder"> ` + -tempRestoreFolder <"tempRestoreFolder"> +#> + +#Requires -Version 5 +#Requires -Module AzureRM.KeyVault +#Requires -Module AzureRM.Storage +#Requires -Module Azure.Storage + +[CmdletBinding()] +param +( + [Parameter(Mandatory = $true)] + [string]$KeyVaultName, + [Parameter(Mandatory = $true)] + [string]$KeyVaultResourceGroup, + [Parameter(Mandatory = $true)] + [string]$StorageAccountName, + [Parameter(Mandatory = $true)] + [string]$StorageResourceGroup, + [Parameter(Mandatory = $true)] + [string]$fileshareName, + [Parameter(Mandatory = $true)] + [string]$backupFolder, + [string]$tempRestoreFolder = "$env:Temp\KeyVaultRestore" +) + +#region - Create temporary folder to download files +if (test-path $tempRestoreFolder) +{ + $paramRemoveItem = @{ + Path = $tempRestoreFolder + Recurse = $true + Force = $true + } + Remove-Item @paramRemoveItem +} + +$paramNewItem = @{ + ItemType = 'Directory' + Force = $true + Path = $tempRestoreFolder +} +New-Item @paramNewItem | Out-Null + +Write-Output "Starting download of backup to Azure Files" + +$paramGetAzureRmStorageAccount = @{ + ResourceGroupName = $storageResourceGroup + Name = $storageAccountName +} +$storageAccount = Get-AzureRmStorageAccount @paramGetAzureRmStorageAccount +#endregion + +#region - Download files from Azure File Share +$paramGetAzureStorageFile = @{ + Context = $storageAccount.Context + ShareName = $fileshareName + Path = $backupFolderName +} +$backupFolderTest = Get-AzureStorageFile @paramGetAzureStorageFile + +if (-not $backupFolderTest) +{ + Write-Error "Backup folder in Azure File Share Not Found" + exit +} + +$paramGetAzureStorageFile = @{ + ShareName = $fileshareName + Path = $backupFolder + Context = $storageAccount.Context +} +$backupFiles = Get-AzureStorageFile @paramGetAzureStorageFile | Get-AzureStoragefile + +foreach ($backupFile in $backupFiles) +{ + Write-Output "downloading $backupFolder\$($backupFile.name)" + $paramGetAzureStorageFileContent = @{ + ShareName = $fileshareName + Path = "$backupFolder\$($backupFile.name)" + Destination = "$tempRestoreFolder\$($backupFile.name)" + Context = $storageAccount.Context + } + Get-AzureStorageFileContent @paramGetAzureStorageFileContent +} +#endregion + +#region - Restore secrets to Key Vault +Write-Output "Starting Restore" + +$secrets = get-childitem $tempRestoreFolder | where-object { $_ -match "^(secret-)" } +$certificates = get-childitem $tempRestoreFolder | where-object { $_ -match "^(certificate-)" } +$keys = get-childitem $tempRestoreFolder | where-object { $_ -match "^(key-)" } + +foreach ($secret in $secrets) +{ + write-output "restoring $($secret.FullName)" + $paramRestoreAzureKeyVaultSecret = @{ + VaultName = $keyvaultName + InputFile = $secret.FullName + } + Restore-AzureKeyVaultSecret @paramRestoreAzureKeyVaultSecret +} + +foreach ($certificate in $certificates) +{ + write-output "restoring $($certificate.FullName) " + $paramRestoreAzureKeyVaultCertificate = @{ + VaultName = $keyvaultName + InputFile = $certificate.FullName + } + Restore-AzureKeyVaultCertificate @paramRestoreAzureKeyVaultCertificate +} + +foreach ($key in $keys) +{ + write-output "restoring $($key.FullName) " + $paramRestoreAzureKeyVaultKey = @{ + VaultName = $keyvaultName + InputFile = $key.FullName + } + Restore-AzureKeyVaultKey @paramRestoreAzureKeyVaultKey +} + +$paramRemoveItem = @{ + Path = $tempRestoreFolder + Recurse = $true + Force = $true +} +Remove-Item @paramRemoveItem + +Write-Output "Restore Complete" +#endregion diff --git a/Modules/InternetInformationServices/2.0/Scripts/key.vault.secrect.rules.ps1 b/Modules/InternetInformationServices/2.0/Scripts/key.vault.secrect.rules.ps1 new file mode 100644 index 0000000..b876456 --- /dev/null +++ b/Modules/InternetInformationServices/2.0/Scripts/key.vault.secrect.rules.ps1 @@ -0,0 +1,78 @@ +<# + .NOTES + ============================================================================================== + Copyright(c) Microsoft Corporation. All rights reserved. + + File: key.vault.secrect.rules.ps1 + + Purpose: Set Key Vault Secrets Automation Script + + Version: 1.0.0.0 - 1st April 2019 - Azure Virtual Datacenter Development Team + ============================================================================================== + + .SYNOPSIS + Set Key Vault Secrets Automation Script + + .DESCRIPTION + Set Key Vault Secrets Automation Script + + Deployment steps of the script are outlined below. + 1) Set Azure KeyVault Parameters + 2) Create Azure KeyVault Secret + + .PARAMETER KeyVaultName + Specify the Azure Key Vault Name parameter. + + .EXAMPLE + Default: + C:\PS>.\key.vault.secrect.rules.ps1 ` + -KeyVaultName <"KeyVaultName"> +#> + +#Requires -Version 5 +#Requires -Module AzureRM.KeyVault + +[CmdletBinding()] +param +( + [Parameter(Mandatory = $false)] + [string]$KeyVaultName +) + +#region - Azure KeyVault Parameters +$kVSecretParameters = @{} + +if($KeyVaultName -ne $null) +{ + $kVSecretParameters.Add("Secret--KeyVault--Vault", $($KeyVaultName)) +} +else +{ + write-output "Key--KeyVault--Vault: NULL" +} +#endregion + +#region - Set Azure KeyVault Secret +$kVSecretParameters.Keys | ForEach-Object { + $key = $_ + $value = $kVSecretParameters.Item($_) + + $Parameters = @{ + VaultName = $KeyVaultName + } + if (Get-AzureKeyVaultSecret @Parameters | Where-Object { $psitem.Name -eq "$key" }) + { + Write-Output "The secret for $key already exists" + } + else + { + Write-Output "Setting Secret for $key" + $Parameters = @{ + VaultName = $KeyVaultName + Name = $key + SecretValue = (ConvertTo-SecureString $value -AsPlainText -Force) + } + Set-AzureKeyVaultSecret @Parameters -Verbose + } +} +#endregion \ No newline at end of file diff --git a/Modules/InternetInformationServices/2.0/Tests/module.tests.ps1 b/Modules/InternetInformationServices/2.0/Tests/module.tests.ps1 new file mode 100644 index 0000000..6295ec9 --- /dev/null +++ b/Modules/InternetInformationServices/2.0/Tests/module.tests.ps1 @@ -0,0 +1,159 @@ +<# + .NOTES + ============================================================================================== + Copyright(c) Microsoft Corporation. All rights reserved. + + File: module.tests.ps1 + + Purpose: Pester - Test Key Vault ARM Templates + + Version: 1.0.0.0 - 1st April 2019 - Azure Virtual Datacenter Development Team + ============================================================================================== + + .SYNOPSIS + This script contains functionality used to test Key Vault ARM template synatax. + + .DESCRIPTION + This script contains functionality used to test Key Vault ARM template synatax. + + Deployment steps of the script are outlined below. + 1) Test Template File Syntax + 2) Test Parameter File Syntax + 3) Test Template and Parameter File Compactibility +#> + +#Requires -Version 5 + +#region Parameters + +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +$here = Join-Path $here ".." +$template = Split-Path -Leaf $here +$TemplateFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "deploy.json") -Recurse | Select-Object -ExpandProperty Name) ) { + $TemplateFileTestCases += @{ TemplateFile = $File } +} +$ParameterFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "Tests" -AdditionalChildPath @("*parameters.json")) -Recurse -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name) ) { + $ParameterFileTestCases += @{ ParameterFile = Join-Path "Tests" $File } +} +$Modules = @(); +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "deploy.json") ) ) { + $Module = [PSCustomObject]@{ + 'Template' = $null + 'Parameters' = $null + } + $Module.Template = $File.FullName; + $Parameters = @(); + ForEach ( $ParameterFile in (Get-ChildItem (Join-Path "$here" "Tests" -AdditionalChildPath @("*parameters.json")) -Recurse -ErrorAction SilentlyContinue| Select-Object -ExpandProperty Name) ) { + $Parameters += (Join-Path "$here" "Tests" -AdditionalChildPath @("$ParameterFile") ) + } + $Module.Parameters = $Parameters; + $Modules += @{ Module = $Module }; +} + +#endregion + +#region Run Pester Test Script +Describe "Template: $template - Key Vault" -Tags Unit { + + Context "Template File Syntax" { + + It "Has a JSON template file" { + (Join-Path "$here" "deploy.json") | Should Exist + } + + It "Converts from JSON and has the expected properties" -TestCases $TemplateFileTestCases { + Param( $TemplateFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters', + 'variables', + 'resources', + 'outputs' | Sort-Object + $templateProperties = (Get-Content (Join-Path "$here" "$TemplateFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateProperties | Should Be $expectedProperties + } + } + + Context "Parameter File Syntax" { + + It "Parameter file does not contains the expected properties" -TestCases $ParameterFileTestCases { + Param( $ParameterFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters' | Sort-Object + Write-Host $ParameterFile + Join-Path "$here" "$ParameterFile" | Write-Host + $templateFileProperties = (Get-Content (Join-Path "$here" "$ParameterFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateFileProperties | Should Be $expectedProperties + } + + } + + Context "Template and Parameter Compactibility" { + + It "Is count of required parameters in template file equal or lesser than count of all parameters in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $requiredParametersInTemplateFile.Count | Should Not BeGreaterThan $allParametersInParametersFile.Count; + } + } + + It "Has all parameters in parameters file existing in template file" -TestCases $Modules { + Param( $Module ) + + $allParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + Write-Host "File analyzed: $Parameter"; + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $result = @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}); + Write-Host "Invalid parameters: $(ConvertTo-Json $result)"; + @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}).Count | Should Be 0; + } + } + + It "Has required parameters in template file existing in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + @($requiredParametersInTemplateFile| Where-Object {$allParametersInParametersFile -notcontains $_}).Count | Should Be 0; + } + } + } + +} +#endregion \ No newline at end of file diff --git a/Modules/InternetInformationServices/2.0/Tests/output.test.ps1 b/Modules/InternetInformationServices/2.0/Tests/output.test.ps1 new file mode 100644 index 0000000..312b83b --- /dev/null +++ b/Modules/InternetInformationServices/2.0/Tests/output.test.ps1 @@ -0,0 +1,47 @@ +<# + .NOTES + ============================================================================================== + Copyright(c) Microsoft Corporation. All rights reserved. + + File: output.test.ps1 + + Purpose: Test - Key Vault ARM Template Output Variables + + Version: 1.0.0.0 - 1st April 2019 - Azure Virtual Datacenter Development Team + ============================================================================================== + + .SYNOPSIS + This script contains functionality used to test Key Vault ARM template output variables. + + .DESCRIPTION + This script contains functionality used to test Key Vault ARM template output variables. + + Deployment steps of the script are outlined below. + 1) Outputs Variable Logic to pipeline + + .PARAMETER KeyVaultName + Specify the Key Vault Name output parameter. + + .EXAMPLE + Default: + C:\PS>.\output.test.ps1 ` + -KeyVaultName <"KeyVaultName"> +#> + +#Requires -Version 5 + +[CmdletBinding()] +param +( + [Parameter(Mandatory = $false)] + [string]$KeyVaultName +) + +if($KeyVaultName -ne $null) +{ + write-output "Azure Key Vault Name: $($KeyVaultName)" +} +else +{ + write-output "Azure Key Vault Name: NULL" +} \ No newline at end of file diff --git a/Modules/InternetInformationServices/2.0/Tests/policy.tests.ps1 b/Modules/InternetInformationServices/2.0/Tests/policy.tests.ps1 new file mode 100644 index 0000000..fb7af63 --- /dev/null +++ b/Modules/InternetInformationServices/2.0/Tests/policy.tests.ps1 @@ -0,0 +1,163 @@ +<# + .NOTES + ============================================================================================== + Copyright(c) Microsoft Corporation. All rights reserved. + + File: module.tests.ps1 + + Purpose: Pester - Test Key Vault ARM Templates + + Version: 1.0.0.0 - 1st April 2019 - Azure Virtual Datacenter Development Team + ============================================================================================== + + .SYNOPSIS + This script contains functionality used to test Azure Key Vault ARM template synatax. + + .DESCRIPTION + This script contains functionality used to test Azure Key Vault ARM template synatax. + + Deployment steps of the script are outlined below. + 1) Test Template File Syntax + 2) Test Parameter File Syntax + 3) Test Template and Parameter File Compactibility +#> + +#Requires -Version 5 + +#region Parameters + +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +$here = Join-Path $here ".." +$template = Split-Path -Leaf $here +$TemplateFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "Policy" -AdditionalChildPath @("deploy.json")) -Recurse -ErrorAction SilentlyContinue| Select-Object -ExpandProperty Name) ) { + $TemplateFileTestCases += @{ TemplateFile = $File } +} +$ParameterFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "Policy" -AdditionalChildPath @("*parameters.json")) -Recurse -ErrorAction SilentlyContinue| Select-Object -ExpandProperty Name) ) { + $ParameterFileTestCases += @{ ParameterFile = Join-Path "Policy" $File } +} +$Modules = @(); +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "Policy" -AdditionalChildPath @("deploy.json")) -ErrorAction SilentlyContinue ) ) { + $Module = [PSCustomObject]@{ + 'Template' = $null + 'Parameters' = $null + } + $Module.Template = $File.FullName; + $Parameters = @(); + ForEach ( $ParameterFile in (Get-ChildItem (Join-Path "$here" "Policy" -AdditionalChildPath @("*parameters.json")) -Recurse -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name) ) { + $Parameters += (Join-Path "$here" "Policy" -AdditionalChildPath @("$ParameterFile") ) + } + $Module.Parameters = $Parameters; + $Modules += @{ Module = $Module }; +} + +#endregion + +if ($null -ne $TemplateFileTestCases -and + $TemplateFileTestCases.Count -gt 0) { + + #region Run Pester Test Script + Describe "Template: $template - Key Vault" -Tags Unit { + + Context "Template File Syntax" { + + It "Has a JSON template file" { + (Join-Path "$here" "deploy.json") | Should Exist + } + + It "Converts from JSON and has the expected properties" -TestCases $TemplateFileTestCases { + Param( $TemplateFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters', + 'variables', + 'resources', + 'outputs' | Sort-Object + $templateProperties = (Get-Content (Join-Path "$here" "$TemplateFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateProperties | Should Be $expectedProperties + } + } + + Context "Parameter File Syntax" { + + It "Parameter file does not contains the expected properties" -TestCases $ParameterFileTestCases { + Param( $ParameterFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters' | Sort-Object + Write-Host $ParameterFile + Join-Path "$here" "$ParameterFile" | Write-Host + $templateFileProperties = (Get-Content (Join-Path "$here" "$ParameterFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateFileProperties | Should Be $expectedProperties + } + + } + + Context "Template and Parameter Compactibility" { + + It "Is count of required parameters in template file equal or lesser than count of all parameters in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $requiredParametersInTemplateFile.Count | Should Not BeGreaterThan $allParametersInParametersFile.Count; + } + } + + It "Has all parameters in parameters file existing in template file" -TestCases $Modules { + Param( $Module ) + + $allParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + Write-Host "File analyzed: $Parameter"; + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $result = @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}); + Write-Host "Invalid parameters: $(ConvertTo-Json $result)"; + @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}).Count | Should Be 0; + } + } + + It "Has required parameters in template file existing in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + @($requiredParametersInTemplateFile| Where-Object {$allParametersInParametersFile -notcontains $_}).Count | Should Be 0; + } + } + } + + } + #endregion +} \ No newline at end of file diff --git a/Modules/InternetInformationServices/2.0/Tests/rbac.tests.ps1 b/Modules/InternetInformationServices/2.0/Tests/rbac.tests.ps1 new file mode 100644 index 0000000..d5efb16 --- /dev/null +++ b/Modules/InternetInformationServices/2.0/Tests/rbac.tests.ps1 @@ -0,0 +1,163 @@ +<# + .NOTES + ============================================================================================== + Copyright(c) Microsoft Corporation. All rights reserved. + + File: module.tests.ps1 + + Purpose: Pester - Test Key Vault ARM Templates + + Version: 1.0.0.0 - 1st April 2019 - Azure Virtual Datacenter Development Team + ============================================================================================== + + .SYNOPSIS + This script contains functionality used to test Azure Key Vault ARM template synatax. + + .DESCRIPTION + This script contains functionality used to test Azure Key Vault ARM template synatax. + + Deployment steps of the script are outlined below. + 1) Test Template File Syntax + 2) Test Parameter File Syntax + 3) Test Template and Parameter File Compactibility +#> + +#Requires -Version 5 + +#region Parameters + +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +$here = Join-Path $here ".." +$template = Split-Path -Leaf $here +$TemplateFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "RBAC" -AdditionalChildPath @("deploy.json")) -ErrorAction SilentlyContinue -Recurse | Select-Object -ExpandProperty Name) ) { + $TemplateFileTestCases += @{ TemplateFile = $File } +} +$ParameterFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "RBAC" -AdditionalChildPath @("*parameters.json")) -ErrorAction SilentlyContinue -Recurse | Select-Object -ExpandProperty Name) ) { + $ParameterFileTestCases += @{ ParameterFile = Join-Path "RBAC" $File } +} +$Modules = @(); +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "RBAC" -AdditionalChildPath @("deploy.json")) -ErrorAction SilentlyContinue ) ) { + $Module = [PSCustomObject]@{ + 'Template' = $null + 'Parameters' = $null + } + $Module.Template = $File.FullName; + $Parameters = @(); + ForEach ( $ParameterFile in (Get-ChildItem (Join-Path "$here" "RBAC" -AdditionalChildPath @("*parameters.json")) -Recurse | Select-Object -ExpandProperty Name) ) { + $Parameters += (Join-Path "$here" "RBAC" -AdditionalChildPath @("$ParameterFile") ) + } + $Module.Parameters = $Parameters; + $Modules += @{ Module = $Module }; +} + +#endregion + +if ($null -ne $TemplateFileTestCases -and + $TemplateFileTestCases.Count -gt 0) { + + #region Run Pester Test Script + Describe "Template: $template - Key Vault" -Tags Unit { + + Context "Template File Syntax" { + + It "Has a JSON template file" -TestCases $TemplateFileTestCases { + (Join-Path "$here" "deploy.json") | Should Exist + } + + It "Converts from JSON and has the expected properties" -TestCases $TemplateFileTestCases { + Param( $TemplateFile ) + Write-Host "TF: $TemplateFile" + $expectedProperties = '$schema', + 'contentVersion', + 'parameters', + 'variables', + 'resources', + 'outputs' | Sort-Object + $templateProperties = (Get-Content (Join-Path "$here" "$TemplateFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateProperties | Should Be $expectedProperties + } + } + + Context "Parameter File Syntax" { + + It "Parameter file does not contains the expected properties" -TestCases $ParameterFileTestCases { + Param( $ParameterFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters' | Sort-Object + Write-Host $ParameterFile + Join-Path "$here" "$ParameterFile" | Write-Host + $templateFileProperties = (Get-Content (Join-Path "$here" "$ParameterFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateFileProperties | Should Be $expectedProperties + } + } + + Context "Template and Parameter Compactibility" { + + It "Is count of required parameters in template file equal or lesser than count of all parameters in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $requiredParametersInTemplateFile.Count | Should Not BeGreaterThan $allParametersInParametersFile.Count; + } + } + + It "Has all parameters in parameters file existing in template file" -TestCases $Modules { + Param( $Module ) + + $allParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + Write-Host "File analyzed: $Parameter"; + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $result = @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}); + Write-Host "Invalid parameters: $(ConvertTo-Json $result)"; + @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}).Count | Should Be 0; + } + } + + It "Has required parameters in template file existing in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + @($requiredParametersInTemplateFile| Where-Object {$allParametersInParametersFile -notcontains $_}).Count | Should Be 0; + } + } + } + + } + #endregion +} \ No newline at end of file diff --git a/Modules/InternetInformationServices/2.0/Tests/virtual.machine.parameters.json b/Modules/InternetInformationServices/2.0/Tests/virtual.machine.parameters.json new file mode 100644 index 0000000..e424a5c --- /dev/null +++ b/Modules/InternetInformationServices/2.0/Tests/virtual.machine.parameters.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "virtualMachineName": { + "value": "foo" + }, + "artifactsStorageAccountName": { + "value": "contoso" + }, + "artifactsStorageAccountSasKey": { + "value": "somekey" + } + } +} \ No newline at end of file diff --git a/Modules/InternetInformationServices/2.0/Tests/virtual.machine.scale.set.parameters.json b/Modules/InternetInformationServices/2.0/Tests/virtual.machine.scale.set.parameters.json new file mode 100644 index 0000000..f015830 --- /dev/null +++ b/Modules/InternetInformationServices/2.0/Tests/virtual.machine.scale.set.parameters.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "virtualMachineScaleSetsName": { + "value": "foo" + }, + "artifactsStorageAccountName": { + "value": "contoso" + }, + "artifactsStorageAccountSasKey": { + "value": "somekey" + } + } +} \ No newline at end of file diff --git a/Modules/InternetInformationServices/2.0/deploy.json b/Modules/InternetInformationServices/2.0/deploy.json new file mode 100644 index 0000000..ab328c4 --- /dev/null +++ b/Modules/InternetInformationServices/2.0/deploy.json @@ -0,0 +1,118 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "virtualMachineName": { + "type": "string", + "defaultValue": "", + "maxLength": 13, + "metadata": { + "description": "Optional. Name for the VMs. Required if virtualMachineScaleSetsName is empty" + } + }, + "virtualMachineCount": { + "type": "int", + "defaultValue": 1, + "metadata": { + "description": "Optional. Number of VMs to create" + } + }, + "virtualMachineOffset": { + "type": "int", + "defaultValue": 1, + "metadata": { + "description": "Optional. This value will be used as start VM count. Specify a value if you want to create VMs starting at a specific number, this is useful when you want to append more VMs." + } + }, + "virtualMachineScaleSetsName": { + "type": "string", + "defaultValue": "", + "maxLength": 13, + "metadata": { + "description": "Optional. Name for VMSS. Required if virtualMachineName is empty" + } + }, + "artifactsStorageAccountName": { + "type": "securestring", + "metadata": { + "description": "Required. Default storage account name. Storage account that contains output parameters and common scripts" + } + }, + "artifactsStorageAccountSasKey": { + "type": "securestring", + "metadata": { + "description": "Required. Shared Access Signature Key used to download custom scripts" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + } + }, + "variables": { + "isVMSS": "[not(empty(parameters('virtualMachineScaleSetsName')))]", + "DSCExtensionName": "DSCExtension", + "artifactsStorageAccountSasToken": "[concat('?', parameters('artifactsStorageAccountSasKey'))]" + }, + "resources": [ + { + "apiVersion": "2019-03-01", + "type": "Microsoft.Compute/virtualMachines/extensions", + "name": "[concat(parameters('virtualMachineName'), copyindex(parameters('virtualMachineOffset')), '/', variables('DSCExtensionName'))]", + "condition": "[not(variables('isVMSS'))]", + "location": "[parameters('location')]", + "copy": { + "name": "vmInstallIIS", + "count": "[parameters('virtualMachineCount')]" + }, + "properties": { + "publisher": "Microsoft.Powershell", + "type": "DSC", + "typeHandlerVersion": "2.9", + "autoUpgradeMinorVersion": true, + "settings": { + "configuration": { + "url": "[concat('https://', parameters('artifactsStorageAccountName'), '.blob.core.windows.net/scripts/Windows/iisaspnet.zip')]", + "script": "iisaspnet.ps1", + "function": "IISASPNET" + } + }, + "protectedSettings": { + "configurationUrlSasToken": "[variables('artifactsStorageAccountSasToken')]" + } + } + }, + { + "type": "Microsoft.Compute/virtualMachineScaleSets/extensions", + "apiVersion": "2019-03-01", + "name": "[concat(parameters('virtualMachineScaleSetsName'), '/', variables('DSCExtensionName'))]", + "condition": "[variables('isVMSS')]", + "location": "[parameters('location')]", + "properties": { + "publisher": "Microsoft.Powershell", + "type": "DSC", + "typeHandlerVersion": "2.9", + "autoUpgradeMinorVersion": true, + "settings": { + "configuration": { + "url": "[concat('https://', parameters('artifactsStorageAccountName'), '.blob.core.windows.net/scripts/Windows/iisaspnet.zip')]", + "script": "iisaspnet.ps1", + "function": "IISASPNET" + } + }, + "protectedSettings": { + "configurationUrlSasToken": "[variables('artifactsStorageAccountSasToken')]" + } + } + } + ], + "outputs": { + "IISResourceGroup": { + "type": "string", + "value": "[resourceGroup().name]" + } + } +} \ No newline at end of file diff --git a/Modules/InternetInformationServices/2.0/readme.md b/Modules/InternetInformationServices/2.0/readme.md new file mode 100644 index 0000000..017366f --- /dev/null +++ b/Modules/InternetInformationServices/2.0/readme.md @@ -0,0 +1,44 @@ +# KeyVault + +This module deploys Key Vault. + +## Resources + +- Microsoft.KeyVault/vaults +- Microsoft.KeyVault/vaults/providers/diagnosticsettings +- Microsoft.KeyVault/vaults/secrets + +## Parameters + +| Parameter Name | Default Value | Description | +| :- | :- | :- | +| `keyVaultName` | | Required. Name of the Azure Key Vault +| `accessPolicies` | `{}` | Optional. Access policies object +| `secretsObject` | `{}` | Optional. All secrets {\"secretName\":\"\",\"secretValue\":\"\"} wrapped in a secure object +| `enableVaultForDeployment` | `true` | Optional. Specifies if the vault is enabled for deployment by script or compute +| `enableVaultForTemplateDeployment` | `true` | Optional. Specifies if the vault is enabled for a template deployment +| `enableVaultForDiskEncryption` | `true` | Optional. Specifies if the azure platform has access to the vault for enabling disk encryption scenarios. +| `logsRetentionInDays` | `365` | Optional. Specifies the number of days that logs will be kept for; a value of 0 will retain data indefinitely. +| `vaultSku` | `Premium` | Optional. Specifies the SKU for the vault +| `diagnosticStorageAccountId` | | Required. Resource identifier of the Diagnostic Storage Account. +| `workspaceId` | | Required. Resource identifier of Log Analytics. +| `networkAcls` | | Required. Service endpoint object information +| `vNetId` | | Required. Virtual Network resource identifier + +## Outputs + +| Output Name | Description | +| :- | :- | +| `keyVaultResourceId` | The Resource Id of the Key Vault. +| `keyVaultResourceGroup` | The name of the Resource Group the Key Vault was created in. +| `keyVaultName` | The Name of the Key Vault. +| `keyVaultUrl` | The Name of the Key Vault. + +## Considerations + +*N/A* + +## Additional resources + +- [What is Azure Key Vault?](https://docs.microsoft.com/en-us/azure/key-vault/key-vault-whatis) +- [Microsoft.KeyVault vaults template reference](https://docs.microsoft.com/en-us/azure/templates/microsoft.keyvault/2018-02-14/vaults) diff --git a/Modules/Jumpbox/2.0/deploy.json b/Modules/Jumpbox/2.0/deploy.json deleted file mode 100644 index 5207a25..0000000 --- a/Modules/Jumpbox/2.0/deploy.json +++ /dev/null @@ -1,1825 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "windowsVirtualMachineName": { - "type": "string", - "minLength": 1, - "maxLength": 13, - "metadata": { - "description": "Required. Name for the Jumpbox VM" - } - }, - "linuxVirtualMachineName": { - "type": "string", - "minLength": 1, - "maxLength": 63, - "metadata": { - "description": "Required. Name for the Jumpbox VM" - } - }, - "workspaceId": { - "type": "securestring", - "metadata": { - "description": "Required. CustomerId value of Log Analytics. This value is referenced in MMA Extension" - } - }, - "logAnalyticsWorkspacePrimarySharedKey": { - "type": "securestring", - "metadata": { - "description": "Required. WorkspaceKey value of OMS. This value is referenced in OMS VM Extension" - } - }, - "artifactsStorageAccountKey": { - "type": "securestring", - "metadata": { - "description": "Required. Artifacts storage account Key. Storage account that contains output parameters and common scripts" - } - }, - "artifactsStorageAccountName": { - "type": "securestring", - "metadata": { - "description": "Required. Artifacts storage account Name." - } - }, - "keyVaultURL": { - "type": "securestring", - "defaultValue": "", - "metadata": { - "description": "Optional. AKV URI" - } - }, - "keyVaultId": { - "type": "securestring", - "defaultValue": "", - "metadata": { - "description": "Optional. AKV Resource Id" - } - }, - "jumpboxKeyEncryptionURL": { - "type": "securestring", - "defaultValue": "", - "metadata": { - "description": "Optional. Jumpbox AKV encryption key" - } - }, - "vNetId": { - "type": "string", - "metadata": { - "description": "Required. Shared services Virtual Network resource Id" - } - }, - "jumpboxAsgId": { - "type": "string", - "metadata": { - "description": "Required. Jumpbox ASG resource identifier" - } - }, - "subnetName": { - "type": "string", - "metadata": { - "description": "Required. Name of Shared Services Subnet, this name is used to get the SubnetId" - } - }, - "adminUserName": { - "type": "string", - "metadata": { - "description": "Required. The username used to establish jumpbox VMs." - } - }, - "adminPassword": { - "type": "securestring", - "metadata": { - "description": "Required. The password given to the admin user." - } - }, - "windowsVirtualMachineCount": { - "type": "int", - "defaultValue": 1, - "metadata": { - "description": "Optional. Number of jumpbox VMs to be created." - } - }, - "windowsVirtualMachineSize": { - "type": "string", - "metadata": { - "description": "Required. Size of the jumpbox VMs." - } - }, - "windowsOSImage": { - "type": "object", - "metadata": { - "description": "Required. OS image used for the jumpbox VMs." - } - }, - "linuxVirtualMachineCount": { - "type": "int", - "defaultValue": 1, - "metadata": { - "description": "Optional. Number of linux jumpbox VMs to be created." - } - }, - "linuxVirtualMachineSize": { - "type": "string", - "metadata": { - "description": "Required. Size of the jumpbox VMs." - } - }, - "linuxOSImage": { - "type": "object", - "metadata": { - "description": "Required. OS image used for the jumpbox VMs." - } - }, - "diagnosticsStorageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Diagnostic Storage Account name" - } - }, - "diagnosticsStorageAccountSasToken": { - "type": "securestring", - "metadata": { - "description": "Required. Diagnostic Storage Account SAS token" - } - } - }, - "variables": { - "uniqueString": "[uniqueString(subscription().id, resourceGroup().id, 'jumpbox')]", - "subnetId": "[concat(parameters('vNetId'), '/subnets/', parameters('subnetName'))]", - "antimalwareExtensionName": "IaaSAntimalware", - "diagnosticsExtensionName": "IaaSDiagnostics", - "networkWatcherExtensionName": "NetworkWatcher", - "encryptionExtensionName": "AzureDiskEncryption", - "encryptionOperation": "EnableEncryption", - "mmaExtensionName": "OMSExtension", - "keyEncryptionAlgorithm": "RSA-OAEP", - "tagPatching": "3rdSat7pm", - "linuxAvailabilitySetName": "[concat(parameters('linuxVirtualMachineName'), '-as')]", - "linuxDiagnosticsExtensionName": "LinuxDiagnostic", - "windowsAvailabilitySetName": "[concat(parameters('windowsVirtualMachineName'), '-as')]", - "windowsPasswordPoliciesExtensionName": "PwdPolicies", - "windowsDependencyExtensionName": "DependencyAgent", - "windowsDependencyExtensionPublisher": "Microsoft.Azure.Monitoring.DependencyAgent", - "windowsDependencyExtensionType": "DependencyAgentWindows", - "windowsDependencyExtensionHandlerVersion": "9.6", - "maxPasswordAge": 70, - "minPasswordAge": 1, - "minPasswordLength": 14, - "pwdHistoryCount": 24, - "pwdMinLengthConfigName": "MinimumPasswordLength", - "pwdMinAgeConfigName": "MinimumPasswordAge", - "pwdNotLast24ConfigName": "EnforcePasswordHistory", - "pwdWithoutReversibleEncryptionConfigName": "StorePasswordsUsingReversibleEncryption", - "pwdMaxAgeConfigName": "MaximumPasswordAge", - "pwdComplexityConfigName": "PasswordMustMeetComplexityRequirements", - "webServerWithTLS1.1configname": "AuditSecureProtocol", - "enableDiskEncryption": "[not(or(or(empty(parameters('keyVaultURL')), empty(parameters('keyVaultId'))), empty(parameters('jumpboxKeyEncryptionURL'))))]", - "windowsVirtualMachineNameOutput": { - "copy": [ - { - "name": "winVMNames", - "count": "[parameters('windowsVirtualMachineCount')]", - "input": "[concat(parameters('windowsVirtualMachineName'), copyindex('winVMNames'))]" - } - ] - }, - "windowsVirtualMachineResourceIdOutput": { - "copy": [ - { - "name": "winVMIds", - "count": "[parameters('windowsVirtualMachineCount')]", - "input": "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('windowsVirtualMachineName'), copyindex('winVMIds')))]" - } - ] - }, - "linuxVirtualMachineNameOutput": { - "copy": [ - { - "name": "linuxVMNames", - "count": "[parameters('linuxVirtualMachineCount')]", - "input": "[concat(parameters('linuxVirtualMachineName'), copyindex('linuxVMNames'))]" - } - ] - }, - "linuxVirtualMachineResourceIdOutput": { - "copy": [ - { - "name": "linuxVMIds", - "count": "[parameters('linuxVirtualMachineCount')]", - "input": "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('linuxVirtualMachineName'), copyindex('linuxVMIds')))]" - } - ] - } - }, - "resources": [ - { - "type": "Microsoft.Compute/availabilitySets", - "apiVersion": "2016-04-30-preview", - "location": "[resourceGroup().location]", - "name": "[variables('windowsAvailabilitySetName')]", - "properties": { - "platformFaultDomainCount": 2, - "platformUpdateDomainCount": 5, - "managed": true - }, - "sku": { - "name": "Aligned" - } - }, - { - "type": "Microsoft.Network/networkInterfaces", - "apiVersion": "2017-09-01", - "location": "[resourceGroup().location]", - "name": "[concat(parameters('windowsVirtualMachineName'), copyindex(1), '-nic')]", - "copy": { - "name": "nicLoop", - "count": "[parameters('windowsVirtualMachineCount')]" - }, - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "privateIPAllocationMethod": "Dynamic", - "subnet": { - "id": "[variables('subnetId')]" - }, - "applicationSecurityGroups": [ - { - "id": "[parameters('jumpboxAsgId')]" - } - ] - } - } - ] - } - }, - { - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2017-03-30", - "location": "[resourceGroup().location]", - "name": "[concat(parameters('windowsVirtualMachineName'), copyindex(1))]", - "copy": { - "name": "windowsVmLoop", - "count": "[parameters('windowsVirtualMachineCount')]" - }, - "tags": { - "UpdateManagement": "[variables('tagPatching')]" - }, - "dependsOn": [ - "[resourceId('Microsoft.Compute/availabilitySets', variables('windowsAvailabilitySetName'))]", - "[resourceId('Microsoft.Network/networkInterfaces', concat(parameters('windowsVirtualMachineName'), copyindex(1), '-nic'))]" - ], - "properties": { - "availabilitySet": { - "id": "[resourceId('Microsoft.Compute/availabilitySets', variables('windowsAvailabilitySetName'))]" - }, - "osProfile": { - "computerName": "[concat(parameters('windowsVirtualMachineName'), copyindex(1))]", - "adminUsername": "[parameters('adminUsername')]", - "adminPassword": "[parameters('adminPassword')]" - }, - "hardwareProfile": { - "vmSize": "[parameters('windowsVirtualMachineSize')]" - }, - "storageProfile": { - "imageReference": { - "publisher": "[parameters('windowsOSImage').publisher]", - "offer": "[parameters('windowsOSImage').offer]", - "sku": "[parameters('windowsOSImage').sku]", - "version": "latest" - }, - "osDisk": { - "name": "[replace(toLower(substring(concat(parameters('windowsVirtualMachineName'), copyindex(1), '-osdisk', '-', replace(concat(variables('uniqueString'), variables('uniqueString')), '-', '')), 0, 40)), '-', '')]", - "osType": "Windows", - "createOption": "FromImage" - } - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[resourceId('Microsoft.Network/networkInterfaces', concat(parameters('windowsVirtualMachineName'), copyindex(1), '-nic'))]" - } - ] - }, - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": true, - "storageUri": "[concat('https://', parameters('diagnosticsStorageAccountName'), '.blob.core.windows.net/')]" - } - } - }, - "resources": [ - { - "type": "extensions", - "name": "[variables('mmaExtensionName')]", - "apiVersion": "2015-06-15", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('windowsVirtualMachineName'), copyindex(1)))]" - ], - "properties": { - "publisher": "Microsoft.EnterpriseCloud.Monitoring", - "type": "MicrosoftMonitoringAgent", - "typeHandlerVersion": "1.0", - "autoUpgradeMinorVersion": true, - "settings": { - "workspaceId": "[parameters('workspaceId')]" - }, - "protectedSettings": { - "workspaceKey": "[parameters('logAnalyticsWorkspacePrimarySharedKey')]" - } - } - }, - { - "type": "extensions", - "name": "[variables('antimalwareExtensionName')]", - "apiVersion": "2017-03-30", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('windowsVirtualMachineName'), copyindex(1)))]", - "[variables('mmaExtensionName')]" - ], - "properties": { - "publisher": "Microsoft.Azure.Security", - "type": "IaaSAntimalware", - "typeHandlerVersion": "1.5", - "autoUpgradeMinorVersion": true, - "settings": { - "AntimalwareEnabled": true, - "RealtimeProtectionEnabled": "true", - "ScheduledScanSettings": { - "isEnabled": "true", - "scanType": "Quick", - "day": "7", - "time": "120" - } - } - } - }, - { - "type": "extensions", - "name": "[variables('diagnosticsExtensionName')]", - "apiVersion": "2017-12-01", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('windowsVirtualMachineName'), copyindex(1)))]", - "[variables('antimalwareExtensionName')]" - ], - "properties": { - "publisher": "Microsoft.Azure.Diagnostics", - "type": "IaaSDiagnostics", - "typeHandlerVersion": "1.5", - "autoUpgradeMinorVersion": true, - "settings": { - "StorageAccount": "[parameters('diagnosticsStorageAccountName')]", - "StorageType": "TableAndBlob", - "WadCfg": { - "DiagnosticMonitorConfiguration": { - "overallQuotaInMB": 5120, - "Metrics": { - "resourceId": "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('windowsVirtualMachineName'), copyindex(1)))]", - "MetricAggregation": [ - { - "scheduledTransferPeriod": "PT1H" - }, - { - "scheduledTransferPeriod": "PT1M" - } - ] - }, - "DiagnosticInfrastructureLogs": { - "scheduledTransferLogLevelFilter": "Error" - }, - "PerformanceCounters": { - "scheduledTransferPeriod": "PT1M", - "PerformanceCounterConfiguration": [ - { - "counterSpecifier": "\\Processor Information(_Total)\\% Processor Time", - "unit": "Percent", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Processor Information(_Total)\\% Privileged Time", - "unit": "Percent", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Processor Information(_Total)\\% User Time", - "unit": "Percent", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Processor Information(_Total)\\Processor Frequency", - "unit": "Count", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\System\\Processes", - "unit": "Count", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Process(_Total)\\Thread Count", - "unit": "Count", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Process(_Total)\\Handle Count", - "unit": "Count", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\System\\System Up Time", - "unit": "Count", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\System\\Context Switches/sec", - "unit": "CountPerSecond", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\System\\Processor Queue Length", - "unit": "Count", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Memory\\% Committed Bytes In Use", - "unit": "Percent", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Memory\\Available Bytes", - "unit": "Bytes", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Memory\\Committed Bytes", - "unit": "Bytes", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Memory\\Cache Bytes", - "unit": "Bytes", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Memory\\Pool Paged Bytes", - "unit": "Bytes", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Memory\\Pool Nonpaged Bytes", - "unit": "Bytes", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Memory\\Pages/sec", - "unit": "CountPerSecond", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Memory\\Page Faults/sec", - "unit": "CountPerSecond", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Process(_Total)\\Working Set", - "unit": "Count", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Process(_Total)\\Working Set - Private", - "unit": "Count", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\LogicalDisk(_Total)\\% Disk Time", - "unit": "Percent", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\LogicalDisk(_Total)\\% Disk Read Time", - "unit": "Percent", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\LogicalDisk(_Total)\\% Disk Write Time", - "unit": "Percent", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\LogicalDisk(_Total)\\% Idle Time", - "unit": "Percent", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\LogicalDisk(_Total)\\Disk Bytes/sec", - "unit": "BytesPerSecond", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\LogicalDisk(_Total)\\Disk Read Bytes/sec", - "unit": "BytesPerSecond", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\LogicalDisk(_Total)\\Disk Write Bytes/sec", - "unit": "BytesPerSecond", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\LogicalDisk(_Total)\\Disk Transfers/sec", - "unit": "BytesPerSecond", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\LogicalDisk(_Total)\\Disk Reads/sec", - "unit": "BytesPerSecond", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\LogicalDisk(_Total)\\Disk Writes/sec", - "unit": "BytesPerSecond", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\LogicalDisk(_Total)\\Avg. Disk sec/Transfer", - "unit": "Count", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\LogicalDisk(_Total)\\Avg. Disk sec/Read", - "unit": "Count", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\LogicalDisk(_Total)\\Avg. Disk sec/Write", - "unit": "Count", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\LogicalDisk(_Total)\\Avg. Disk Queue Length", - "unit": "Count", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\LogicalDisk(_Total)\\Avg. Disk Read Queue Length", - "unit": "Count", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\LogicalDisk(_Total)\\Avg. Disk Write Queue Length", - "unit": "Count", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\LogicalDisk(_Total)\\% Free Space", - "unit": "Percent", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\LogicalDisk(_Total)\\Free Megabytes", - "unit": "Count", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Network Interface(*)\\Bytes Total/sec", - "unit": "BytesPerSecond", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Network Interface(*)\\Bytes Sent/sec", - "unit": "BytesPerSecond", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Network Interface(*)\\Bytes Received/sec", - "unit": "BytesPerSecond", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Network Interface(*)\\Packets/sec", - "unit": "BytesPerSecond", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Network Interface(*)\\Packets Sent/sec", - "unit": "BytesPerSecond", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Network Interface(*)\\Packets Received/sec", - "unit": "BytesPerSecond", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Network Interface(*)\\Packets Outbound Errors", - "unit": "Count", - "sampleRate": "PT60S" - }, - { - "counterSpecifier": "\\Network Interface(*)\\Packets Received Errors", - "unit": "Count", - "sampleRate": "PT60S" - } - ] - }, - "WindowsEventLog": { - "scheduledTransferPeriod": "PT1M", - "DataSource": [ - { - "name": "Application!*[System[(Level = 1 or Level = 2 or Level = 3)]]" - }, - { - "name": "Security!*[System[band(Keywords,4503599627370496)]]" - }, - { - "name": "System!*[System[(Level = 1 or Level = 2 or Level = 3)]]" - } - ] - } - } - } - }, - "protectedSettings": { - "storageAccountName": "[parameters('diagnosticsStorageAccountName')]", - "storageAccountEndPoint": "https://core.windows.net/", - "storageAccountSasToken": "[parameters('diagnosticsStorageAccountSasToken')]" - } - } - }, - { - "type": "extensions", - "name": "[variables('networkWatcherExtensionName')]", - "apiVersion": "2017-03-30", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('windowsVirtualMachineName'), copyindex(1)))]" - ], - "properties": { - "publisher": "Microsoft.Azure.NetworkWatcher", - "type": "NetworkWatcherAgentWindows", - "typeHandlerVersion": "1.4", - "autoUpgradeMinorVersion": true - } - }, - { - "type": "extensions", - "name": "[variables('windowsPasswordPoliciesExtensionName')]", - "apiVersion": "2017-03-30", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('windowsVirtualMachineName'), copyindex(1)))]" - ], - "properties": { - "publisher": "Microsoft.Compute", - "type": "CustomScriptExtension", - "typeHandlerVersion": "1.8", - "autoUpgradeMinorVersion": true, - "settings": { - "fileUris": [ - "[concat('https://', parameters('artifactsStorageAccountName'), '.blob.core.windows.net/scripts/Windows/enable-local-policy-settings.ps1')]" - ] - }, - "protectedSettings": { - "storageAccountName": "[parameters('artifactsStorageAccountName')]", - "storageAccountKey": "[parameters('artifactsStorageAccountKey')]", - "commandToExecute": "[concat('powershell -ExecutionPolicy Unrestricted -File ./Windows/enable-local-policy-settings.ps1 -MaxPwdAge ', variables('maxPasswordAge'), ' -MinPwdAge ', variables('minPasswordAge'), ' -MinPwdLength ', variables('minPasswordLength'), ' -PwdHistoryCount ', variables('pwdHistoryCount'))]" - } - } - }, - { - "type": "extensions", - "name": "[variables('windowsDependencyExtensionName')]", - "apiVersion": "2018-06-01", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('windowsVirtualMachineName'), copyindex(1)))]", - "[variables('windowsPasswordPoliciesExtensionName')]" - ], - "properties": { - "publisher": "[variables('windowsDependencyExtensionPublisher')]", - "type": "[variables('windowsDependencyExtensionType')]", - "typeHandlerVersion": "[variables('windowsDependencyExtensionHandlerVersion')]", - "autoUpgradeMinorVersion": true - } - }, - { - "apiVersion": "2018-06-30-preview", - "type": "Microsoft.Compute/virtualMachines/providers/guestConfigurationAssignments", - "name": "[concat(parameters('windowsVirtualMachineName'), copyindex(1), '/Microsoft.GuestConfiguration/', variables('pwdMinLengthConfigName'))]", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('windowsVirtualMachineName'), copyindex(1)))]", - "[variables('windowsDependencyExtensionName')]" - ], - "properties": { - "guestConfiguration": { - "name": "[variables('pwdMinLengthConfigName')]", - "version": "1.*" - } - } - }, - { - "apiVersion": "2018-06-30-preview", - "type": "Microsoft.Compute/virtualMachines/providers/guestConfigurationAssignments", - "name": "[concat(parameters('windowsVirtualMachineName'), copyindex(1), '/Microsoft.GuestConfiguration/', variables('pwdMinAgeConfigName'))]", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('windowsVirtualMachineName'), copyindex(1)))]", - "[variables('windowsDependencyExtensionName')]" - ], - "properties": { - "guestConfiguration": { - "name": "[variables('pwdMinAgeConfigName')]", - "version": "1.*" - } - } - }, - { - "apiVersion": "2018-06-30-preview", - "type": "Microsoft.Compute/virtualMachines/providers/guestConfigurationAssignments", - "name": "[concat(parameters('windowsVirtualMachineName'), copyindex(1), '/Microsoft.GuestConfiguration/', variables('pwdNotLast24ConfigName'))]", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('windowsVirtualMachineName'), copyindex(1)))]", - "[variables('windowsDependencyExtensionName')]" - ], - "properties": { - "guestConfiguration": { - "name": "[variables('pwdNotLast24ConfigName')]", - "version": "1.*" - } - } - }, - { - "apiVersion": "2018-06-30-preview", - "type": "Microsoft.Compute/virtualMachines/providers/guestConfigurationAssignments", - "name": "[concat(parameters('windowsVirtualMachineName'), copyindex(1), '/Microsoft.GuestConfiguration/', variables('pwdWithoutReversibleEncryptionConfigName'))]", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('windowsVirtualMachineName'), copyindex(1)))]", - "[variables('windowsDependencyExtensionName')]" - ], - "properties": { - "guestConfiguration": { - "name": "[variables('pwdWithoutReversibleEncryptionConfigName')]", - "version": "1.*" - } - } - }, - { - "apiVersion": "2018-06-30-preview", - "type": "Microsoft.Compute/virtualMachines/providers/guestConfigurationAssignments", - "name": "[concat(parameters('windowsVirtualMachineName'), copyindex(1), '/Microsoft.GuestConfiguration/', variables('webServerWithTLS1.1configname'))]", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('windowsVirtualMachineName'), copyindex(1)))]", - "[variables('windowsDependencyExtensionName')]" - ], - "properties": { - "guestConfiguration": { - "name": "[variables('webServerWithTLS1.1configname')]", - "version": "1.*" - } - } - }, - { - "apiVersion": "2018-06-30-preview", - "type": "Microsoft.Compute/virtualMachines/providers/guestConfigurationAssignments", - "name": "[concat(parameters('windowsVirtualMachineName'), copyindex(1), '/Microsoft.GuestConfiguration/', variables('pwdMaxAgeConfigName'))]", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('windowsVirtualMachineName'), copyindex(1)))]", - "[variables('windowsDependencyExtensionName')]" - ], - "properties": { - "guestConfiguration": { - "name": "[variables('pwdMaxAgeConfigName')]", - "version": "1.*" - } - } - }, - { - "apiVersion": "2018-06-30-preview", - "type": "Microsoft.Compute/virtualMachines/providers/guestConfigurationAssignments", - "name": "[concat(parameters('windowsVirtualMachineName'), copyindex(1), '/Microsoft.GuestConfiguration/', variables('pwdComplexityConfigName'))]", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('windowsVirtualMachineName'), copyindex(1)))]", - "[variables('windowsDependencyExtensionName')]" - ], - "properties": { - "guestConfiguration": { - "name": "[variables('pwdComplexityConfigName')]", - "version": "1.*" - } - } - }, - { - "apiVersion": "2015-05-01-preview", - "name": "AzurePolicyforWindows", - "type": "extensions", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('windowsVirtualMachineName'), copyindex(1)))]", - "[variables('windowsDependencyExtensionName')]" - ], - "properties": { - "publisher": "Microsoft.GuestConfiguration", - "type": "ConfigurationforWindows", - "typeHandlerVersion": "1.1", - "autoUpgradeMinorVersion": true, - "settings": {}, - "protectedSettings": {} - } - }, - { - "type": "extensions", - "name": "[variables('encryptionExtensionName')]", - "apiVersion": "2017-03-30", - "location": "[resourceGroup().location]", - "condition": "[variables('enableDiskEncryption')]", - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('windowsVirtualMachineName'), copyindex(1)))]", - "[resourceId('Microsoft.Compute/virtualMachines/extensions', concat(parameters('windowsVirtualMachineName'), copyindex(1)), variables('antimalwareExtensionName'))]", - "[resourceId('Microsoft.Compute/virtualMachines/extensions', concat(parameters('windowsVirtualMachineName'), copyindex(1)), variables('diagnosticsExtensionName'))]", - "[resourceId('Microsoft.Compute/virtualMachines/extensions', concat(parameters('windowsVirtualMachineName'), copyindex(1)), variables('networkWatcherExtensionName'))]", - "[resourceId('Microsoft.Compute/virtualMachines/extensions', concat(parameters('windowsVirtualMachineName'), copyindex(1)), variables('windowsPasswordPoliciesExtensionName'))]", - "[resourceId('Microsoft.Compute/virtualMachines/extensions', concat(parameters('windowsVirtualMachineName'), copyindex(1)), variables('windowsDependencyExtensionName'))]", - "[resourceId('Microsoft.Compute/virtualMachines/extensions', concat(parameters('windowsVirtualMachineName'), copyindex(1)), 'AzurePolicyforWindows')]" - ], - "properties": { - "publisher": "Microsoft.Azure.Security", - "type": "AzureDiskEncryption", - "typeHandlerVersion": "2.2", - "autoUpgradeMinorVersion": true, - "forceUpdateTag": "1.0", - "settings": { - "EncryptionOperation": "[variables('encryptionOperation')]", - "KeyVaultURL": "[parameters('keyVaultURL')]", - "KeyVaultResourceId": "[parameters('keyVaultId')]", - "KeyEncryptionKeyURL": "[parameters('jumpboxKeyEncryptionURL')]", - "KeyEncryptionAlgorithm": "[variables('keyEncryptionAlgorithm')]", - "ResizeOSDisk": false, - "VolumeType": "All" - } - } - } - ] - }, - { - "name": "[concat('vm', copyindex(1), 'OSEncryptionNestedDeployment')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2016-09-01", - "condition": "[variables('enableDiskEncryption')]", - "dependsOn": [ - "windowsVmLoop" - ], - "copy": { - "name": "vmWindowsEncryptLoop", - "count": "[parameters('windowsVirtualMachineCount')]" - }, - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "resources": [ - { - "apiVersion": "2017-03-30", - "type": "Microsoft.Compute/virtualMachines", - "name": "[concat(parameters('windowsVirtualMachineName'), copyindex(1))]", - "location": "[resourceGroup().location]", - "condition": "[variables('enableDiskEncryption')]", - "properties": { - "storageProfile": { - "osDisk": { - "encryptionSettings": { - "enabled": true, - "diskEncryptionKey": { - "sourceVault": { - "id": "[parameters('keyVaultId')]" - }, - "secretUrl": "[if(equals(variables('enableDiskEncryption'), bool('false')), json('null'), reference(resourceId('Microsoft.Compute/virtualMachines/extensions', concat(parameters('windowsVirtualMachineName'), copyindex(1)), variables('encryptionExtensionName'))).instanceView.statuses[0].message)]" - }, - "keyEncryptionKey": { - "sourceVault": { - "id": "[parameters('keyVaultId')]" - }, - "keyUrl": "[parameters('jumpboxKeyEncryptionURL')]" - } - } - } - } - } - } - ] - }, - "parameters": {} - } - }, - { - "type": "Microsoft.Compute/availabilitySets", - "apiVersion": "2016-04-30-preview", - "location": "[resourceGroup().location]", - "name": "[variables('linuxAvailabilitySetName')]", - "properties": { - "platformFaultDomainCount": 2, - "platformUpdateDomainCount": 5, - "managed": true - }, - "sku": { - "name": "Aligned" - } - }, - { - "type": "Microsoft.Network/networkInterfaces", - "apiVersion": "2017-09-01", - "location": "[resourceGroup().location]", - "name": "[concat(parameters('linuxVirtualMachineName'), copyindex(1), '-nic')]", - "copy": { - "name": "nicLoop", - "count": "[parameters('linuxVirtualMachineCount')]" - }, - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "privateIPAllocationMethod": "Dynamic", - "subnet": { - "id": "[variables('subnetId')]" - }, - "applicationSecurityGroups": [ - { - "id": "[parameters('jumpboxAsgId')]" - } - ] - } - } - ] - } - }, - { - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2018-06-01", - "location": "[resourceGroup().location]", - "name": "[concat(parameters('linuxVirtualMachineName'), copyindex(1))]", - "copy": { - "name": "linuxVmLoop", - "count": "[parameters('linuxVirtualMachineCount')]" - }, - "tags": { - "UpdateManagement": "[variables('tagPatching')]" - }, - "dependsOn": [ - "[resourceId('Microsoft.Compute/availabilitySets', variables('linuxAvailabilitySetName'))]", - "[concat('Microsoft.Network/networkInterfaces/', concat(parameters('linuxVirtualMachineName'), copyindex(1), '-nic'))]" - ], - "properties": { - "availabilitySet": { - "id": "[resourceId('Microsoft.Compute/availabilitySets', variables('linuxAvailabilitySetName'))]" - }, - "osProfile": { - "computerName": "[concat(parameters('linuxVirtualMachineName'), copyindex(1))]", - "adminUsername": "[parameters('adminUsername')]", - "adminPassword": "[parameters('adminPassword')]" - }, - "hardwareProfile": { - "vmSize": "[parameters('linuxVirtualMachineSize')]" - }, - "storageProfile": { - "imageReference": { - "publisher": "[parameters('linuxOSImage').publisher]", - "offer": "[parameters('linuxOSImage').offer]", - "sku": "[parameters('linuxOSImage').sku]", - "version": "latest" - }, - "osDisk": { - "name": "[replace(toLower(substring(concat(parameters('linuxVirtualMachineName'), copyindex(1), '-osdisk', '-', replace(concat(variables('uniqueString'), variables('uniqueString')), '-', '')), 0, 40)), '-', '')]", - "osType": "Linux", - "createOption": "FromImage" - } - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[resourceId('Microsoft.Network/networkInterfaces', concat(parameters('linuxVirtualMachineName'), copyindex(1), '-nic'))]", - "properties": { - "primary": true - } - } - ] - }, - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": true, - "storageUri": "[concat('https://', parameters('diagnosticsStorageAccountName'), '.blob.core.windows.net/')]" - } - } - }, - "resources": [ - { - "type": "extensions", - "name": "[variables('mmaExtensionName')]", - "apiVersion": "2018-06-01", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('linuxVirtualMachineName'), copyindex(1)))]" - ], - "properties": { - "publisher": "Microsoft.EnterpriseCloud.Monitoring", - "type": "OmsAgentForLinux", - "typeHandlerVersion": "1.7", - "settings": { - "workspaceId": "[parameters('workspaceId')]" - }, - "protectedSettings": { - "workspaceKey": "[parameters('logAnalyticsWorkspacePrimarySharedKey')]" - } - } - }, - { - "type": "extensions", - "name": "[variables('linuxDiagnosticsExtensionName')]", - "apiVersion": "2017-12-01", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('linuxVirtualMachineName'), copyindex(1)))]", - "[variables('mmaExtensionName')]" - ], - "properties": { - "publisher": "Microsoft.Azure.Diagnostics", - "type": "LinuxDiagnostic", - "typeHandlerVersion": "3.0", - "autoUpgradeMinorVersion": true, - "settings": { - "StorageAccount": "[parameters('diagnosticsStorageAccountName')]", - "ladCfg": { - "diagnosticMonitorConfiguration": { - "eventVolume": "Medium", - "metrics": { - "metricAggregation": [ - { - "scheduledTransferPeriod": "PT1H" - }, - { - "scheduledTransferPeriod": "PT1M" - } - ], - "resourceId": "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('linuxVirtualMachineName'), copyindex(1)))]" - }, - "performanceCounters": { - "performanceCounterConfiguration": [ - { - "annotation": [ - { - "displayName": "Disk read guest OS", - "locale": "en-us" - } - ], - "class": "disk", - "condition": "IsAggregate=TRUE", - "counter": "readbytespersecond", - "counterSpecifier": "/builtin/disk/readbytespersecond", - "type": "builtin", - "unit": "BytesPerSecond" - }, - { - "annotation": [ - { - "displayName": "Disk writes", - "locale": "en-us" - } - ], - "class": "disk", - "condition": "IsAggregate=TRUE", - "counter": "writespersecond", - "counterSpecifier": "/builtin/disk/writespersecond", - "type": "builtin", - "unit": "CountPerSecond" - }, - { - "annotation": [ - { - "displayName": "Disk transfer time", - "locale": "en-us" - } - ], - "class": "disk", - "condition": "IsAggregate=TRUE", - "counter": "averagetransfertime", - "counterSpecifier": "/builtin/disk/averagetransfertime", - "type": "builtin", - "unit": "Seconds" - }, - { - "annotation": [ - { - "displayName": "Disk transfers", - "locale": "en-us" - } - ], - "class": "disk", - "condition": "IsAggregate=TRUE", - "counter": "transferspersecond", - "counterSpecifier": "/builtin/disk/transferspersecond", - "type": "builtin", - "unit": "CountPerSecond" - }, - { - "annotation": [ - { - "displayName": "Disk write guest OS", - "locale": "en-us" - } - ], - "class": "disk", - "condition": "IsAggregate=TRUE", - "counter": "writebytespersecond", - "counterSpecifier": "/builtin/disk/writebytespersecond", - "type": "builtin", - "unit": "BytesPerSecond" - }, - { - "annotation": [ - { - "displayName": "Disk read time", - "locale": "en-us" - } - ], - "class": "disk", - "condition": "IsAggregate=TRUE", - "counter": "averagereadtime", - "counterSpecifier": "/builtin/disk/averagereadtime", - "type": "builtin", - "unit": "Seconds" - }, - { - "annotation": [ - { - "displayName": "Disk write time", - "locale": "en-us" - } - ], - "class": "disk", - "condition": "IsAggregate=TRUE", - "counter": "averagewritetime", - "counterSpecifier": "/builtin/disk/averagewritetime", - "type": "builtin", - "unit": "Seconds" - }, - { - "annotation": [ - { - "displayName": "Disk total bytes", - "locale": "en-us" - } - ], - "class": "disk", - "condition": "IsAggregate=TRUE", - "counter": "bytespersecond", - "counterSpecifier": "/builtin/disk/bytespersecond", - "type": "builtin", - "unit": "BytesPerSecond" - }, - { - "annotation": [ - { - "displayName": "Disk reads", - "locale": "en-us" - } - ], - "class": "disk", - "condition": "IsAggregate=TRUE", - "counter": "readspersecond", - "counterSpecifier": "/builtin/disk/readspersecond", - "type": "builtin", - "unit": "CountPerSecond" - }, - { - "annotation": [ - { - "displayName": "Disk queue length", - "locale": "en-us" - } - ], - "class": "disk", - "condition": "IsAggregate=TRUE", - "counter": "averagediskqueuelength", - "counterSpecifier": "/builtin/disk/averagediskqueuelength", - "type": "builtin", - "unit": "Count" - }, - { - "annotation": [ - { - "displayName": "Network in guest OS", - "locale": "en-us" - } - ], - "class": "network", - "counter": "bytesreceived", - "counterSpecifier": "/builtin/network/bytesreceived", - "type": "builtin", - "unit": "Bytes" - }, - { - "annotation": [ - { - "displayName": "Network total bytes", - "locale": "en-us" - } - ], - "class": "network", - "counter": "bytestotal", - "counterSpecifier": "/builtin/network/bytestotal", - "type": "builtin", - "unit": "Bytes" - }, - { - "annotation": [ - { - "displayName": "Network out guest OS", - "locale": "en-us" - } - ], - "class": "network", - "counter": "bytestransmitted", - "counterSpecifier": "/builtin/network/bytestransmitted", - "type": "builtin", - "unit": "Bytes" - }, - { - "annotation": [ - { - "displayName": "Network collisions", - "locale": "en-us" - } - ], - "class": "network", - "counter": "totalcollisions", - "counterSpecifier": "/builtin/network/totalcollisions", - "type": "builtin", - "unit": "Count" - }, - { - "annotation": [ - { - "displayName": "Packets received errors", - "locale": "en-us" - } - ], - "class": "network", - "counter": "totalrxerrors", - "counterSpecifier": "/builtin/network/totalrxerrors", - "type": "builtin", - "unit": "Count" - }, - { - "annotation": [ - { - "displayName": "Packets sent", - "locale": "en-us" - } - ], - "class": "network", - "counter": "packetstransmitted", - "counterSpecifier": "/builtin/network/packetstransmitted", - "type": "builtin", - "unit": "Count" - }, - { - "annotation": [ - { - "displayName": "Packets received", - "locale": "en-us" - } - ], - "class": "network", - "counter": "packetsreceived", - "counterSpecifier": "/builtin/network/packetsreceived", - "type": "builtin", - "unit": "Count" - }, - { - "annotation": [ - { - "displayName": "Packets sent errors", - "locale": "en-us" - } - ], - "class": "network", - "counter": "totaltxerrors", - "counterSpecifier": "/builtin/network/totaltxerrors", - "type": "builtin", - "unit": "Count" - }, - { - "annotation": [ - { - "displayName": "Filesystem transfers/sec", - "locale": "en-us" - } - ], - "class": "filesystem", - "condition": "IsAggregate=TRUE", - "counter": "transferspersecond", - "counterSpecifier": "/builtin/filesystem/transferspersecond", - "type": "builtin", - "unit": "CountPerSecond" - }, - { - "annotation": [ - { - "displayName": "Filesystem % free space", - "locale": "en-us" - } - ], - "class": "filesystem", - "condition": "IsAggregate=TRUE", - "counter": "percentfreespace", - "counterSpecifier": "/builtin/filesystem/percentfreespace", - "type": "builtin", - "unit": "Percent" - }, - { - "annotation": [ - { - "displayName": "Filesystem % used space", - "locale": "en-us" - } - ], - "class": "filesystem", - "condition": "IsAggregate=TRUE", - "counter": "percentusedspace", - "counterSpecifier": "/builtin/filesystem/percentusedspace", - "type": "builtin", - "unit": "Percent" - }, - { - "annotation": [ - { - "displayName": "Filesystem used space", - "locale": "en-us" - } - ], - "class": "filesystem", - "condition": "IsAggregate=TRUE", - "counter": "usedspace", - "counterSpecifier": "/builtin/filesystem/usedspace", - "type": "builtin", - "unit": "Bytes" - }, - { - "annotation": [ - { - "displayName": "Filesystem read bytes/sec", - "locale": "en-us" - } - ], - "class": "filesystem", - "condition": "IsAggregate=TRUE", - "counter": "bytesreadpersecond", - "counterSpecifier": "/builtin/filesystem/bytesreadpersecond", - "type": "builtin", - "unit": "CountPerSecond" - }, - { - "annotation": [ - { - "displayName": "Filesystem free space", - "locale": "en-us" - } - ], - "class": "filesystem", - "condition": "IsAggregate=TRUE", - "counter": "freespace", - "counterSpecifier": "/builtin/filesystem/freespace", - "type": "builtin", - "unit": "Bytes" - }, - { - "annotation": [ - { - "displayName": "Filesystem % free inodes", - "locale": "en-us" - } - ], - "class": "filesystem", - "condition": "IsAggregate=TRUE", - "counter": "percentfreeinodes", - "counterSpecifier": "/builtin/filesystem/percentfreeinodes", - "type": "builtin", - "unit": "Percent" - }, - { - "annotation": [ - { - "displayName": "Filesystem bytes/sec", - "locale": "en-us" - } - ], - "class": "filesystem", - "condition": "IsAggregate=TRUE", - "counter": "bytespersecond", - "counterSpecifier": "/builtin/filesystem/bytespersecond", - "type": "builtin", - "unit": "BytesPerSecond" - }, - { - "annotation": [ - { - "displayName": "Filesystem reads/sec", - "locale": "en-us" - } - ], - "class": "filesystem", - "condition": "IsAggregate=TRUE", - "counter": "readspersecond", - "counterSpecifier": "/builtin/filesystem/readspersecond", - "type": "builtin", - "unit": "CountPerSecond" - }, - { - "annotation": [ - { - "displayName": "Filesystem write bytes/sec", - "locale": "en-us" - } - ], - "class": "filesystem", - "condition": "IsAggregate=TRUE", - "counter": "byteswrittenpersecond", - "counterSpecifier": "/builtin/filesystem/byteswrittenpersecond", - "type": "builtin", - "unit": "CountPerSecond" - }, - { - "annotation": [ - { - "displayName": "Filesystem writes/sec", - "locale": "en-us" - } - ], - "class": "filesystem", - "condition": "IsAggregate=TRUE", - "counter": "writespersecond", - "counterSpecifier": "/builtin/filesystem/writespersecond", - "type": "builtin", - "unit": "CountPerSecond" - }, - { - "annotation": [ - { - "displayName": "Filesystem % used inodes", - "locale": "en-us" - } - ], - "class": "filesystem", - "condition": "IsAggregate=TRUE", - "counter": "percentusedinodes", - "counterSpecifier": "/builtin/filesystem/percentusedinodes", - "type": "builtin", - "unit": "Percent" - }, - { - "annotation": [ - { - "displayName": "CPU IO wait time", - "locale": "en-us" - } - ], - "class": "processor", - "condition": "IsAggregate=TRUE", - "counter": "percentiowaittime", - "counterSpecifier": "/builtin/processor/percentiowaittime", - "type": "builtin", - "unit": "Percent" - }, - { - "annotation": [ - { - "displayName": "CPU user time", - "locale": "en-us" - } - ], - "class": "processor", - "condition": "IsAggregate=TRUE", - "counter": "percentusertime", - "counterSpecifier": "/builtin/processor/percentusertime", - "type": "builtin", - "unit": "Percent" - }, - { - "annotation": [ - { - "displayName": "CPU nice time", - "locale": "en-us" - } - ], - "class": "processor", - "condition": "IsAggregate=TRUE", - "counter": "percentnicetime", - "counterSpecifier": "/builtin/processor/percentnicetime", - "type": "builtin", - "unit": "Percent" - }, - { - "annotation": [ - { - "displayName": "CPU percentage guest OS", - "locale": "en-us" - } - ], - "class": "processor", - "condition": "IsAggregate=TRUE", - "counter": "percentprocessortime", - "counterSpecifier": "/builtin/processor/percentprocessortime", - "type": "builtin", - "unit": "Percent" - }, - { - "annotation": [ - { - "displayName": "CPU interrupt time", - "locale": "en-us" - } - ], - "class": "processor", - "condition": "IsAggregate=TRUE", - "counter": "percentinterrupttime", - "counterSpecifier": "/builtin/processor/percentinterrupttime", - "type": "builtin", - "unit": "Percent" - }, - { - "annotation": [ - { - "displayName": "CPU idle time", - "locale": "en-us" - } - ], - "class": "processor", - "condition": "IsAggregate=TRUE", - "counter": "percentidletime", - "counterSpecifier": "/builtin/processor/percentidletime", - "type": "builtin", - "unit": "Percent" - }, - { - "annotation": [ - { - "displayName": "CPU privileged time", - "locale": "en-us" - } - ], - "class": "processor", - "condition": "IsAggregate=TRUE", - "counter": "percentprivilegedtime", - "counterSpecifier": "/builtin/processor/percentprivilegedtime", - "type": "builtin", - "unit": "Percent" - }, - { - "annotation": [ - { - "displayName": "Memory available", - "locale": "en-us" - } - ], - "class": "memory", - "counter": "availablememory", - "counterSpecifier": "/builtin/memory/availablememory", - "type": "builtin", - "unit": "Bytes" - }, - { - "annotation": [ - { - "displayName": "Swap percent used", - "locale": "en-us" - } - ], - "class": "memory", - "counter": "percentusedswap", - "counterSpecifier": "/builtin/memory/percentusedswap", - "type": "builtin", - "unit": "Percent" - }, - { - "annotation": [ - { - "displayName": "Memory used", - "locale": "en-us" - } - ], - "class": "memory", - "counter": "usedmemory", - "counterSpecifier": "/builtin/memory/usedmemory", - "type": "builtin", - "unit": "Bytes" - }, - { - "annotation": [ - { - "displayName": "Page reads", - "locale": "en-us" - } - ], - "class": "memory", - "counter": "pagesreadpersec", - "counterSpecifier": "/builtin/memory/pagesreadpersec", - "type": "builtin", - "unit": "CountPerSecond" - }, - { - "annotation": [ - { - "displayName": "Swap available", - "locale": "en-us" - } - ], - "class": "memory", - "counter": "availableswap", - "counterSpecifier": "/builtin/memory/availableswap", - "type": "builtin", - "unit": "Bytes" - }, - { - "annotation": [ - { - "displayName": "Swap percent available", - "locale": "en-us" - } - ], - "class": "memory", - "counter": "percentavailableswap", - "counterSpecifier": "/builtin/memory/percentavailableswap", - "type": "builtin", - "unit": "Percent" - }, - { - "annotation": [ - { - "displayName": "Mem. percent available", - "locale": "en-us" - } - ], - "class": "memory", - "counter": "percentavailablememory", - "counterSpecifier": "/builtin/memory/percentavailablememory", - "type": "builtin", - "unit": "Percent" - }, - { - "annotation": [ - { - "displayName": "Pages", - "locale": "en-us" - } - ], - "class": "memory", - "counter": "pagespersec", - "counterSpecifier": "/builtin/memory/pagespersec", - "type": "builtin", - "unit": "CountPerSecond" - }, - { - "annotation": [ - { - "displayName": "Swap used", - "locale": "en-us" - } - ], - "class": "memory", - "counter": "usedswap", - "counterSpecifier": "/builtin/memory/usedswap", - "type": "builtin", - "unit": "Bytes" - }, - { - "annotation": [ - { - "displayName": "Memory percentage", - "locale": "en-us" - } - ], - "class": "memory", - "counter": "percentusedmemory", - "counterSpecifier": "/builtin/memory/percentusedmemory", - "type": "builtin", - "unit": "Percent" - }, - { - "annotation": [ - { - "displayName": "Page writes", - "locale": "en-us" - } - ], - "class": "memory", - "counter": "pageswrittenpersec", - "counterSpecifier": "/builtin/memory/pageswrittenpersec", - "type": "builtin", - "unit": "CountPerSecond" - } - ] - }, - "syslogEvents": { - "syslogEventConfiguration": { - "LOG_AUTH": "LOG_DEBUG", - "LOG_AUTHPRIV": "LOG_DEBUG", - "LOG_CRON": "LOG_DEBUG", - "LOG_DAEMON": "LOG_DEBUG", - "LOG_FTP": "LOG_DEBUG", - "LOG_KERN": "LOG_DEBUG", - "LOG_LOCAL0": "LOG_DEBUG", - "LOG_LOCAL1": "LOG_DEBUG", - "LOG_LOCAL2": "LOG_DEBUG", - "LOG_LOCAL3": "LOG_DEBUG", - "LOG_LOCAL4": "LOG_DEBUG", - "LOG_LOCAL5": "LOG_DEBUG", - "LOG_LOCAL6": "LOG_DEBUG", - "LOG_LOCAL7": "LOG_DEBUG", - "LOG_LPR": "LOG_DEBUG", - "LOG_MAIL": "LOG_DEBUG", - "LOG_NEWS": "LOG_DEBUG", - "LOG_SYSLOG": "LOG_DEBUG", - "LOG_USER": "LOG_DEBUG", - "LOG_UUCP": "LOG_DEBUG" - } - } - }, - "sampleRateInSeconds": 15 - } - }, - "protectedSettings": { - "storageAccountName": "[parameters('diagnosticsStorageAccountName')]", - "storageAccountEndPoint": "https://core.windows.net/", - "storageAccountSasToken": "[parameters('diagnosticsStorageAccountSasToken')]" - } - } - }, - { - "type": "extensions", - "name": "[concat('linux-', variables('networkWatcherExtensionName'))]", - "apiVersion": "2017-03-30", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('linuxVirtualMachineName'), copyindex(1)))]", - "[variables('mmaExtensionName')]" - ], - "properties": { - "publisher": "Microsoft.Azure.NetworkWatcher", - "type": "NetworkWatcherAgentLinux", - "typeHandlerVersion": "1.4", - "autoUpgradeMinorVersion": true - } - } - ] - } - ], - "outputs": { - "jumpboxResourceGroup": { - "type": "string", - "value": "[resourceGroup().name]", - "metadata" : { - "description": "The Resource Group the Jumpbox resources were deployed to." - } - }, - "windowsVirtualMachineNames": { - "type": "array", - "value": "[variables('windowsVirtualMachineNameOutput')['winVMNames']]", - "metadata": { - "description": "An Array of Names of the Windows Virtual Machines deployed." - } - }, - "windowsVirtualMachineResourceIds": { - "type": "array", - "value": "[variables('windowsVirtualMachineResourceIdOutput')['winVMIds']]", - "metadata": { - "description": "An Array of Resource Ids of the Windows Virtual Machines deployed." - } - }, - "linuxVirtualMachineNames": { - "type": "array", - "value": "[variables('linuxVirtualMachineNameOutput')['linuxVMNames']]", - "metadata": { - "description": "An Array of Names of the Linux Virtual Machines deployed." - } - }, - "linuxVirtualMachineResourceIds": { - "type": "array", - "value": "[variables('linuxVirtualMachineResourceIdOutput')['linuxVMIds']]", - "metadata": { - "description": "An Array of Resource Ids of the Windows Virtual Machines deployed." - } - } - } -} \ No newline at end of file diff --git a/Modules/Jumpbox/2.0/parameters.json b/Modules/Jumpbox/2.0/parameters.json deleted file mode 100644 index b67ae64..0000000 --- a/Modules/Jumpbox/2.0/parameters.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "windowsVirtualMachineName": { - "value": "winjb" - }, - "linuxVirtualMachineName": { - "value": "linuxjb" - }, - "workspaceId": { - "value": "" - }, - "logAnalyticsWorkspacePrimarySharedKey": { - "value": "" - }, - "artifactsStorageAccountKey": { - "value": "" - }, - "artifactsStorageAccountName": { - "value": "" - }, - "vNetId": { - "value": "" - }, - "jumpboxAsgId": { - "value": "" - }, - "subnetName": { - "value": "" - }, - "adminUsername": { - "value": "" - }, - "adminPassword": { - "value": "" - }, - "windowsVirtualMachineCount": { - "value": 1 - }, - "windowsVirtualMachineSize": { - "value": "Standard_DS2_v2" - }, - "windowsOSImage": { - "value": { - "offer": "WindowsServer", - "publisher": "MicrosoftWindowsServer", - "sku": "2016-Datacenter" - } - }, - "linuxVirtualMachineCount": { - "value": 1 - }, - "linuxVirtualMachineSize": { - "value": "Standard_D2s_v3" - }, - "linuxOSImage": { - "value": { - "publisher": "Canonical", - "offer": "UbuntuServer", - "sku": "18.04-LTS", - "version": "latest" - } - }, - "diagnosticsStorageAccountName": { - "value": "" - }, - "diagnosticsStorageAccountSasToken": { - "value": "" - } - } -} \ No newline at end of file diff --git a/Modules/Jumpbox/2.0/readme.md b/Modules/Jumpbox/2.0/readme.md deleted file mode 100644 index 24c5ba2..0000000 --- a/Modules/Jumpbox/2.0/readme.md +++ /dev/null @@ -1,52 +0,0 @@ -# Jumpbox - -This template deploys a Jumpbox. - -## Resources - -- Microsoft.Compute/virtualMachines -- Microsoft.Compute/availabilitySets -- Microsoft.Network/networkInterfaces -- Microsoft.Compute/virtualMachines/extensions -- Microsoft.Compute/virtualMachines/providers/guestConfigurationAssignments - -## Parameters - -| Parameter Name | Default Value | Description | -| :- | :- | :- | -| `windowsVirtualMachineName` | | Required. Name for the Jumpbox VM -| `linuxVirtualMachineName` | | Required. Name for the Jumpbox VM -| `workspaceId` | | Required. CustomerId value of Log Analytics. This value is referenced in MMA Extension -| `logAnalyticsWorkspacePrimarySharedKey` | | Required. WorkspaceKey value of OMS. This value is referenced in OMS VM Extension -| `artifactsStorageAccountKey` | | Required. Artifacts storage account Key. Storage account that contains output parameters and common scripts -| `artifactsStorageAccountName` | | Required. Artifacts storage account Name. -| `keyVaultURL` | `""` | Optional. AKV URI -| `keyVaultId` | `""` | Optional. AKV Resource Id -| `jumpboxKeyEncryptionURL` | `""` | Optional. Jumpbox AKV encryption key -| `vNetId` | | Required. Shared services Virtual Network resource Id -| `jumpboxAsgId` | | Required. Jumpbox ASG resource identifier -| `subnetName` | | Required. Name of Shared Services Subnet, this name is used to get the SubnetId -| `adminUserName` | | Required. The username used to establish jumpbox VMs. -| `adminPassword` | | Required. The password given to the admin user. -| `windowsVirtualMachineCount` | `1` | Optional. Number of jumpbox VMs to be created. -| `windowsVirtualMachineSize` | | Required. Size of the jumpbox VMs. -| `windowsOSImage` | | Required. OS image used for the jumpbox VMs. -| `linuxVirtualMachineCount` | `1` | Optional. Number of linux jumpbox VMs to be created. -| `linuxVirtualMachineSize` | | Required. Size of the jumpbox VMs. -| `linuxOSImage` | | Required. OS image used for the jumpbox VMs. -| `diagnosticsStorageAccountName` | | Required. Diagnostic Storage Account name -| `diagnosticsStorageAccountSasToken` | | Required. Diagnostic Storage Account SAS token - -## Outputs - -*N/A* - -## Considerations - -*N/A* - -## Additional resources - -- [Microsoft.Compute virtualMachines template reference](https://docs.microsoft.com/en-us/azure/templates/microsoft.compute/2019-03-01/virtualmachines) -- [Microsoft.Compute availabilitySets template reference)[https://docs.microsoft.com/en-us/azure/templates/microsoft.compute/2019-03-01/availabilitysets] -- [Microsoft.Network networkInterfaces template reference](https://docs.microsoft.com/en-us/azure/templates/microsoft.network/2018-11-01/networkinterfaces) diff --git a/Modules/Jumpbox/2.0/Policy/git_placeholder.md b/Modules/LoadBalancers/2.0/Policy/git_placeholder.md similarity index 100% rename from Modules/Jumpbox/2.0/Policy/git_placeholder.md rename to Modules/LoadBalancers/2.0/Policy/git_placeholder.md diff --git a/Modules/Jumpbox/2.0/RBAC/git_placeholder.md b/Modules/LoadBalancers/2.0/RBAC/git_placeholder.md similarity index 100% rename from Modules/Jumpbox/2.0/RBAC/git_placeholder.md rename to Modules/LoadBalancers/2.0/RBAC/git_placeholder.md diff --git a/Modules/Jumpbox/2.0/Scripts/git_placeholder.md b/Modules/LoadBalancers/2.0/Scripts/git_placeholder.md similarity index 100% rename from Modules/Jumpbox/2.0/Scripts/git_placeholder.md rename to Modules/LoadBalancers/2.0/Scripts/git_placeholder.md diff --git a/Modules/LoadBalancers/2.0/Tests/module.tests.ps1 b/Modules/LoadBalancers/2.0/Tests/module.tests.ps1 new file mode 100644 index 0000000..fa9f38e --- /dev/null +++ b/Modules/LoadBalancers/2.0/Tests/module.tests.ps1 @@ -0,0 +1,161 @@ +<# + .NOTES + ============================================================================================== + Copyright(c) Microsoft Corporation. All rights reserved. + + File: module.tests.ps1 + + Purpose: Pester - Test Load Balancers ARM Templates + + Version: 1.0.0.0 - 1st April 2019 - Azure Virtual Datacenter Development Team + ============================================================================================== + + .SYNOPSIS + This script contains functionality used to test Load Balancers ARM template synatax. + + .DESCRIPTION + This script contains functionality used to test Load Balancers ARM template synatax. + + Deployment steps of the script are outlined below. + 1) Test Template File Syntax + 2) Test Parameter File Syntax + 3) Test Template and Parameter File Compactibility +#> + +#Requires -Version 5 + +#region Parameters + +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +$here = Join-Path $here ".." +$template = Split-Path -Leaf $here +$TemplateFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "deploy.json") -Recurse | Select-Object -ExpandProperty Name) ) { + $TemplateFileTestCases += @{ TemplateFile = $File } +} +$ParameterFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "Tests" -AdditionalChildPath @("*parameters.json")) -Recurse -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name) ) { + $ParameterFileTestCases += @{ ParameterFile = Join-Path "Tests" $File } +} +$Modules = @(); +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "deploy.json") ) ) { + $Module = [PSCustomObject]@{ + 'Template' = $null + 'Parameters' = $null + } + $Module.Template = $File.FullName; + $Parameters = @(); + ForEach ( $ParameterFile in (Get-ChildItem (Join-Path "$here" "Tests" -AdditionalChildPath @("*parameters.json")) -Recurse -ErrorAction SilentlyContinue| Select-Object -ExpandProperty Name) ) { + $Parameters += (Join-Path "$here" "Tests" -AdditionalChildPath @("$ParameterFile") ) + } + $Module.Parameters = $Parameters; + $Modules += @{ Module = $Module }; +} + +#endregion + +#region Run Pester Test Script +Describe "Template: $template - Load Balancers" -Tags Unit { + + Context "Template File Syntax" { + + It "Has a JSON template file" { + (Join-Path "$here" "deploy.json") | Should Exist + } + + It "Converts from JSON and has the expected properties" -TestCases $TemplateFileTestCases { + Param( $TemplateFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters', + 'variables', + 'resources', + 'outputs' | Sort-Object + $templateProperties = (Get-Content (Join-Path "$here" "$TemplateFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateProperties | Should Be $expectedProperties + } + } + + Context "Parameter File Syntax" { + + It "Parameter file does not contains the expected properties" -TestCases $ParameterFileTestCases { + Param( $ParameterFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters' | Sort-Object + Write-Host $ParameterFile + Join-Path "$here" "$ParameterFile" | Write-Host + $templateFileProperties = (Get-Content (Join-Path "$here" "$ParameterFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateFileProperties | Should Be $expectedProperties + } + + } + + Context "Template and Parameter Compactibility" { + + It "Is count of required parameters in template file equal or lesser than count of all parameters in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $requiredParametersInTemplateFile.Count | Should Not BeGreaterThan $allParametersInParametersFile.Count; + } + } + + It "Has all parameters in parameters file existing in template file" -TestCases $Modules { + Param( $Module ) + + $allParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + Write-Host "File analyzed: $Parameter"; + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $result = @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}); + Write-Host "Invalid parameters: $(ConvertTo-Json $result)"; + @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}).Count | Should Be 0; + } + } + + It "Has required parameters in template file existing in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + + @($requiredParametersInTemplateFile | Where-Object {$allParametersInParametersFile -notcontains $_}).Count | Should Be 0; + } + } + } + +} +#endregion \ No newline at end of file diff --git a/Modules/LoadBalancers/2.0/Tests/parameters.json b/Modules/LoadBalancers/2.0/Tests/parameters.json new file mode 100644 index 0000000..3e6138f --- /dev/null +++ b/Modules/LoadBalancers/2.0/Tests/parameters.json @@ -0,0 +1,47 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "loadBalancerName": { + "value": "contoso-lb" + }, + "loadBalancingRules": { + "value": [ + { + "name": "load-balancing-1", + "properties": { + "frontendPort": 80, + "backendPort": 80, + "enableFloatingIP": false, + "idleTimeoutInMinutes": 3, + "protocol": "TCP", + "enableTcpReset": false, + "loadDistribution": false, + "disableOutboundSnat": false, + "probeName": "bar" + } + } + ] + }, + "probes": { + "value": [ + { + "name": "probe", + "properties": { + "protocol": "TCP", + "port": 80, + "requestPath": "/", + "intervalInSeconds": 10, + "numberOfProbes": 5 + } + } + ] + }, + "vNetId": { + "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup/providers/Microsoft.Network/virtualNetworks/contoso-vnet-example" + }, + "subnetName": { + "value": "sharedsvcs" + } + } +} \ No newline at end of file diff --git a/Modules/LoadBalancers/2.0/Tests/policy.tests.ps1 b/Modules/LoadBalancers/2.0/Tests/policy.tests.ps1 new file mode 100644 index 0000000..59b89af --- /dev/null +++ b/Modules/LoadBalancers/2.0/Tests/policy.tests.ps1 @@ -0,0 +1,163 @@ +<# + .NOTES + ============================================================================================== + Copyright(c) Microsoft Corporation. All rights reserved. + + File: module.tests.ps1 + + Purpose: Pester - Test Virtual Machine ARM Templates + + Version: 1.0.0.0 - 1st April 2019 - Azure Virtual Datacenter Development Team + ============================================================================================== + + .SYNOPSIS + This script contains functionality used to test Azure Virtual Machine ARM template synatax. + + .DESCRIPTION + This script contains functionality used to test Azure Virtual Machine ARM template synatax. + + Deployment steps of the script are outlined below. + 1) Test Template File Syntax + 2) Test Parameter File Syntax + 3) Test Template and Parameter File Compactibility +#> + +#Requires -Version 5 + +#region Parameters + +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +$here = Join-Path $here ".." +$template = Split-Path -Leaf $here +$TemplateFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "Policy" -AdditionalChildPath @("deploy.json")) -Recurse -ErrorAction SilentlyContinue| Select-Object -ExpandProperty Name) ) { + $TemplateFileTestCases += @{ TemplateFile = $File } +} +$ParameterFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "Policy" -AdditionalChildPath @("*parameters.json")) -Recurse -ErrorAction SilentlyContinue| Select-Object -ExpandProperty Name) ) { + $ParameterFileTestCases += @{ ParameterFile = Join-Path "Policy" $File } +} +$Modules = @(); +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "Policy" -AdditionalChildPath @("deploy.json")) -ErrorAction SilentlyContinue ) ) { + $Module = [PSCustomObject]@{ + 'Template' = $null + 'Parameters' = $null + } + $Module.Template = $File.FullName; + $Parameters = @(); + ForEach ( $ParameterFile in (Get-ChildItem (Join-Path "$here" "Policy" -AdditionalChildPath @("*parameters.json")) -Recurse -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name) ) { + $Parameters += (Join-Path "$here" "Policy" -AdditionalChildPath @("$ParameterFile") ) + } + $Module.Parameters = $Parameters; + $Modules += @{ Module = $Module }; +} + +#endregion + +if ($null -ne $TemplateFileTestCases -and + $TemplateFileTestCases.Count -gt 0) { + + #region Run Pester Test Script + Describe "Template: $template - Virtual Machine" -Tags Unit { + + Context "Template File Syntax" { + + It "Has a JSON template file" { + (Join-Path "$here" "deploy.json") | Should Exist + } + + It "Converts from JSON and has the expected properties" -TestCases $TemplateFileTestCases { + Param( $TemplateFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters', + 'variables', + 'resources', + 'outputs' | Sort-Object + $templateProperties = (Get-Content (Join-Path "$here" "$TemplateFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateProperties | Should Be $expectedProperties + } + } + + Context "Parameter File Syntax" { + + It "Parameter file does not contains the expected properties" -TestCases $ParameterFileTestCases { + Param( $ParameterFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters' | Sort-Object + Write-Host $ParameterFile + Join-Path "$here" "$ParameterFile" | Write-Host + $templateFileProperties = (Get-Content (Join-Path "$here" "$ParameterFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateFileProperties | Should Be $expectedProperties + } + + } + + Context "Template and Parameter Compactibility" { + + It "Is count of required parameters in template file equal or lesser than count of all parameters in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $requiredParametersInTemplateFile.Count | Should Not BeGreaterThan $allParametersInParametersFile.Count; + } + } + + It "Has all parameters in parameters file existing in template file" -TestCases $Modules { + Param( $Module ) + + $allParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + Write-Host "File analyzed: $Parameter"; + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $result = @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}); + Write-Host "Invalid parameters: $(ConvertTo-Json $result)"; + @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}).Count | Should Be 0; + } + } + + It "Has required parameters in template file existing in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + @($requiredParametersInTemplateFile| Where-Object {$allParametersInParametersFile -notcontains $_}).Count | Should Be 0; + } + } + } + + } + #endregion +} \ No newline at end of file diff --git a/Modules/LoadBalancers/2.0/Tests/rbac.tests.ps1 b/Modules/LoadBalancers/2.0/Tests/rbac.tests.ps1 new file mode 100644 index 0000000..0a22928 --- /dev/null +++ b/Modules/LoadBalancers/2.0/Tests/rbac.tests.ps1 @@ -0,0 +1,163 @@ +<# + .NOTES + ============================================================================================== + Copyright(c) Microsoft Corporation. All rights reserved. + + File: module.tests.ps1 + + Purpose: Pester - Test Virtual Machine ARM Templates + + Version: 1.0.0.0 - 1st April 2019 - Azure Virtual Datacenter Development Team + ============================================================================================== + + .SYNOPSIS + This script contains functionality used to test Azure Virtual Machine ARM template synatax. + + .DESCRIPTION + This script contains functionality used to test Azure Virtual Machine ARM template synatax. + + Deployment steps of the script are outlined below. + 1) Test Template File Syntax + 2) Test Parameter File Syntax + 3) Test Template and Parameter File Compactibility +#> + +#Requires -Version 5 + +#region Parameters + +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +$here = Join-Path $here ".." +$template = Split-Path -Leaf $here +$TemplateFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "RBAC" -AdditionalChildPath @("deploy.json")) -ErrorAction SilentlyContinue -Recurse | Select-Object -ExpandProperty Name) ) { + $TemplateFileTestCases += @{ TemplateFile = $File } +} +$ParameterFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "RBAC" -AdditionalChildPath @("*parameters.json")) -ErrorAction SilentlyContinue -Recurse | Select-Object -ExpandProperty Name) ) { + $ParameterFileTestCases += @{ ParameterFile = Join-Path "RBAC" $File } +} +$Modules = @(); +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "RBAC" -AdditionalChildPath @("deploy.json")) -ErrorAction SilentlyContinue ) ) { + $Module = [PSCustomObject]@{ + 'Template' = $null + 'Parameters' = $null + } + $Module.Template = $File.FullName; + $Parameters = @(); + ForEach ( $ParameterFile in (Get-ChildItem (Join-Path "$here" "RBAC" -AdditionalChildPath @("*parameters.json")) -Recurse | Select-Object -ExpandProperty Name) ) { + $Parameters += (Join-Path "$here" "RBAC" -AdditionalChildPath @("$ParameterFile") ) + } + $Module.Parameters = $Parameters; + $Modules += @{ Module = $Module }; +} + +#endregion + +if ($null -ne $TemplateFileTestCases -and + $TemplateFileTestCases.Count -gt 0) { + + #region Run Pester Test Script + Describe "Template: $template - Virtual Machine" -Tags Unit { + + Context "Template File Syntax" { + + It "Has a JSON template file" -TestCases $TemplateFileTestCases { + (Join-Path "$here" "deploy.json") | Should Exist + } + + It "Converts from JSON and has the expected properties" -TestCases $TemplateFileTestCases { + Param( $TemplateFile ) + Write-Host "TF: $TemplateFile" + $expectedProperties = '$schema', + 'contentVersion', + 'parameters', + 'variables', + 'resources', + 'outputs' | Sort-Object + $templateProperties = (Get-Content (Join-Path "$here" "$TemplateFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateProperties | Should Be $expectedProperties + } + } + + Context "Parameter File Syntax" { + + It "Parameter file does not contains the expected properties" -TestCases $ParameterFileTestCases { + Param( $ParameterFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters' | Sort-Object + Write-Host $ParameterFile + Join-Path "$here" "$ParameterFile" | Write-Host + $templateFileProperties = (Get-Content (Join-Path "$here" "$ParameterFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateFileProperties | Should Be $expectedProperties + } + } + + Context "Template and Parameter Compactibility" { + + It "Is count of required parameters in template file equal or lesser than count of all parameters in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $requiredParametersInTemplateFile.Count | Should Not BeGreaterThan $allParametersInParametersFile.Count; + } + } + + It "Has all parameters in parameters file existing in template file" -TestCases $Modules { + Param( $Module ) + + $allParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + Write-Host "File analyzed: $Parameter"; + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $result = @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}); + Write-Host "Invalid parameters: $(ConvertTo-Json $result)"; + @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}).Count | Should Be 0; + } + } + + It "Has required parameters in template file existing in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + @($requiredParametersInTemplateFile| Where-Object {$allParametersInParametersFile -notcontains $_}).Count | Should Be 0; + } + } + } + + } + #endregion +} \ No newline at end of file diff --git a/Modules/LoadBalancers/2.0/deploy.json b/Modules/LoadBalancers/2.0/deploy.json new file mode 100644 index 0000000..a3b4c06 --- /dev/null +++ b/Modules/LoadBalancers/2.0/deploy.json @@ -0,0 +1,157 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "loadBalancerName": { + "type": "string", + "metadata": { + "description": "Required. The Proximity Placement Groups Name" + } + }, + "loadBalancingRules": { + "type": "array", + "minLength": 1, + "metadata": { + "description": "Required. Array of objects containing all load balancing rules" + } + }, + "probes": { + "type": "array", + "minLength": 1, + "metadata": { + "description": "Required. Array of objects containing all probes, these are references in the load balancing rules" + } + }, + "vNetId": { + "type": "string", + "metadata": { + "description": "Required. Shared services Virtual Network resource identifier" + } + }, + "subnetName": { + "type": "string", + "metadata": { + "description": "Required. Name of Shared Services Subnet, this name is used to get the SubnetId" + } + }, + "loadBalancerIPAddress": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. IP address statically assigned to the Load Balancer. If left empty, the Load Balancer will use the next available IP (dynamic assignment)" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + } + }, + "variables": { + "subnetId": "[concat(parameters('vNetId'), '/subnets/', parameters('subnetName'))]", + "frontendIPConfiguration": "[concat(parameters('loadBalancerName'), '-fe')]", + "loadBalancerBackendPoolName": "[concat(parameters('loadBalancerName'), '-bp')]", + "loadBalancingRules": { + "copy": [ + { + "name": "loadBalancingRules", + "count": "[length(parameters('loadBalancingRules'))]", + "input": { + "name": "[parameters('loadBalancingRules')[copyIndex('loadBalancingRules')].name]", + "properties": { + "frontendIPConfiguration": { + "id": "[resourceId('Microsoft.Network/loadBalancers/frontendIPConfigurations', parameters('loadBalancerName'), variables('frontendIPConfiguration'))]" + }, + "backendAddressPool": { + "id": "[resourceId('Microsoft.Network/loadBalancers/backendAddressPools', parameters('loadBalancerName'), variables('loadBalancerBackendPoolName'))]" + }, + "frontendPort": "[parameters('loadBalancingRules')[copyIndex('loadBalancingRules')].properties.frontendPort]", + "backendPort": "[parameters('loadBalancingRules')[copyIndex('loadBalancingRules')].properties.backendPort]", + "enableFloatingIP": "[parameters('loadBalancingRules')[copyIndex('loadBalancingRules')].properties.enableFloatingIP]", + "idleTimeoutInMinutes": "[parameters('loadBalancingRules')[copyIndex('loadBalancingRules')].properties.idleTimeoutInMinutes]", + "protocol": "[parameters('loadBalancingRules')[copyIndex('loadBalancingRules')].properties.protocol]", + "enableTcpReset": "[parameters('loadBalancingRules')[copyIndex('loadBalancingRules')].properties.enableTcpReset]", + "loadDistribution": "[parameters('loadBalancingRules')[copyIndex('loadBalancingRules')].properties.loadDistribution]", + "disableOutboundSnat": "[parameters('loadBalancingRules')[copyIndex('loadBalancingRules')].properties.disableOutboundSnat]", + "probe": { + "id": "[resourceId('Microsoft.Network/loadBalancers/probes', parameters('loadBalancerName'), parameters('loadBalancingRules')[copyIndex('loadBalancingRules')].properties.probeName)]" + } + } + } + } + ] + }, + "probes": { + "copy": [ + { + "name": "probes", + "count": "[length(parameters('probes'))]", + "input": { + "name": "[parameters('probes')[copyIndex('probes')].name]", + "properties": { + "protocol": "[parameters('probes')[copyIndex('probes')].properties.protocol]", + "port": "[parameters('probes')[copyIndex('probes')].properties.port]", + "requestPath": "[parameters('probes')[copyIndex('probes')].properties.requestPath]", + "intervalInSeconds": "[parameters('probes')[copyIndex('probes')].properties.intervalInSeconds]", + "numberOfProbes": "[parameters('probes')[copyIndex('probes')].properties.numberOfProbes]" + } + } + } + ] + } + }, + "resources": [ + { + "name": "[parameters('loadBalancerName')]", + "type": "Microsoft.Network/loadBalancers", + "apiVersion": "2019-04-01", + "location": "[parameters('location')]", + "tags": {}, + "sku": { + "name": "Standard" + }, + "properties": { + "frontendIPConfigurations": [ + { + "name": "[variables('frontendIPConfiguration')]", + "properties": { + "subnet": { + "id": "[variables('subnetId')]" + }, + "privateIPAddress": "[if(empty(parameters('loadBalancerIPAddress')), json('null'), parameters('loadBalancerIPAddress'))]", + "privateIPAllocationMethod": "[if(empty(parameters('loadBalancerIPAddress')), 'Dynamic', 'Static')]" + } + } + ], + "backendAddressPools": [ + { + "name": "[variables('loadBalancerBackendPoolName')]" + } + ], + "loadBalancingRules": "[variables('loadBalancingRules').loadBalancingRules]", + "probes": "[variables('probes').probes]" + }, + "resources": [] + } + ], + "outputs": { + "loadBalancerName": { + "type": "string", + "value": "[parameters('loadBalancerName')]" + }, + "loadBalancerResourceId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/loadBalancers', parameters('loadBalancerName'))]" + }, + "loadBalancerResourceGroup": { + "type": "string", + "value": "[resourceGroup().name]" + }, + "loadBalancerResourceBackendPoolId": { + "type": "string", + "value": "[concat(resourceId('Microsoft.Network/loadBalancers', parameters('loadBalancerName')), '/backendAddressPools/', variables('loadBalancerBackendPoolName'))]" + } + } +} \ No newline at end of file diff --git a/Modules/SQLServer/2.0/Policy/git_placeholder.md b/Modules/ProximityPlacementGroups/2.0/Policy/git_placeholder.md similarity index 100% rename from Modules/SQLServer/2.0/Policy/git_placeholder.md rename to Modules/ProximityPlacementGroups/2.0/Policy/git_placeholder.md diff --git a/Modules/SQLServer/2.0/RBAC/git_placeholder.md b/Modules/ProximityPlacementGroups/2.0/RBAC/git_placeholder.md similarity index 100% rename from Modules/SQLServer/2.0/RBAC/git_placeholder.md rename to Modules/ProximityPlacementGroups/2.0/RBAC/git_placeholder.md diff --git a/Modules/SQLServer/2.0/Scripts/git_placeholder.md b/Modules/ProximityPlacementGroups/2.0/Scripts/git_placeholder.md similarity index 100% rename from Modules/SQLServer/2.0/Scripts/git_placeholder.md rename to Modules/ProximityPlacementGroups/2.0/Scripts/git_placeholder.md diff --git a/Modules/ProximityPlacementGroups/2.0/Tests/module.tests.ps1 b/Modules/ProximityPlacementGroups/2.0/Tests/module.tests.ps1 new file mode 100644 index 0000000..a66784a --- /dev/null +++ b/Modules/ProximityPlacementGroups/2.0/Tests/module.tests.ps1 @@ -0,0 +1,161 @@ +<# + .NOTES + ============================================================================================== + Copyright(c) Microsoft Corporation. All rights reserved. + + File: module.tests.ps1 + + Purpose: Pester - Test Proximity Placement Groups ARM Templates + + Version: 1.0.0.0 - 1st April 2019 - Azure Virtual Datacenter Development Team + ============================================================================================== + + .SYNOPSIS + This script contains functionality used to test Proximity Placement Groups ARM template synatax. + + .DESCRIPTION + This script contains functionality used to test Proximity Placement Groups ARM template synatax. + + Deployment steps of the script are outlined below. + 1) Test Template File Syntax + 2) Test Parameter File Syntax + 3) Test Template and Parameter File Compactibility +#> + +#Requires -Version 5 + +#region Parameters + +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +$here = Join-Path $here ".." +$template = Split-Path -Leaf $here +$TemplateFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "deploy.json") -Recurse | Select-Object -ExpandProperty Name) ) { + $TemplateFileTestCases += @{ TemplateFile = $File } +} +$ParameterFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "Tests" -AdditionalChildPath @("*parameters.json")) -Recurse -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name) ) { + $ParameterFileTestCases += @{ ParameterFile = Join-Path "Tests" $File } +} +$Modules = @(); +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "deploy.json") ) ) { + $Module = [PSCustomObject]@{ + 'Template' = $null + 'Parameters' = $null + } + $Module.Template = $File.FullName; + $Parameters = @(); + ForEach ( $ParameterFile in (Get-ChildItem (Join-Path "$here" "Tests" -AdditionalChildPath @("*parameters.json")) -Recurse -ErrorAction SilentlyContinue| Select-Object -ExpandProperty Name) ) { + $Parameters += (Join-Path "$here" "Tests" -AdditionalChildPath @("$ParameterFile") ) + } + $Module.Parameters = $Parameters; + $Modules += @{ Module = $Module }; +} + +#endregion + +#region Run Pester Test Script +Describe "Template: $template - Proximity Placement Groups" -Tags Unit { + + Context "Template File Syntax" { + + It "Has a JSON template file" { + (Join-Path "$here" "deploy.json") | Should Exist + } + + It "Converts from JSON and has the expected properties" -TestCases $TemplateFileTestCases { + Param( $TemplateFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters', + 'variables', + 'resources', + 'outputs' | Sort-Object + $templateProperties = (Get-Content (Join-Path "$here" "$TemplateFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateProperties | Should Be $expectedProperties + } + } + + Context "Parameter File Syntax" { + + It "Parameter file does not contains the expected properties" -TestCases $ParameterFileTestCases { + Param( $ParameterFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters' | Sort-Object + Write-Host $ParameterFile + Join-Path "$here" "$ParameterFile" | Write-Host + $templateFileProperties = (Get-Content (Join-Path "$here" "$ParameterFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateFileProperties | Should Be $expectedProperties + } + + } + + Context "Template and Parameter Compactibility" { + + It "Is count of required parameters in template file equal or lesser than count of all parameters in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $requiredParametersInTemplateFile.Count | Should Not BeGreaterThan $allParametersInParametersFile.Count; + } + } + + It "Has all parameters in parameters file existing in template file" -TestCases $Modules { + Param( $Module ) + + $allParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + Write-Host "File analyzed: $Parameter"; + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $result = @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}); + Write-Host "Invalid parameters: $(ConvertTo-Json $result)"; + @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}).Count | Should Be 0; + } + } + + It "Has required parameters in template file existing in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + + @($requiredParametersInTemplateFile | Where-Object {$allParametersInParametersFile -notcontains $_}).Count | Should Be 0; + } + } + } + +} +#endregion \ No newline at end of file diff --git a/Modules/ProximityPlacementGroups/2.0/Tests/parameters.json b/Modules/ProximityPlacementGroups/2.0/Tests/parameters.json new file mode 100644 index 0000000..0e60e76 --- /dev/null +++ b/Modules/ProximityPlacementGroups/2.0/Tests/parameters.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "proximityPlacementGroupsName": { + "value": "foo" + } + } +} \ No newline at end of file diff --git a/Modules/ProximityPlacementGroups/2.0/Tests/policy.tests.ps1 b/Modules/ProximityPlacementGroups/2.0/Tests/policy.tests.ps1 new file mode 100644 index 0000000..59b89af --- /dev/null +++ b/Modules/ProximityPlacementGroups/2.0/Tests/policy.tests.ps1 @@ -0,0 +1,163 @@ +<# + .NOTES + ============================================================================================== + Copyright(c) Microsoft Corporation. All rights reserved. + + File: module.tests.ps1 + + Purpose: Pester - Test Virtual Machine ARM Templates + + Version: 1.0.0.0 - 1st April 2019 - Azure Virtual Datacenter Development Team + ============================================================================================== + + .SYNOPSIS + This script contains functionality used to test Azure Virtual Machine ARM template synatax. + + .DESCRIPTION + This script contains functionality used to test Azure Virtual Machine ARM template synatax. + + Deployment steps of the script are outlined below. + 1) Test Template File Syntax + 2) Test Parameter File Syntax + 3) Test Template and Parameter File Compactibility +#> + +#Requires -Version 5 + +#region Parameters + +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +$here = Join-Path $here ".." +$template = Split-Path -Leaf $here +$TemplateFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "Policy" -AdditionalChildPath @("deploy.json")) -Recurse -ErrorAction SilentlyContinue| Select-Object -ExpandProperty Name) ) { + $TemplateFileTestCases += @{ TemplateFile = $File } +} +$ParameterFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "Policy" -AdditionalChildPath @("*parameters.json")) -Recurse -ErrorAction SilentlyContinue| Select-Object -ExpandProperty Name) ) { + $ParameterFileTestCases += @{ ParameterFile = Join-Path "Policy" $File } +} +$Modules = @(); +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "Policy" -AdditionalChildPath @("deploy.json")) -ErrorAction SilentlyContinue ) ) { + $Module = [PSCustomObject]@{ + 'Template' = $null + 'Parameters' = $null + } + $Module.Template = $File.FullName; + $Parameters = @(); + ForEach ( $ParameterFile in (Get-ChildItem (Join-Path "$here" "Policy" -AdditionalChildPath @("*parameters.json")) -Recurse -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name) ) { + $Parameters += (Join-Path "$here" "Policy" -AdditionalChildPath @("$ParameterFile") ) + } + $Module.Parameters = $Parameters; + $Modules += @{ Module = $Module }; +} + +#endregion + +if ($null -ne $TemplateFileTestCases -and + $TemplateFileTestCases.Count -gt 0) { + + #region Run Pester Test Script + Describe "Template: $template - Virtual Machine" -Tags Unit { + + Context "Template File Syntax" { + + It "Has a JSON template file" { + (Join-Path "$here" "deploy.json") | Should Exist + } + + It "Converts from JSON and has the expected properties" -TestCases $TemplateFileTestCases { + Param( $TemplateFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters', + 'variables', + 'resources', + 'outputs' | Sort-Object + $templateProperties = (Get-Content (Join-Path "$here" "$TemplateFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateProperties | Should Be $expectedProperties + } + } + + Context "Parameter File Syntax" { + + It "Parameter file does not contains the expected properties" -TestCases $ParameterFileTestCases { + Param( $ParameterFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters' | Sort-Object + Write-Host $ParameterFile + Join-Path "$here" "$ParameterFile" | Write-Host + $templateFileProperties = (Get-Content (Join-Path "$here" "$ParameterFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateFileProperties | Should Be $expectedProperties + } + + } + + Context "Template and Parameter Compactibility" { + + It "Is count of required parameters in template file equal or lesser than count of all parameters in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $requiredParametersInTemplateFile.Count | Should Not BeGreaterThan $allParametersInParametersFile.Count; + } + } + + It "Has all parameters in parameters file existing in template file" -TestCases $Modules { + Param( $Module ) + + $allParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + Write-Host "File analyzed: $Parameter"; + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $result = @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}); + Write-Host "Invalid parameters: $(ConvertTo-Json $result)"; + @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}).Count | Should Be 0; + } + } + + It "Has required parameters in template file existing in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + @($requiredParametersInTemplateFile| Where-Object {$allParametersInParametersFile -notcontains $_}).Count | Should Be 0; + } + } + } + + } + #endregion +} \ No newline at end of file diff --git a/Modules/ProximityPlacementGroups/2.0/Tests/rbac.tests.ps1 b/Modules/ProximityPlacementGroups/2.0/Tests/rbac.tests.ps1 new file mode 100644 index 0000000..0a22928 --- /dev/null +++ b/Modules/ProximityPlacementGroups/2.0/Tests/rbac.tests.ps1 @@ -0,0 +1,163 @@ +<# + .NOTES + ============================================================================================== + Copyright(c) Microsoft Corporation. All rights reserved. + + File: module.tests.ps1 + + Purpose: Pester - Test Virtual Machine ARM Templates + + Version: 1.0.0.0 - 1st April 2019 - Azure Virtual Datacenter Development Team + ============================================================================================== + + .SYNOPSIS + This script contains functionality used to test Azure Virtual Machine ARM template synatax. + + .DESCRIPTION + This script contains functionality used to test Azure Virtual Machine ARM template synatax. + + Deployment steps of the script are outlined below. + 1) Test Template File Syntax + 2) Test Parameter File Syntax + 3) Test Template and Parameter File Compactibility +#> + +#Requires -Version 5 + +#region Parameters + +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +$here = Join-Path $here ".." +$template = Split-Path -Leaf $here +$TemplateFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "RBAC" -AdditionalChildPath @("deploy.json")) -ErrorAction SilentlyContinue -Recurse | Select-Object -ExpandProperty Name) ) { + $TemplateFileTestCases += @{ TemplateFile = $File } +} +$ParameterFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "RBAC" -AdditionalChildPath @("*parameters.json")) -ErrorAction SilentlyContinue -Recurse | Select-Object -ExpandProperty Name) ) { + $ParameterFileTestCases += @{ ParameterFile = Join-Path "RBAC" $File } +} +$Modules = @(); +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "RBAC" -AdditionalChildPath @("deploy.json")) -ErrorAction SilentlyContinue ) ) { + $Module = [PSCustomObject]@{ + 'Template' = $null + 'Parameters' = $null + } + $Module.Template = $File.FullName; + $Parameters = @(); + ForEach ( $ParameterFile in (Get-ChildItem (Join-Path "$here" "RBAC" -AdditionalChildPath @("*parameters.json")) -Recurse | Select-Object -ExpandProperty Name) ) { + $Parameters += (Join-Path "$here" "RBAC" -AdditionalChildPath @("$ParameterFile") ) + } + $Module.Parameters = $Parameters; + $Modules += @{ Module = $Module }; +} + +#endregion + +if ($null -ne $TemplateFileTestCases -and + $TemplateFileTestCases.Count -gt 0) { + + #region Run Pester Test Script + Describe "Template: $template - Virtual Machine" -Tags Unit { + + Context "Template File Syntax" { + + It "Has a JSON template file" -TestCases $TemplateFileTestCases { + (Join-Path "$here" "deploy.json") | Should Exist + } + + It "Converts from JSON and has the expected properties" -TestCases $TemplateFileTestCases { + Param( $TemplateFile ) + Write-Host "TF: $TemplateFile" + $expectedProperties = '$schema', + 'contentVersion', + 'parameters', + 'variables', + 'resources', + 'outputs' | Sort-Object + $templateProperties = (Get-Content (Join-Path "$here" "$TemplateFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateProperties | Should Be $expectedProperties + } + } + + Context "Parameter File Syntax" { + + It "Parameter file does not contains the expected properties" -TestCases $ParameterFileTestCases { + Param( $ParameterFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters' | Sort-Object + Write-Host $ParameterFile + Join-Path "$here" "$ParameterFile" | Write-Host + $templateFileProperties = (Get-Content (Join-Path "$here" "$ParameterFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateFileProperties | Should Be $expectedProperties + } + } + + Context "Template and Parameter Compactibility" { + + It "Is count of required parameters in template file equal or lesser than count of all parameters in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $requiredParametersInTemplateFile.Count | Should Not BeGreaterThan $allParametersInParametersFile.Count; + } + } + + It "Has all parameters in parameters file existing in template file" -TestCases $Modules { + Param( $Module ) + + $allParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + Write-Host "File analyzed: $Parameter"; + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $result = @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}); + Write-Host "Invalid parameters: $(ConvertTo-Json $result)"; + @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}).Count | Should Be 0; + } + } + + It "Has required parameters in template file existing in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + @($requiredParametersInTemplateFile| Where-Object {$allParametersInParametersFile -notcontains $_}).Count | Should Be 0; + } + } + } + + } + #endregion +} \ No newline at end of file diff --git a/Modules/ProximityPlacementGroups/2.0/deploy.json b/Modules/ProximityPlacementGroups/2.0/deploy.json new file mode 100644 index 0000000..1f47b2d --- /dev/null +++ b/Modules/ProximityPlacementGroups/2.0/deploy.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "proximityPlacementGroupsName": { + "type": "string", + "metadata": { + "description": "Required. The Proximity Placement Groups Name" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + } + }, + "variables": {}, + "resources": [ + { + "apiVersion": "2018-04-01", + "type": "Microsoft.Compute/proximityPlacementGroups", + "name": "[parameters('proximityPlacementGroupsName')]", + "location": "[parameters('location')]" + } + ], + "outputs": { + "proximityPlacementGroupsName": { + "type": "string", + "value": "[parameters('proximityPlacementGroupsName')]" + }, + "proximityPlacementGroupsResourceId": { + "type": "string", + "value": "[resourceId('Microsoft.Compute/proximityPlacementGroups', parameters('proximityPlacementGroupsName'))]" + }, + "proximityPlacementGroupsResourceGroup": { + "type": "string", + "value": "[resourceGroup().name]" + } + } +} \ No newline at end of file diff --git a/Modules/SQLServer/2.0/sql.server.deploy.json b/Modules/SQLServer/2.0/sql.server.deploy.json deleted file mode 100644 index 6284fe8..0000000 --- a/Modules/SQLServer/2.0/sql.server.deploy.json +++ /dev/null @@ -1,223 +0,0 @@ -{ - "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "SQLServerName": { - "type": "string", - "metadata": { - "description": "Azure SQL Server name" - } - }, - "SQLserverLocation": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "The location of the SQL server." - } - }, - "SQLServerAzureADAdmin": { - "type": "string", - "metadata": { - "description": "The account name to use for the SQL server administrator." - } - }, - "SQLServerAzureADAdminSID": { - "type": "string" - }, - - "SQLServerAdminPassword": { - "type": "securestring", - "metadata": { - "description": "SQL.admin password" - } - }, - "SQLServerFirewallAllowedPublicIPs": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "List of IPv4 IPs or ranges to allow access from the internet to the SQL Server" - } - }, - "SQLServerFirewallAllowedSubnets": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Information of existing virtual networks and subnets allowed to access the SQL Server" - } - }, - "SQLServerMicrosoftServicesBypassFirewall": { - "type": "bool", - "defaultValue": true - }, - "SecurityLogStorageAccountEndpoint": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Endpoint of the storage account where Audit Logs will be stored", - "AllEnvironmentsAutoAssignValueFromVariablesKeyVaultSecretName": "SecurityLogStorageAccountEndpoint", - "ConcatParameterAfterValue": "parameters('SQLserverLocation')" - } - }, - "SecurityLogStorageAccountAccessKey": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Access Key to be used in the Audit Logs storage account", - "AllEnvironmentsAutoAssignValueFromSecretsKeyVaultSecretName": "Security-Storage-Account-PrimaryKey", - "ConcatParameterAfterValue": "parameters('SQLserverLocation')" - } - } - }, - "variables": { - "SQLServerFirewallAllowedIPsArrayCount": "[length(parameters('SQLServerFirewallAllowedPublicIPs'))]", - "SQLServerFirewallAllowedIPsArrayEmpty": "[equals(variables('SQLServerFirewallAllowedIPsArrayCount'),0)]", - "SQLServerFirewallAllowedSubnetsArrayCount": "[length(parameters('SQLServerFirewallAllowedSubnets'))]", - "SQLServerFirewallAllowedSubnetsArrayEmpty": "[equals(variables('SQLServerFirewallAllowedSubnetsArrayCount'),0)]", - "EmptyArray": [] - }, - "resources": [ - { - "name": "[parameters('SQLServerName')]", - "type": "Microsoft.Sql/servers", - "location": "[parameters('SQLserverLocation')]", - "apiVersion": "2015-05-01-preview", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "administratorLogin": "admin.SQL", - "administratorLoginPassword": "[parameters('SQLServerAdminPassword')]" - }, - "resources": [ - { - "condition" : "[not(equals(parameters('SecurityLogStorageAccountEndpoint'),''))]", - "apiVersion": "2017-03-01-preview", - "type": "auditingSettings", - "name": "[concat(parameters('SQLServerName'),'-auditsettings')]", - "location": "[parameters('SQLserverLocation')]", - "dependsOn": [ - "[concat('Microsoft.Sql/servers/', parameters('SQLServerName'))]" - ], - "properties": { - "state": "Enabled", - "storageEndpoint": "[parameters('SecurityLogStorageAccountEndpoint')]", - "storageAccountAccessKey": "[parameters('SecurityLogStorageAccountAccessKey')]", - "retentionDays": 365, - "auditActionsAndGroups": [ - "BATCH_STARTED_GROUP", - "BATCH_COMPLETED_GROUP", - "APPLICATION_ROLE_CHANGE_PASSWORD_GROUP", - "BACKUP_RESTORE_GROUP", - "DATABASE_LOGOUT_GROUP", - "DATABASE_OBJECT_CHANGE_GROUP", - "DATABASE_OBJECT_OWNERSHIP_CHANGE_GROUP", - "DATABASE_OBJECT_PERMISSION_CHANGE_GROUP", - "FAILED_DATABASE_AUTHENTICATION_GROUP", - "SCHEMA_OBJECT_ACCESS_GROUP", - "SCHEMA_OBJECT_CHANGE_GROUP", - "SCHEMA_OBJECT_OWNERSHIP_CHANGE_GROUP", - "SCHEMA_OBJECT_PERMISSION_CHANGE_GROUP", - "SUCCESSFUL_DATABASE_AUTHENTICATION_GROUP", - "USER_CHANGE_PASSWORD_GROUP", - "DATABASE_PERMISSION_CHANGE_GROUP", - "DATABASE_PRINCIPAL_CHANGE_GROUP", - "DATABASE_PRINCIPAL_IMPERSONATION_GROUP", - "DATABASE_ROLE_MEMBER_CHANGE_GROUP", - "DATABASE_OPERATION_GROUP" - ] - } - }, - { - "condition" : "[not(equals(parameters('SecurityLogStorageAccountEndpoint'),''))]", - "apiVersion": "2015-05-01-preview", - "type": "securityAlertPolicies", - "name": "[concat(parameters('SQLServerName'),'-securityAlertsettings')]", - "dependsOn": [ - "[concat('Microsoft.Sql/servers/', parameters('SQLServerName'))]", - "[concat('Microsoft.Sql/servers/', parameters('SQLServerName'),'/auditingSettings/',parameters('SQLServerName'),'-auditsettings')]" - ], - "properties": { - "state": "Enabled", - "emailAccountAdmins": "Enabled", - "retentionDays": "365", - "storageEndpoint": "[parameters('SecurityLogStorageAccountEndpoint')]", - "storageAccountAccessKey": "[parameters('SecurityLogStorageAccountAccessKey')]" - } - }, - { - "condition" : "[parameters('SQLServerMicrosoftServicesBypassFirewall')]", - "type": "Microsoft.Sql/servers/firewallrules", - "name": "[concat(parameters('SQLServerName'), '/', 'AllowAllWindowsAzureIps')]", - "apiVersion": "2015-05-01-preview", - "properties": { - "startIpAddress": "0.0.0.0", - "endIpAddress": "0.0.0.0" - }, - "dependsOn": [ - "[parameters('SQLServerName')]" - ] - }, - { - "type": "Microsoft.Sql/servers/administrators", - "apiVersion": "2014-04-01-preview", - "name": "[concat(parameters('SQLServerName'), '/', 'ActiveDirectory')]", - "properties": { - "administratorType": "ActiveDirectory", - "login": "[parameters('SQLServerAzureADAdmin')]", - "sid": "[parameters('SQLServerAzureADAdminSID')]", - "tenantId": "[subscription().tenantId]" - }, - "dependsOn": [ - "[parameters('SQLServerName')]" - ] - } - ] - }, - { - "condition": "[not(variables('SQLServerFirewallAllowedSubnetsArrayEmpty'))]", - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Sql/servers/virtualNetworkRules", - "name": "[concat(parameters('SQLServerName'), '/', 'SubnetRule', copyIndex('SQLServerFirewallAllowedSubnets'))]", - "scale": null, - "properties": { - "virtualNetworkSubnetId": "[if(variables('SQLServerFirewallAllowedSubnetsArrayEmpty'),' ',concat('/subscriptions/',parameters('SQLServerFirewallAllowedSubnets')[copyIndex('SQLServerFirewallAllowedSubnets')].VNetSubscriptionId,'/resourceGroups/',parameters('SQLServerFirewallAllowedSubnets')[copyIndex('SQLServerFirewallAllowedSubnets')].VNetResourceGroupName,'/providers/Microsoft.Network/virtualnetworks/',parameters('SQLServerFirewallAllowedSubnets')[copyIndex('SQLServerFirewallAllowedSubnets')].VNetName,'/subnets/',parameters('SQLServerFirewallAllowedSubnets')[copyIndex('SQLServerFirewallAllowedSubnets')].VNetSubnetName))]", - "ignoreMissingVnetServiceEndpoint": false - }, - "dependsOn": [ - "[resourceId('Microsoft.Sql/servers', parameters('SQLServerName'))]", - "[resourceId('Microsoft.Sql/servers/administrators', parameters('SQLServerName'), 'ActiveDirectory')]", - "[concat('Microsoft.Sql/servers/', parameters('SQLServerName'),'/auditingSettings/',parameters('SQLServerName'),'-auditsettings')]", - "[concat('Microsoft.Sql/servers/', parameters('SQLServerName'),'/securityAlertPolicies/',parameters('SQLServerName'),'-securityAlertsettings')]", - "[resourceId('Microsoft.Sql/servers/firewallrules', parameters('SQLServerName'), 'AllowAllWindowsAzureIps')]" - - ], - "copy": - { - "name": "SQLServerFirewallAllowedSubnets", - "count": "[if(variables('SQLServerFirewallAllowedSubnetsArrayEmpty'),1,variables('SQLServerFirewallAllowedSubnetsArrayCount'))]" - } - }, - { - "condition": "[not(variables('SQLServerFirewallAllowedIPsArrayEmpty'))]", - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Sql/servers/firewallRules", - "name": "[concat(parameters('SQLServerName'), '/', 'FirewallRule', copyIndex('SQLServerFirewallAllowedIPAddresses'))]", - "properties": { - "startIpAddress": "[if(variables('SQLServerFirewallAllowedIPsArrayEmpty'),' ', parameters('SQLServerFirewallAllowedPublicIPs')[copyIndex('SQLServerFirewallAllowedIPAddresses')].start)]", - "endIpAddress": "[if(variables('SQLServerFirewallAllowedIPsArrayEmpty'),' ', parameters('SQLServerFirewallAllowedPublicIPs')[copyIndex('SQLServerFirewallAllowedIPAddresses')].end)]" - }, - "dependsOn": [ - "[resourceId('Microsoft.Sql/servers', parameters('SQLServerName'))]", - "[resourceId('Microsoft.Sql/servers/administrators', parameters('SQLServerName'), 'ActiveDirectory')]", - "[concat('Microsoft.Sql/servers/', parameters('SQLServerName'),'/auditingSettings/',parameters('SQLServerName'),'-auditsettings')]", - "[concat('Microsoft.Sql/servers/', parameters('SQLServerName'),'/securityAlertPolicies/',parameters('SQLServerName'),'-securityAlertsettings')]", - "[resourceId('Microsoft.Sql/servers/firewallrules', parameters('SQLServerName'), 'AllowAllWindowsAzureIps')]" - ], - "copy": { - "name": "SQLServerFirewallAllowedIPAddresses", - "count": "[if(variables('SQLServerFirewallAllowedIPsArrayEmpty'),1,variables('SQLServerFirewallAllowedIPsArrayCount'))]" - } - } - ], - "outputs": {} -} diff --git a/Modules/SQLServer/2.0/sql.server.parameters.json b/Modules/SQLServer/2.0/sql.server.parameters.json deleted file mode 100644 index b463546..0000000 --- a/Modules/SQLServer/2.0/sql.server.parameters.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "SQLServerName": { - "value": null - }, - "SQLServerAzureADAdmin": { - "value": null - }, - "SQLServerAzureADAdminSID": { - "value": null - }, - "SQLServerAdminPassword": { - "value": null - }, - "SQLServerFirewallAllowedPublicIPs": { - "value": [] - }, - "SQLServerFirewallAllowedSubnets": { - "value": [] - }, - "SecurityLogStorageAccountEndpoint": { - "value": null - }, - "SecurityLogStorageAccountAccessKey": { - "value": null - } - } -} \ No newline at end of file diff --git a/Modules/SQLServer/2.0/Pipeline/sql.server.azuredevops.ci.yaml b/Modules/SQLServerAlwaysOn/2.0/Pipeline/sql.server.azuredevops.ci.yaml similarity index 100% rename from Modules/SQLServer/2.0/Pipeline/sql.server.azuredevops.ci.yaml rename to Modules/SQLServerAlwaysOn/2.0/Pipeline/sql.server.azuredevops.ci.yaml diff --git a/Modules/SQLServer/2.0/Tests/git_placeholder.md b/Modules/SQLServerAlwaysOn/2.0/Policy/git_placeholder.md similarity index 100% rename from Modules/SQLServer/2.0/Tests/git_placeholder.md rename to Modules/SQLServerAlwaysOn/2.0/Policy/git_placeholder.md diff --git a/Modules/SQLServerAlwaysOn/2.0/RBAC/git_placeholder.md b/Modules/SQLServerAlwaysOn/2.0/RBAC/git_placeholder.md new file mode 100644 index 0000000..e69de29 diff --git a/Modules/SQLServerAlwaysOn/2.0/Scripts/git_placeholder.md b/Modules/SQLServerAlwaysOn/2.0/Scripts/git_placeholder.md new file mode 100644 index 0000000..e69de29 diff --git a/Modules/Jumpbox/2.0/Tests/module.tests.ps1 b/Modules/SQLServerAlwaysOn/2.0/Tests/module.tests.ps1 similarity index 65% rename from Modules/Jumpbox/2.0/Tests/module.tests.ps1 rename to Modules/SQLServerAlwaysOn/2.0/Tests/module.tests.ps1 index c26ad72..1b2ca4e 100644 --- a/Modules/Jumpbox/2.0/Tests/module.tests.ps1 +++ b/Modules/SQLServerAlwaysOn/2.0/Tests/module.tests.ps1 @@ -5,7 +5,7 @@ File: module.tests.ps1 - Purpose: Pester - Test Storage Account ARM Templates + Purpose: Pester - Test Virtual Machine ARM Templates Version: 1.0.0.0 - 1st April 2019 - Azure Virtual Datacenter Development Team ============================================================================================== @@ -34,25 +34,28 @@ ForEach ( $File in (Get-ChildItem (Join-Path "$here" "deploy.json") -Recurse | S $TemplateFileTestCases += @{ TemplateFile = $File } } $ParameterFileTestCases = @() -ForEach ( $File in (Get-ChildItem (Join-Path "$here" "parameters.json") -Recurse | Select-Object -ExpandProperty Name) ) { - $ParameterFileTestCases += @{ ParameterFile = $File } +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "Tests" -AdditionalChildPath @("*parameters.json")) -Recurse -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name) ) { + $ParameterFileTestCases += @{ ParameterFile = Join-Path "Tests" $File } } $Modules = @(); -ForEach ( $File in (Get-ChildItem (Join-Path "$here" "deploy.json") -Recurse ) ) { +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "deploy.json") ) ) { $Module = [PSCustomObject]@{ 'Template' = $null 'Parameters' = $null } $Module.Template = $File.FullName; - $Module.Parameters = (Get-ChildItem -Path (Join-Path $($File.DirectoryName) "parameters.json")).FullName; + $Parameters = @(); + ForEach ( $ParameterFile in (Get-ChildItem (Join-Path "$here" "Tests" -AdditionalChildPath @("*parameters.json")) -Recurse -ErrorAction SilentlyContinue| Select-Object -ExpandProperty Name) ) { + $Parameters += (Join-Path "$here" "Tests" -AdditionalChildPath @("$ParameterFile") ) + } + $Module.Parameters = $Parameters; $Modules += @{ Module = $Module }; - } #endregion #region Run Pester Test Script -Describe "Template: $template - Storage Accounts" -Tags Unit { +Describe "Template: $template - Virtual Machine" -Tags Unit { Context "Template File Syntax" { @@ -79,15 +82,13 @@ Describe "Template: $template - Storage Accounts" -Tags Unit { Context "Parameter File Syntax" { - It "Has environment parameters file" { - (Join-Path "$here" "parameters.json") | Should Exist - } - It "Parameter file does not contains the expected properties" -TestCases $ParameterFileTestCases { Param( $ParameterFile ) $expectedProperties = '$schema', 'contentVersion', - 'parameters' | Sort-Object + 'parameters' | Sort-Object + Write-Host $ParameterFile + Join-Path "$here" "$ParameterFile" | Write-Host $templateFileProperties = (Get-Content (Join-Path "$here" "$ParameterFile") ` | ConvertFrom-Json -ErrorAction SilentlyContinue) ` | Get-Member -MemberType NoteProperty ` @@ -98,7 +99,6 @@ Describe "Template: $template - Storage Accounts" -Tags Unit { } - Context "Template and Parameter Compactibility" { It "Is count of required parameters in template file equal or lesser than count of all parameters in parameters file" -TestCases $Modules { @@ -109,12 +109,13 @@ Describe "Template: $template - Storage Accounts" -Tags Unit { | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` | Sort-Object -Property Name ` | ForEach-Object Name - $allParametersInParametersFile = (Get-Content "$($Module.Parameters)" ` + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` | Sort-Object -Property Name ` | ForEach-Object Name - $requiredParametersInTemplateFile.Count | Should Not BeGreaterThan $allParametersInParametersFile.Count; - + $requiredParametersInTemplateFile.Count | Should Not BeGreaterThan $allParametersInParametersFile.Count; + } } It "Has all parameters in parameters file existing in template file" -TestCases $Modules { @@ -124,11 +125,20 @@ Describe "Template: $template - Storage Accounts" -Tags Unit { | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` | Sort-Object -Property Name ` | ForEach-Object Name - $allParametersInParametersFile = (Get-Content "$($Module.Parameters)" ` - | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` - | Sort-Object -Property Name ` - | ForEach-Object Name - @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}).Count | Should Be 0; + ForEach ( $Parameter in $Module.Parameters ) { + Write-Host "File analyzed: $Parameter"; + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $result = @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}); + + if ($result.Count -gt 0) { + Write-Host "Invalid parameters: $(ConvertTo-Json $result)" + } + + @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}).Count | Should Be 0; + } } It "Has required parameters in template file existing in parameters file" -TestCases $Modules { @@ -139,11 +149,22 @@ Describe "Template: $template - Storage Accounts" -Tags Unit { | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` | Sort-Object -Property Name ` | ForEach-Object Name - $allParametersInParametersFile = (Get-Content "$($Module.Parameters)" ` - | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` - | Sort-Object -Property Name ` - | ForEach-Object Name - @($requiredParametersInTemplateFile| Where-Object {$allParametersInParametersFile -notcontains $_}).Count | Should Be 0; + ForEach ( $Parameter in $Module.Parameters ) { + + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + + $invalidParameters = ` + $requiredParametersInTemplateFile | Where-Object {$allParametersInParametersFile -notcontains $_} + + if ($invalidParameters.Count -gt 0) { + Write-Host "Parameters not found: $(ConvertTo-Json $invalidParameters)" + } + + $invalidParameters.Count | Should Be 0 + } } } diff --git a/Modules/SQLServerAlwaysOn/2.0/Tests/parameters.json b/Modules/SQLServerAlwaysOn/2.0/Tests/parameters.json new file mode 100644 index 0000000..456c9db --- /dev/null +++ b/Modules/SQLServerAlwaysOn/2.0/Tests/parameters.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "virtualMachineName": { + "value": "string" + }, + "adminUsername": { + "value": "securestring" + }, + "adminPassword": { + "value": "securestring" + }, + "clusterName": { + "value": "securestring" + }, + "artifactsStorageAccountName": { + "value": "securestring" + }, + "artifactsStorageAccountKey": { + "value": "securestring" + }, + "artifactsStorageAccountSasKey": { + "value": "securestring" + }, + "cloudWitnessStorageAccountName": { + "value": "securestring" + }, + "cloudWitnessStorageAccountKey": { + "value": "securestring" + }, + "sqlServerILB_IPAddress": { + "value": "securestring" + }, + "domainName": { + "value": "securestring" + }, + "domainAdminUsername": { + "value": "securestring" + }, + "domainAdminPassword": { + "value": "securestring" + } + } +} \ No newline at end of file diff --git a/Modules/SQLServerAlwaysOn/2.0/Tests/policy.tests.ps1 b/Modules/SQLServerAlwaysOn/2.0/Tests/policy.tests.ps1 new file mode 100644 index 0000000..59b89af --- /dev/null +++ b/Modules/SQLServerAlwaysOn/2.0/Tests/policy.tests.ps1 @@ -0,0 +1,163 @@ +<# + .NOTES + ============================================================================================== + Copyright(c) Microsoft Corporation. All rights reserved. + + File: module.tests.ps1 + + Purpose: Pester - Test Virtual Machine ARM Templates + + Version: 1.0.0.0 - 1st April 2019 - Azure Virtual Datacenter Development Team + ============================================================================================== + + .SYNOPSIS + This script contains functionality used to test Azure Virtual Machine ARM template synatax. + + .DESCRIPTION + This script contains functionality used to test Azure Virtual Machine ARM template synatax. + + Deployment steps of the script are outlined below. + 1) Test Template File Syntax + 2) Test Parameter File Syntax + 3) Test Template and Parameter File Compactibility +#> + +#Requires -Version 5 + +#region Parameters + +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +$here = Join-Path $here ".." +$template = Split-Path -Leaf $here +$TemplateFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "Policy" -AdditionalChildPath @("deploy.json")) -Recurse -ErrorAction SilentlyContinue| Select-Object -ExpandProperty Name) ) { + $TemplateFileTestCases += @{ TemplateFile = $File } +} +$ParameterFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "Policy" -AdditionalChildPath @("*parameters.json")) -Recurse -ErrorAction SilentlyContinue| Select-Object -ExpandProperty Name) ) { + $ParameterFileTestCases += @{ ParameterFile = Join-Path "Policy" $File } +} +$Modules = @(); +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "Policy" -AdditionalChildPath @("deploy.json")) -ErrorAction SilentlyContinue ) ) { + $Module = [PSCustomObject]@{ + 'Template' = $null + 'Parameters' = $null + } + $Module.Template = $File.FullName; + $Parameters = @(); + ForEach ( $ParameterFile in (Get-ChildItem (Join-Path "$here" "Policy" -AdditionalChildPath @("*parameters.json")) -Recurse -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name) ) { + $Parameters += (Join-Path "$here" "Policy" -AdditionalChildPath @("$ParameterFile") ) + } + $Module.Parameters = $Parameters; + $Modules += @{ Module = $Module }; +} + +#endregion + +if ($null -ne $TemplateFileTestCases -and + $TemplateFileTestCases.Count -gt 0) { + + #region Run Pester Test Script + Describe "Template: $template - Virtual Machine" -Tags Unit { + + Context "Template File Syntax" { + + It "Has a JSON template file" { + (Join-Path "$here" "deploy.json") | Should Exist + } + + It "Converts from JSON and has the expected properties" -TestCases $TemplateFileTestCases { + Param( $TemplateFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters', + 'variables', + 'resources', + 'outputs' | Sort-Object + $templateProperties = (Get-Content (Join-Path "$here" "$TemplateFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateProperties | Should Be $expectedProperties + } + } + + Context "Parameter File Syntax" { + + It "Parameter file does not contains the expected properties" -TestCases $ParameterFileTestCases { + Param( $ParameterFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters' | Sort-Object + Write-Host $ParameterFile + Join-Path "$here" "$ParameterFile" | Write-Host + $templateFileProperties = (Get-Content (Join-Path "$here" "$ParameterFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateFileProperties | Should Be $expectedProperties + } + + } + + Context "Template and Parameter Compactibility" { + + It "Is count of required parameters in template file equal or lesser than count of all parameters in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $requiredParametersInTemplateFile.Count | Should Not BeGreaterThan $allParametersInParametersFile.Count; + } + } + + It "Has all parameters in parameters file existing in template file" -TestCases $Modules { + Param( $Module ) + + $allParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + Write-Host "File analyzed: $Parameter"; + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $result = @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}); + Write-Host "Invalid parameters: $(ConvertTo-Json $result)"; + @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}).Count | Should Be 0; + } + } + + It "Has required parameters in template file existing in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + @($requiredParametersInTemplateFile| Where-Object {$allParametersInParametersFile -notcontains $_}).Count | Should Be 0; + } + } + } + + } + #endregion +} \ No newline at end of file diff --git a/Modules/SQLServerAlwaysOn/2.0/Tests/rbac.tests.ps1 b/Modules/SQLServerAlwaysOn/2.0/Tests/rbac.tests.ps1 new file mode 100644 index 0000000..0a22928 --- /dev/null +++ b/Modules/SQLServerAlwaysOn/2.0/Tests/rbac.tests.ps1 @@ -0,0 +1,163 @@ +<# + .NOTES + ============================================================================================== + Copyright(c) Microsoft Corporation. All rights reserved. + + File: module.tests.ps1 + + Purpose: Pester - Test Virtual Machine ARM Templates + + Version: 1.0.0.0 - 1st April 2019 - Azure Virtual Datacenter Development Team + ============================================================================================== + + .SYNOPSIS + This script contains functionality used to test Azure Virtual Machine ARM template synatax. + + .DESCRIPTION + This script contains functionality used to test Azure Virtual Machine ARM template synatax. + + Deployment steps of the script are outlined below. + 1) Test Template File Syntax + 2) Test Parameter File Syntax + 3) Test Template and Parameter File Compactibility +#> + +#Requires -Version 5 + +#region Parameters + +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +$here = Join-Path $here ".." +$template = Split-Path -Leaf $here +$TemplateFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "RBAC" -AdditionalChildPath @("deploy.json")) -ErrorAction SilentlyContinue -Recurse | Select-Object -ExpandProperty Name) ) { + $TemplateFileTestCases += @{ TemplateFile = $File } +} +$ParameterFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "RBAC" -AdditionalChildPath @("*parameters.json")) -ErrorAction SilentlyContinue -Recurse | Select-Object -ExpandProperty Name) ) { + $ParameterFileTestCases += @{ ParameterFile = Join-Path "RBAC" $File } +} +$Modules = @(); +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "RBAC" -AdditionalChildPath @("deploy.json")) -ErrorAction SilentlyContinue ) ) { + $Module = [PSCustomObject]@{ + 'Template' = $null + 'Parameters' = $null + } + $Module.Template = $File.FullName; + $Parameters = @(); + ForEach ( $ParameterFile in (Get-ChildItem (Join-Path "$here" "RBAC" -AdditionalChildPath @("*parameters.json")) -Recurse | Select-Object -ExpandProperty Name) ) { + $Parameters += (Join-Path "$here" "RBAC" -AdditionalChildPath @("$ParameterFile") ) + } + $Module.Parameters = $Parameters; + $Modules += @{ Module = $Module }; +} + +#endregion + +if ($null -ne $TemplateFileTestCases -and + $TemplateFileTestCases.Count -gt 0) { + + #region Run Pester Test Script + Describe "Template: $template - Virtual Machine" -Tags Unit { + + Context "Template File Syntax" { + + It "Has a JSON template file" -TestCases $TemplateFileTestCases { + (Join-Path "$here" "deploy.json") | Should Exist + } + + It "Converts from JSON and has the expected properties" -TestCases $TemplateFileTestCases { + Param( $TemplateFile ) + Write-Host "TF: $TemplateFile" + $expectedProperties = '$schema', + 'contentVersion', + 'parameters', + 'variables', + 'resources', + 'outputs' | Sort-Object + $templateProperties = (Get-Content (Join-Path "$here" "$TemplateFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateProperties | Should Be $expectedProperties + } + } + + Context "Parameter File Syntax" { + + It "Parameter file does not contains the expected properties" -TestCases $ParameterFileTestCases { + Param( $ParameterFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters' | Sort-Object + Write-Host $ParameterFile + Join-Path "$here" "$ParameterFile" | Write-Host + $templateFileProperties = (Get-Content (Join-Path "$here" "$ParameterFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateFileProperties | Should Be $expectedProperties + } + } + + Context "Template and Parameter Compactibility" { + + It "Is count of required parameters in template file equal or lesser than count of all parameters in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $requiredParametersInTemplateFile.Count | Should Not BeGreaterThan $allParametersInParametersFile.Count; + } + } + + It "Has all parameters in parameters file existing in template file" -TestCases $Modules { + Param( $Module ) + + $allParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + Write-Host "File analyzed: $Parameter"; + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $result = @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}); + Write-Host "Invalid parameters: $(ConvertTo-Json $result)"; + @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}).Count | Should Be 0; + } + } + + It "Has required parameters in template file existing in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + @($requiredParametersInTemplateFile| Where-Object {$allParametersInParametersFile -notcontains $_}).Count | Should Be 0; + } + } + } + + } + #endregion +} \ No newline at end of file diff --git a/Modules/SQLServerAlwaysOn/2.0/deploy.json b/Modules/SQLServerAlwaysOn/2.0/deploy.json new file mode 100644 index 0000000..39eb66d --- /dev/null +++ b/Modules/SQLServerAlwaysOn/2.0/deploy.json @@ -0,0 +1,414 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "virtualMachineName": { + "type": "string", + "minLength": 1, + "maxLength": 13, + "metadata": { + "description": "Required. Name for the VMs" + } + }, + "virtualMachineCount": { + "type": "int", + "defaultValue": 1, + "metadata": { + "description": "Optional. Number of VMs to create" + } + }, + "virtualMachineOffset": { + "type": "int", + "defaultValue": 1, + "metadata": { + "description": "Optional. This value will be used as start VM count. Specify a value if you want to create VMs starting at a specific number, this is useful when you want to append more VMs." + } + }, + "adminUsername": { + "type": "securestring", + "metadata": { + "description": "Required. Administrator username" + } + }, + "adminPassword": { + "type": "securestring", + "metadata": { + "description": "Required. When specifying a Windows Virtual Machine, this value should be passed" + } + }, + "clusterName": { + "type": "string", + "metadata": { + "description": "Required. SQL Server AlwaysOn Cluster name" + } + }, + "artifactsStorageAccountName": { + "type": "securestring", + "metadata": { + "description": "Required. Default storage account name. Storage account that contains output parameters and common scripts" + } + }, + "artifactsStorageAccountKey": { + "type": "securestring", + "metadata": { + "description": "Required. Default storage account Key. Storage account that contains output parameters and common scripts" + } + }, + "artifactsStorageAccountSasKey": { + "type": "securestring", + "metadata": { + "description": "Required. Shared Access Signature Key used to download custom scripts" + } + }, + "cloudWitnessStorageAccountName": { + "type": "securestring", + "metadata": { + "description": "Required. Default storage account name. Storage account that contains output parameters and common scripts" + } + }, + "cloudWitnessStorageAccountKey": { + "type": "securestring", + "metadata": { + "description": "Required. Default storage account Key. Storage account that contains output parameters and common scripts" + } + }, + "sqlServerILB_IPAddress": { + "type": "string", + "metadata": { + "description": "Required. SQL Server Internal Load Balancer IP Address" + } + }, + "domainName": { + "type": "securestring", + "metadata": { + "description": "Required. AD domain name. If joinToDomain is set to true, this value becomes required." + } + }, + "domainAdminUsername": { + "type": "securestring", + "metadata": { + "description": "Required. Domain user that has privileges to join a VM into a Domain. If joinToDomain is set to true, this value becomes required." + } + }, + "domainAdminPassword": { + "type": "securestring", + "metadata": { + "description": "Required. Domain user that has privileges to join a VM into a Domain. If joinToDomain is set to true, this value becomes required." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + } + }, + "variables": { + "DSCExtensionName": "DSCExtension", + "PwshExtensionName": "PwshExtension", + "artifactsStorageAccountSasToken": "[concat('?', parameters('artifactsStorageAccountSasKey'))]" + }, + "resources": [ + { + "apiVersion": "2017-03-30", + "type": "Microsoft.Compute/virtualMachines", + "name": "[concat(parameters('virtualMachineName'), copyIndex(parameters('virtualMachineOffset')))]", + "location": "[parameters('location')]", + "copy": { + "name": "vmInstallDSCModulesLoop", + "count": "[parameters('virtualMachineCount')]" + }, + "resources": [ + { + "type": "extensions", + "name": "[variables('PwshExtensionName')]", + "apiVersion": "2017-03-30", + "location": "[parameters('location')]", + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('virtualMachineName'), copyIndex(parameters('virtualMachineOffset'))))]" + ], + "properties": { + "publisher": "Microsoft.Compute", + "type": "CustomScriptExtension", + "typeHandlerVersion": "1.8", + "autoUpgradeMinorVersion": true, + "settings": { + "fileUris": [ + "[concat('https://', parameters('artifactsStorageAccountName'), '.blob.core.windows.net/scripts/Windows/PrepareSQLServer_Install_Modules.ps1')]" + ] + }, + "protectedSettings": { + "storageAccountName": "[parameters('artifactsStorageAccountName')]", + "storageAccountKey": "[parameters('artifactsStorageAccountKey')]", + "commandToExecute": "powershell -ExecutionPolicy Unrestricted -File ./windows/PrepareSQLServer_Install_Modules.ps1" + } + } + } + ] + }, + { + "name": "vm1NestedDeploymentSetupSQL", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2016-09-01", + "dependsOn": [ + "vmInstallDSCModulesLoop" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "resources": [ + { + "apiVersion": "2017-03-30", + "type": "Microsoft.Compute/virtualMachines", + "name": "[concat(parameters('virtualMachineName'), '1')]", + "location": "[parameters('location')]", + "resources": [ + { + "type": "extensions", + "name": "[variables('DSCExtensionName')]", + "apiVersion": "2017-03-30", + "location": "[parameters('location')]", + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('virtualMachineName'), '1'))]" + ], + "properties": { + "publisher": "Microsoft.Powershell", + "type": "DSC", + "typeHandlerVersion": "2.7", + "autoUpgradeMinorVersion": true, + "settings": { + "configuration": { + "url": "[concat('https://', parameters('artifactsStorageAccountName'), '.blob.core.windows.net/scripts/windows/PrepareSQLServer.ps1.zip')]", + "script": "PrepareSqlServer.ps1", + "function": "SqlServerPrepareDsc" + }, + "configurationArguments": { + "DomainName": "[parameters('domainName')]", + "ClusterName": "[parameters('clusterName')]", + "ClusterOwnerNode": "[concat(parameters('virtualMachineName'), '1')]", + "ClusterIP": "[parameters('sqlServerILB_IPAddress')]", + "witnessStorageBlobEndPoint": "[concat('https://', parameters('cloudWitnessStorageAccountName'), '.blob.core.windows.net')]", + "witnessStorageAccountKey": "[parameters('cloudWitnessStorageAccountKey')]" + } + }, + "protectedSettings": { + "configurationUrlSasToken": "[variables('artifactsStorageAccountSasToken')]", + "configurationArguments": { + "AdminCreds": { + "UserName": "[parameters('domainAdminUsername')]", + "Password": "[parameters('domainAdminPassword')]" + }, + "sqlServiceCreds": { + "UserName": "[parameters('adminUsername')]", + "Password": "[parameters('adminPassword')]" + } + } + } + } + } + ] + } + ] + }, + "parameters": {} + } + }, + { + "name": "NestedDeploymentSleep", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2016-09-01", + "dependsOn": [ + "vm1NestedDeploymentSetupSQL" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "resources": [ + { + "apiVersion": "2017-03-30", + "type": "Microsoft.Compute/virtualMachines", + "name": "[concat(parameters('virtualMachineName'), '1')]", + "location": "[parameters('location')]", + "resources": [ + { + "type": "extensions", + "name": "[variables('PwshExtensionName')]", + "apiVersion": "2017-03-30", + "location": "[parameters('location')]", + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('virtualMachineName'), '1'))]" + ], + "properties": { + "publisher": "Microsoft.Compute", + "type": "CustomScriptExtension", + "typeHandlerVersion": "1.8", + "autoUpgradeMinorVersion": true, + "settings": { + "fileUris": [ + "[concat('https://', parameters('artifactsStorageAccountName'), '.blob.core.windows.net/scripts/Windows/sleep.ps1')]" + ] + }, + "protectedSettings": { + "storageAccountName": "[parameters('artifactsStorageAccountName')]", + "storageAccountKey": "[parameters('artifactsStorageAccountKey')]", + "commandToExecute": "powershell -ExecutionPolicy Unrestricted -File ./windows/sleep.ps1 -Sleep 600" + } + } + } + ] + } + ] + }, + "parameters": {} + } + }, + { + "name": "vm2NestedDeploymentSetupSQL", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2016-09-01", + "dependsOn": [ + "NestedDeploymentSleep" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "resources": [ + { + "apiVersion": "2017-03-30", + "type": "Microsoft.Compute/virtualMachines", + "name": "[concat(parameters('virtualMachineName'), '2')]", + "location": "[parameters('location')]", + "resources": [ + { + "type": "extensions", + "name": "[variables('DSCExtensionName')]", + "apiVersion": "2017-03-30", + "location": "[parameters('location')]", + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('virtualMachineName'), '2'))]" + ], + "properties": { + "publisher": "Microsoft.Powershell", + "type": "DSC", + "typeHandlerVersion": "2.7", + "autoUpgradeMinorVersion": true, + "settings": { + "configuration": { + "url": "[concat('https://', parameters('artifactsStorageAccountName'), '.blob.core.windows.net/scripts/Windows/PrepareSQLServer.ps1.zip')]", + "script": "PrepareSqlServer.ps1", + "function": "SqlServerPrepareDsc" + }, + "configurationArguments": { + "DomainName": "[parameters('domainName')]", + "ClusterName": "[parameters('clusterName')]", + "ClusterOwnerNode": "[concat(parameters('virtualMachineName'), '1')]", + "ClusterIP": "[parameters('sqlServerILB_IPAddress')]", + "witnessStorageBlobEndPoint": "[concat('https://', parameters('cloudWitnessStorageAccountName'), '.blob.core.windows.net')]", + "witnessStorageAccountKey": "[parameters('cloudWitnessStorageAccountKey')]" + } + }, + "protectedSettings": { + "configurationUrlSasToken": "[variables('artifactsStorageAccountSasToken')]", + "configurationArguments": { + "AdminCreds": { + "UserName": "[parameters('domainAdminUsername')]", + "Password": "[parameters('domainAdminPassword')]" + }, + "sqlServiceCreds": { + "UserName": "[parameters('adminUsername')]", + "Password": "[parameters('adminPassword')]" + } + } + } + } + } + ] + } + ] + }, + "parameters": {} + } + }, + { + "name": "vm1NestedDeploymentSetupSQLAG", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2016-09-01", + "dependsOn": [ + "vm2NestedDeploymentSetupSQL" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "resources": [ + { + "apiVersion": "2017-03-30", + "type": "Microsoft.Compute/virtualMachines", + "name": "[concat(parameters('virtualMachineName'), '1')]", + "location": "[parameters('location')]", + "resources": [ + { + "type": "extensions", + "name": "[variables('DSCExtensionName')]", + "apiVersion": "2017-03-30", + "location": "[parameters('location')]", + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('virtualMachineName'), '1'))]" + ], + "properties": { + "publisher": "Microsoft.Powershell", + "type": "DSC", + "typeHandlerVersion": "2.7", + "autoUpgradeMinorVersion": true, + "settings": { + "configuration": { + "url": "[concat('https://', parameters('artifactsStorageAccountName'), '.blob.core.windows.net/scripts/Windows/CreateHADB.ps1.zip')]", + "script": "agdb.ps1", + "function": "SQLServerDBDsc" + }, + "configurationArguments": { + "DomainName": "[parameters('domainName')]", + "ClusterName": "[parameters('clusterName')]", + "ClusterOwnerNode": "[concat(parameters('virtualMachineName'), '1')]", + "ClusterIP": "[parameters('sqlServerILB_IPAddress')]", + "witnessStorageBlobEndPoint": "[concat('https://', parameters('cloudWitnessStorageAccountName'), '.blob.core.windows.net')]", + "witnessStorageAccountKey": "[parameters('cloudWitnessStorageAccountKey')]" + } + }, + "protectedSettings": { + "configurationUrlSasToken": "[variables('artifactsStorageAccountSasToken')]", + "configurationArguments": { + "AdminCreds": { + "UserName": "[parameters('domainAdminUsername')]", + "Password": "[parameters('domainAdminPassword')]" + }, + "sqlServiceCreds": { + "UserName": "[parameters('adminUsername')]", + "Password": "[parameters('adminPassword')]" + } + } + } + } + } + ] + } + ] + }, + "parameters": {} + } + } + ], + "outputs": {} +} \ No newline at end of file diff --git a/Modules/SQLServer/2.0/_README.md b/Modules/SQLServerAlwaysOn/2.0/readme.md similarity index 58% rename from Modules/SQLServer/2.0/_README.md rename to Modules/SQLServerAlwaysOn/2.0/readme.md index 25951a5..0269ff1 100644 --- a/Modules/SQLServer/2.0/_README.md +++ b/Modules/SQLServerAlwaysOn/2.0/readme.md @@ -1,6 +1,6 @@ # SQL Server -This template deploys an SQL Server. +This template deploys an SQL Server Always On Virtual Machines using Cloud Witness. ## Deployed Resources diff --git a/Modules/VirtualMachineScaleSets/2.0/Policy/git_placeholder.md b/Modules/VirtualMachineScaleSets/2.0/Policy/git_placeholder.md new file mode 100644 index 0000000..e69de29 diff --git a/Modules/VirtualMachineScaleSets/2.0/RBAC/git_placeholder.md b/Modules/VirtualMachineScaleSets/2.0/RBAC/git_placeholder.md new file mode 100644 index 0000000..e69de29 diff --git a/Modules/VirtualMachineScaleSets/2.0/Scripts/git_placeholder.md b/Modules/VirtualMachineScaleSets/2.0/Scripts/git_placeholder.md new file mode 100644 index 0000000..e69de29 diff --git a/Modules/VirtualMachineScaleSets/2.0/Tests/linux.parameters.json b/Modules/VirtualMachineScaleSets/2.0/Tests/linux.parameters.json new file mode 100644 index 0000000..c86dfe0 --- /dev/null +++ b/Modules/VirtualMachineScaleSets/2.0/Tests/linux.parameters.json @@ -0,0 +1,56 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "virtualMachineScaleSetsName": { + "value": "adds" + }, + "virtualMachineScaleSetsSku": { + "value": { + "name": "SKUName", + "tier": "Standard", + "capacity": 5 + } + }, + "virtualMachineScaleSetsOSImage": { + "value": { + "publisher": "Canonical", + "offer": "UbuntuServer", + "sku": "18.04-LTS" + } + }, + "virtualMachineScaleSetsOSType": { + "value": "Linux" + }, + "workspaceId": { + "value": "00000000-0000-0000-0000-000000000000" + }, + "logAnalyticsWorkspacePrimarySharedKey": { + "value": "" + }, + "diagnosticsStorageAccountName": { + "value": "contoso-diag-storage" + }, + "diagnosticsStorageAccountSasToken": { + "value": "" + }, + "artifactsStorageAccountName": { + "value": "contoso-diag-storage" + }, + "artifactsStorageAccountSasKey": { + "value": "" + }, + "vNetId": { + "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup/providers/Microsoft.Network/virtualNetworks/contoso-vnet-example" + }, + "subnetName": { + "value": "sharedsvcs" + }, + "adminUsername": { + "value": "contoso" + }, + "sshPublicKey": { + "value": "password" + } + } +} \ No newline at end of file diff --git a/Modules/VirtualMachineScaleSets/2.0/Tests/module.tests.ps1 b/Modules/VirtualMachineScaleSets/2.0/Tests/module.tests.ps1 new file mode 100644 index 0000000..126f87a --- /dev/null +++ b/Modules/VirtualMachineScaleSets/2.0/Tests/module.tests.ps1 @@ -0,0 +1,164 @@ +<# + .NOTES + ============================================================================================== + Copyright(c) Microsoft Corporation. All rights reserved. + + File: module.tests.ps1 + + Purpose: Pester - Test Virtual Machine Scale Sets Scale Set ARM Templates + + Version: 1.0.0.0 - 1st April 2019 - Azure Virtual Datacenter Development Team + ============================================================================================== + + .SYNOPSIS + This script contains functionality used to test Virtual Machine Scale Sets Scale Set ARM template synatax. + + .DESCRIPTION + This script contains functionality used to test Virtual Machine Scale Sets Scale Set ARM template synatax. + + Deployment steps of the script are outlined below. + 1) Test Template File Syntax + 2) Test Parameter File Syntax + 3) Test Template and Parameter File Compactibility +#> + +#Requires -Version 5 + +#region Parameters + +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +$here = Join-Path $here ".." +$template = Split-Path -Leaf $here +$TemplateFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "deploy.json") -Recurse | Select-Object -ExpandProperty Name) ) { + $TemplateFileTestCases += @{ TemplateFile = $File } +} +$ParameterFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "Tests" -AdditionalChildPath @("*parameters.json")) -Recurse -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name) ) { + $ParameterFileTestCases += @{ ParameterFile = Join-Path "Tests" $File } +} +$Modules = @(); +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "deploy.json") ) ) { + $Module = [PSCustomObject]@{ + 'Template' = $null + 'Parameters' = $null + } + $Module.Template = $File.FullName; + $Parameters = @(); + ForEach ( $ParameterFile in (Get-ChildItem (Join-Path "$here" "Tests" -AdditionalChildPath @("*parameters.json")) -Recurse -ErrorAction SilentlyContinue| Select-Object -ExpandProperty Name) ) { + $Parameters += (Join-Path "$here" "Tests" -AdditionalChildPath @("$ParameterFile") ) + } + $Module.Parameters = $Parameters; + $Modules += @{ Module = $Module }; +} + +#endregion + +#region Run Pester Test Script +Describe "Template: $template - Virtual Machine Scale Sets" -Tags Unit { + + Context "Template File Syntax" { + + It "Has a JSON template file" { + (Join-Path "$here" "deploy.json") | Should Exist + } + + It "Converts from JSON and has the expected properties" -TestCases $TemplateFileTestCases { + Param( $TemplateFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters', + 'variables', + 'resources', + 'outputs' | Sort-Object + $templateProperties = (Get-Content (Join-Path "$here" "$TemplateFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateProperties | Should Be $expectedProperties + } + } + + Context "Parameter File Syntax" { + + It "Parameter file does not contains the expected properties" -TestCases $ParameterFileTestCases { + Param( $ParameterFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters' | Sort-Object + Write-Host $ParameterFile + Join-Path "$here" "$ParameterFile" | Write-Host + $templateFileProperties = (Get-Content (Join-Path "$here" "$ParameterFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateFileProperties | Should Be $expectedProperties + } + + } + + Context "Template and Parameter Compactibility" { + + It "Is count of required parameters in template file equal or lesser than count of all parameters in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + Write-Host "Required: $(ConvertTo-Json $requiredParametersInTemplateFile)" + ForEach ( $Parameter in $Module.Parameters ) { + Write-Host "File: $Parameter" + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + Write-Host "Got: $(ConvertTo-Json $allParametersInParametersFile)" + $requiredParametersInTemplateFile.Count | Should Not BeGreaterThan $allParametersInParametersFile.Count; + } + } + + It "Has all parameters in parameters file existing in template file" -TestCases $Modules { + Param( $Module ) + + $allParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + Write-Host "File analyzed: $Parameter"; + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $result = @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}); + Write-Host "Invalid parameters: $(ConvertTo-Json $result)"; + @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}).Count | Should Be 0; + } + } + + It "Has required parameters in template file existing in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + + @($requiredParametersInTemplateFile | Where-Object {$allParametersInParametersFile -notcontains $_}).Count | Should Be 0; + } + } + } + +} +#endregion \ No newline at end of file diff --git a/Modules/VirtualMachineScaleSets/2.0/Tests/policy.tests.ps1 b/Modules/VirtualMachineScaleSets/2.0/Tests/policy.tests.ps1 new file mode 100644 index 0000000..59b89af --- /dev/null +++ b/Modules/VirtualMachineScaleSets/2.0/Tests/policy.tests.ps1 @@ -0,0 +1,163 @@ +<# + .NOTES + ============================================================================================== + Copyright(c) Microsoft Corporation. All rights reserved. + + File: module.tests.ps1 + + Purpose: Pester - Test Virtual Machine ARM Templates + + Version: 1.0.0.0 - 1st April 2019 - Azure Virtual Datacenter Development Team + ============================================================================================== + + .SYNOPSIS + This script contains functionality used to test Azure Virtual Machine ARM template synatax. + + .DESCRIPTION + This script contains functionality used to test Azure Virtual Machine ARM template synatax. + + Deployment steps of the script are outlined below. + 1) Test Template File Syntax + 2) Test Parameter File Syntax + 3) Test Template and Parameter File Compactibility +#> + +#Requires -Version 5 + +#region Parameters + +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +$here = Join-Path $here ".." +$template = Split-Path -Leaf $here +$TemplateFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "Policy" -AdditionalChildPath @("deploy.json")) -Recurse -ErrorAction SilentlyContinue| Select-Object -ExpandProperty Name) ) { + $TemplateFileTestCases += @{ TemplateFile = $File } +} +$ParameterFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "Policy" -AdditionalChildPath @("*parameters.json")) -Recurse -ErrorAction SilentlyContinue| Select-Object -ExpandProperty Name) ) { + $ParameterFileTestCases += @{ ParameterFile = Join-Path "Policy" $File } +} +$Modules = @(); +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "Policy" -AdditionalChildPath @("deploy.json")) -ErrorAction SilentlyContinue ) ) { + $Module = [PSCustomObject]@{ + 'Template' = $null + 'Parameters' = $null + } + $Module.Template = $File.FullName; + $Parameters = @(); + ForEach ( $ParameterFile in (Get-ChildItem (Join-Path "$here" "Policy" -AdditionalChildPath @("*parameters.json")) -Recurse -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name) ) { + $Parameters += (Join-Path "$here" "Policy" -AdditionalChildPath @("$ParameterFile") ) + } + $Module.Parameters = $Parameters; + $Modules += @{ Module = $Module }; +} + +#endregion + +if ($null -ne $TemplateFileTestCases -and + $TemplateFileTestCases.Count -gt 0) { + + #region Run Pester Test Script + Describe "Template: $template - Virtual Machine" -Tags Unit { + + Context "Template File Syntax" { + + It "Has a JSON template file" { + (Join-Path "$here" "deploy.json") | Should Exist + } + + It "Converts from JSON and has the expected properties" -TestCases $TemplateFileTestCases { + Param( $TemplateFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters', + 'variables', + 'resources', + 'outputs' | Sort-Object + $templateProperties = (Get-Content (Join-Path "$here" "$TemplateFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateProperties | Should Be $expectedProperties + } + } + + Context "Parameter File Syntax" { + + It "Parameter file does not contains the expected properties" -TestCases $ParameterFileTestCases { + Param( $ParameterFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters' | Sort-Object + Write-Host $ParameterFile + Join-Path "$here" "$ParameterFile" | Write-Host + $templateFileProperties = (Get-Content (Join-Path "$here" "$ParameterFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateFileProperties | Should Be $expectedProperties + } + + } + + Context "Template and Parameter Compactibility" { + + It "Is count of required parameters in template file equal or lesser than count of all parameters in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $requiredParametersInTemplateFile.Count | Should Not BeGreaterThan $allParametersInParametersFile.Count; + } + } + + It "Has all parameters in parameters file existing in template file" -TestCases $Modules { + Param( $Module ) + + $allParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + Write-Host "File analyzed: $Parameter"; + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $result = @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}); + Write-Host "Invalid parameters: $(ConvertTo-Json $result)"; + @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}).Count | Should Be 0; + } + } + + It "Has required parameters in template file existing in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + @($requiredParametersInTemplateFile| Where-Object {$allParametersInParametersFile -notcontains $_}).Count | Should Be 0; + } + } + } + + } + #endregion +} \ No newline at end of file diff --git a/Modules/VirtualMachineScaleSets/2.0/Tests/rbac.tests.ps1 b/Modules/VirtualMachineScaleSets/2.0/Tests/rbac.tests.ps1 new file mode 100644 index 0000000..0a22928 --- /dev/null +++ b/Modules/VirtualMachineScaleSets/2.0/Tests/rbac.tests.ps1 @@ -0,0 +1,163 @@ +<# + .NOTES + ============================================================================================== + Copyright(c) Microsoft Corporation. All rights reserved. + + File: module.tests.ps1 + + Purpose: Pester - Test Virtual Machine ARM Templates + + Version: 1.0.0.0 - 1st April 2019 - Azure Virtual Datacenter Development Team + ============================================================================================== + + .SYNOPSIS + This script contains functionality used to test Azure Virtual Machine ARM template synatax. + + .DESCRIPTION + This script contains functionality used to test Azure Virtual Machine ARM template synatax. + + Deployment steps of the script are outlined below. + 1) Test Template File Syntax + 2) Test Parameter File Syntax + 3) Test Template and Parameter File Compactibility +#> + +#Requires -Version 5 + +#region Parameters + +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +$here = Join-Path $here ".." +$template = Split-Path -Leaf $here +$TemplateFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "RBAC" -AdditionalChildPath @("deploy.json")) -ErrorAction SilentlyContinue -Recurse | Select-Object -ExpandProperty Name) ) { + $TemplateFileTestCases += @{ TemplateFile = $File } +} +$ParameterFileTestCases = @() +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "RBAC" -AdditionalChildPath @("*parameters.json")) -ErrorAction SilentlyContinue -Recurse | Select-Object -ExpandProperty Name) ) { + $ParameterFileTestCases += @{ ParameterFile = Join-Path "RBAC" $File } +} +$Modules = @(); +ForEach ( $File in (Get-ChildItem (Join-Path "$here" "RBAC" -AdditionalChildPath @("deploy.json")) -ErrorAction SilentlyContinue ) ) { + $Module = [PSCustomObject]@{ + 'Template' = $null + 'Parameters' = $null + } + $Module.Template = $File.FullName; + $Parameters = @(); + ForEach ( $ParameterFile in (Get-ChildItem (Join-Path "$here" "RBAC" -AdditionalChildPath @("*parameters.json")) -Recurse | Select-Object -ExpandProperty Name) ) { + $Parameters += (Join-Path "$here" "RBAC" -AdditionalChildPath @("$ParameterFile") ) + } + $Module.Parameters = $Parameters; + $Modules += @{ Module = $Module }; +} + +#endregion + +if ($null -ne $TemplateFileTestCases -and + $TemplateFileTestCases.Count -gt 0) { + + #region Run Pester Test Script + Describe "Template: $template - Virtual Machine" -Tags Unit { + + Context "Template File Syntax" { + + It "Has a JSON template file" -TestCases $TemplateFileTestCases { + (Join-Path "$here" "deploy.json") | Should Exist + } + + It "Converts from JSON and has the expected properties" -TestCases $TemplateFileTestCases { + Param( $TemplateFile ) + Write-Host "TF: $TemplateFile" + $expectedProperties = '$schema', + 'contentVersion', + 'parameters', + 'variables', + 'resources', + 'outputs' | Sort-Object + $templateProperties = (Get-Content (Join-Path "$here" "$TemplateFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateProperties | Should Be $expectedProperties + } + } + + Context "Parameter File Syntax" { + + It "Parameter file does not contains the expected properties" -TestCases $ParameterFileTestCases { + Param( $ParameterFile ) + $expectedProperties = '$schema', + 'contentVersion', + 'parameters' | Sort-Object + Write-Host $ParameterFile + Join-Path "$here" "$ParameterFile" | Write-Host + $templateFileProperties = (Get-Content (Join-Path "$here" "$ParameterFile") ` + | ConvertFrom-Json -ErrorAction SilentlyContinue) ` + | Get-Member -MemberType NoteProperty ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $templateFileProperties | Should Be $expectedProperties + } + } + + Context "Template and Parameter Compactibility" { + + It "Is count of required parameters in template file equal or lesser than count of all parameters in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $requiredParametersInTemplateFile.Count | Should Not BeGreaterThan $allParametersInParametersFile.Count; + } + } + + It "Has all parameters in parameters file existing in template file" -TestCases $Modules { + Param( $Module ) + + $allParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + Write-Host "File analyzed: $Parameter"; + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + $result = @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}); + Write-Host "Invalid parameters: $(ConvertTo-Json $result)"; + @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}).Count | Should Be 0; + } + } + + It "Has required parameters in template file existing in parameters file" -TestCases $Modules { + Param( $Module ) + + $requiredParametersInTemplateFile = (Get-Content "$($Module.Template)" ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Where-Object -FilterScript { -not ($_.Value.PSObject.Properties.Name -eq "defaultValue") } ` + | Sort-Object -Property Name ` + | ForEach-Object Name + ForEach ( $Parameter in $Module.Parameters ) { + $allParametersInParametersFile = (Get-Content $Parameter ` + | ConvertFrom-Json -ErrorAction SilentlyContinue).Parameters.PSObject.Properties ` + | Sort-Object -Property Name ` + | ForEach-Object Name + @($requiredParametersInTemplateFile| Where-Object {$allParametersInParametersFile -notcontains $_}).Count | Should Be 0; + } + } + } + + } + #endregion +} \ No newline at end of file diff --git a/Modules/VirtualMachineScaleSets/2.0/Tests/windows.parameters.json b/Modules/VirtualMachineScaleSets/2.0/Tests/windows.parameters.json new file mode 100644 index 0000000..03648cd --- /dev/null +++ b/Modules/VirtualMachineScaleSets/2.0/Tests/windows.parameters.json @@ -0,0 +1,56 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "virtualMachineScaleSetsName": { + "value": "adds" + }, + "virtualMachineScaleSetsSku": { + "value": { + "name": "SKUName", + "tier": "Standard", + "capacity": 5 + } + }, + "virtualMachineScaleSetsOSImage": { + "value": { + "offer": "WindowsServer", + "publisher": "MicrosoftWindowsServer", + "sku": "2016-Datacenter" + } + }, + "virtualMachineScaleSetsOSType": { + "value": "Windows" + }, + "workspaceId": { + "value": "00000000-0000-0000-0000-000000000000" + }, + "logAnalyticsWorkspacePrimarySharedKey": { + "value": "" + }, + "diagnosticsStorageAccountName": { + "value": "contoso-diag-storage" + }, + "diagnosticsStorageAccountSasToken": { + "value": "" + }, + "artifactsStorageAccountName": { + "value": "contoso-diag-storage" + }, + "artifactsStorageAccountSasKey": { + "value": "" + }, + "vNetId": { + "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup/providers/Microsoft.Network/virtualNetworks/contoso-vnet-example" + }, + "subnetName": { + "value": "sharedsvcs" + }, + "adminUsername": { + "value": "contoso" + }, + "adminPassword": { + "value": "password" + } + } +} \ No newline at end of file diff --git a/Modules/VirtualMachineScaleSets/2.0/deploy.json b/Modules/VirtualMachineScaleSets/2.0/deploy.json new file mode 100644 index 0000000..525df07 --- /dev/null +++ b/Modules/VirtualMachineScaleSets/2.0/deploy.json @@ -0,0 +1,787 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "virtualMachineScaleSetsName": { + "type": "string", + "minLength": 1, + "maxLength": 13, + "metadata": { + "description": "Required. Name for the ADDS VMs" + } + }, + "virtualMachineScaleSetsSku": { + "type": "object", + "metadata": { + "description": "Required. Object containing the VMSS Sku, the object must have the following format: { 'name': 'SKUName', 'tier': 'Standard | Basic', 'capacity': 'Instance count' }" + } + }, + "virtualMachineScaleSetsOSImage": { + "type": "object", + "metadata": { + "description": "Required. OS image used for VMSS" + } + }, + "virtualMachineScaleSetsUpgradePolicy": { + "type": "string", + "defaultValue": "Manual", + "allowedValues": [ + "Manual", + "Automatic" + ], + "metadata": { + "description": "Optional. The upgrade policy of a VMSS, if Manual is specified, you control the application of updates to virtual machines in the scale set. You do this by using the manualUpgrade action. When Automatic is set, All virtual machines in the scale set are automatically updated at the same time. - Automatic, Manual, Rolling." + } + }, + "virtualMachineScaleSetsRollingUpgradePolicy": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The rolling upgrade policy of a VMSS, required if virtualMachineScaleSetsUpgradePolicy is set to Automatic and you want to perform rolling updates." + } + }, + "virtualMachineScaleSetsAutomaticOSUpgradePolicy": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The rolling upgrade policy of a VMSS, required if virtualMachineScaleSetsUpgradePolicy is set to Automatic and you want to perform automatic updates." + } + }, + "virtualMachineScaleSetsOSType": { + "type": "string", + "allowedValues": [ + "Windows", + "Linux" + ], + "metadata": { + "description": "Required. OS type used for the VMs" + } + }, + "virtualMachineScaleSetsDataDisks": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of objects with the following expected format: [{ 'size': 120 }, { 'size': 130 }], this array indicates that two data disks will be created." + } + }, + "virtualMachineScaleSetsPrivateIPAddressVersion": { + "type": "string", + "defaultValue": "IPv4", + "allowedValues": [ + "IPv4", + "IPv6" + ], + "metadata": { + "description": "Optional. Represents whether the specific ipconfiguration is IPv4 or IPv6" + } + }, + "loadBalancerBackendPoolId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Represents a Load Balancer backend pool resource identifier, if left blank, no Load Balancer will be associated to the VMSS" + } + }, + "customData": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Custom data associated to the VM, this value will be automatically converted into base64 to account for the expected VM format." + } + }, + "workspaceId": { + "type": "string", + "metadata": { + "description": "Required. WorkspaceId or CustomerId value of OMS. This value is referenced in OMS VM Extension" + } + }, + "logAnalyticsWorkspacePrimarySharedKey": { + "type": "securestring", + "metadata": { + "description": "Required. WorkspaceKey value of OMS. This value is referenced in OMS VM Extension" + } + }, + "diagnosticsStorageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Storage account used to store diagnostic information" + } + }, + "diagnosticsStorageAccountSasToken": { + "type": "securestring", + "metadata": { + "description": "Required. Diagnostic Storage Account SAS token" + } + }, + "artifactsStorageAccountName": { + "type": "securestring", + "metadata": { + "description": "Required. Default storage account name. Storage account that contains output parameters and common scripts" + } + }, + "artifactsStorageAccountSasKey": { + "type": "securestring", + "metadata": { + "description": "Required. Shared Access Signature Key used to download custom scripts" + } + }, + "vNetId": { + "type": "string", + "metadata": { + "description": "Required. Shared services Virtual Network resource identifier" + } + }, + "subnetName": { + "type": "string", + "metadata": { + "description": "Required. Name of Shared Services Subnet, this name is used to get the SubnetId" + } + }, + "applicationSecurityGroupId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Application Security Group to associate to the Network Interface. If left empty, the Network Interface would not be associated to any Application Security Group." + } + }, + "adminUsername": { + "type": "securestring", + "metadata": { + "description": "Required. Domain user that has privileges to join a VM into a Domain" + } + }, + "adminPassword": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Optional. When specifying a Windows Virtual Machine, this value should be passed" + } + }, + "sshPublicKey": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Optional. SSH public key. When specifying a Linux Virtual Machine, this value should be passed. Linux VMs can be accessed via SSH public key only." + } + }, + "proximityPlacementGroupsId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. If passed, the VMSS will be assigned to a Proximity Placement Groups." + } + }, + "domainAdminUsername": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Optional. Domain user that has privileges to join a VM into a Domain. If joinToDomain is set to true, this value becomes required." + } + }, + "domainAdminPassword": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Optional. Domain user that has privileges to join a VM into a Domain. If joinToDomain is set to true, this value becomes required." + } + }, + "domainName": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Optional. AD domain name. If joinToDomain is set to true, this value becomes required." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + } + }, + "variables": { + "joinToDomain": "[and(not(empty(parameters('domainAdminUsername'))), not(empty(parameters('domainAdminPassword'))))]", + "applicationSecurityGroups": [ + { + "id": "[parameters('applicationSecurityGroupId')]" + } + ], + "proximityPlacementGroup": { + "id": "[parameters('proximityPlacementGroupsId')]" + }, + "loadBalancerBackendPoolId": [ + { + "id": "[parameters('loadBalancerBackendPoolId')]" + } + ], + "linuxConfiguration": { + "disablePasswordAuthentication": true, + "ssh": { + "publicKeys": [ + { + "path": "[concat('/home/', parameters('adminUsername'), '/.ssh/authorized_keys')]", + "keyData": "[parameters('sshPublicKey')]" + } + ] + } + }, + "subnetId": "[concat(parameters('vNetId'), '/subnets/', parameters('subnetName'))]", + "antimalwareExtensionName": "IaaSAntimalware", + "diagnosticsExtensionName": "IaaSDiagnostics", + "networkWatcherExtensionName": "NetworkWatcher", + "MMAExtensionName": "OMSExtension", + "tagPatching": "3rdSat7pm", + "DSCExtensionName": "DSCExtension", + "joinToDomainExtensionName": "JoinToDomainExtension", + "domainAndUsername": "[concat(parameters('domainName'), '\\', parameters('domainAdminUsername'))]" + }, + "resources": [ + { + "type": "Microsoft.Compute/virtualMachineScaleSets", + "apiVersion": "2019-03-01", + "location": "[parameters('location')]", + "name": "[parameters('virtualMachineScaleSetsName')]", + "tags": { + "computerName": "[parameters('virtualMachineScaleSetsName')]", + "UpdateManagement": "[variables('tagPatching')]" + }, + "sku": { + "name": "[parameters('virtualMachineScaleSetsSku').name]", + "tier": "[parameters('virtualMachineScaleSetsSku').tier]", + "capacity": "[parameters('virtualMachineScaleSetsSku').capacity]" + }, + "properties": { + "upgradePolicy": { + "mode": "[parameters('virtualMachineScaleSetsUpgradePolicy')]", + "rollingUpgradePolicy": "[parameters('virtualMachineScaleSetsRollingUpgradePolicy')]", + "automaticOSUpgradePolicy": "[parameters('virtualMachineScaleSetsAutomaticOSUpgradePolicy')]" + }, + "virtualMachineProfile": { + "osProfile": { + "computerNamePrefix": "[parameters('virtualMachineScaleSetsName')]", + "adminUsername": "[parameters('adminUsername')]", + "adminPassword": "[if(equals(parameters('virtualMachineScaleSetsOSType'), 'Linux'), json('null'), parameters('adminPassword'))]", + "customData": "[if(empty(parameters('customData')), json('null'), base64(parameters('customData')))]", + "linuxConfiguration": "[if(equals(parameters('virtualMachineScaleSetsOSType'), 'Linux'), variables('linuxConfiguration'), json('null'))]" + }, + "storageProfile": { + "imageReference": { + "publisher": "[parameters('virtualMachineScaleSetsOSImage').publisher]", + "offer": "[parameters('virtualMachineScaleSetsOSImage').offer]", + "sku": "[parameters('virtualMachineScaleSetsOSImage').sku]", + "version": "latest" + }, + "osDisk": { + "caching": "ReadOnly", + "createOption": "FromImage", + "diskSizeGB": 256 + }, + "copy": [ + { + "name": "dataDisks", + "count": "[length(parameters('virtualMachineScaleSetsDataDisks'))]", + "input": { + "lun": "[copyIndex('dataDisks')]", + "diskSizeGB": "[parameters('virtualMachineScaleSetsDataDisks')[copyIndex('dataDisks')].size]", + "createOption": "Empty", + "caching": "None" + } + } + ] + }, + "networkProfile": { + "networkInterfaceConfigurations": [ + { + "name": "[concat(parameters('virtualMachineScaleSetsName'), '-nic')]", + "properties": { + "enableAcceleratedNetworking": true, + "primary": true, + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "subnet": { + "id": "[variables('subnetId')]" + }, + "privateIPAddressVersion": "[parameters('virtualMachineScaleSetsPrivateIPAddressVersion')]", + "applicationSecurityGroups": "[if(empty(parameters('applicationSecurityGroupId')), json('null'), variables('applicationSecurityGroups'))]", + "loadBalancerBackendAddressPools": "[if(empty(parameters('loadBalancerBackendPoolId')), json('null'), variables('loadBalancerBackendPoolId'))]" + } + } + ], + "enableIPForwarding": true + } + } + ] + }, + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": true, + "storageUri": "[concat('https://', parameters('diagnosticsStorageAccountName'), '.blob.core.windows.net/')]" + } + }, + "extensionProfile": { + "extensions": [ + { + "name": "[variables('MMAExtensionName')]", + "properties": { + "publisher": "Microsoft.EnterpriseCloud.Monitoring", + "type": "MicrosoftMonitoringAgent", + "typeHandlerVersion": "1.0", + "autoUpgradeMinorVersion": true, + "settings": { + "workspaceId": "[parameters('workspaceId')]" + }, + "protectedSettings": { + "workspaceKey": "[parameters('logAnalyticsWorkspacePrimarySharedKey')]" + } + } + }, + { + "name": "[variables('antimalwareExtensionName')]", + "properties": { + "publisher": "Microsoft.Azure.Security", + "type": "IaaSAntimalware", + "typeHandlerVersion": "1.5", + "autoUpgradeMinorVersion": true, + "settings": { + "AntimalwareEnabled": true, + "RealtimeProtectionEnabled": "true", + "ScheduledScanSettings": { + "isEnabled": "true", + "scanType": "Quick", + "day": "7", + "time": "120" + } + } + } + }, + { + "name": "[variables('diagnosticsExtensionName')]", + "properties": { + "publisher": "Microsoft.Azure.Diagnostics", + "type": "IaaSDiagnostics", + "typeHandlerVersion": "1.5", + "autoUpgradeMinorVersion": true, + "settings": { + "StorageAccount": "[parameters('diagnosticsStorageAccountName')]", + "StorageType": "Blob", + "WadCfg": { + "DiagnosticMonitorConfiguration": { + "overallQuotaInMB": 5120, + "Metrics": { + "resourceId": "[resourceId('Microsoft.Compute/virtualMachineScaleSets', parameters('virtualMachineScaleSetsName'))]", + "MetricAggregation": [ + { + "scheduledTransferPeriod": "PT1H" + }, + { + "scheduledTransferPeriod": "PT1M" + } + ] + }, + "DiagnosticInfrastructureLogs": { + "scheduledTransferLogLevelFilter": "Error" + }, + "PerformanceCounters": { + "scheduledTransferPeriod": "PT1M", + "PerformanceCounterConfiguration": [ + { + "counterSpecifier": "\\Processor Information(_Total)\\% Processor Time", + "unit": "Percent", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Processor Information(_Total)\\% Privileged Time", + "unit": "Percent", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Processor Information(_Total)\\% User Time", + "unit": "Percent", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Processor Information(_Total)\\Processor Frequency", + "unit": "Count", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\System\\Processes", + "unit": "Count", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Process(_Total)\\Thread Count", + "unit": "Count", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Process(_Total)\\Handle Count", + "unit": "Count", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\System\\System Up Time", + "unit": "Count", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\System\\Context Switches/sec", + "unit": "CountPerSecond", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\System\\Processor Queue Length", + "unit": "Count", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Memory\\% Committed Bytes In Use", + "unit": "Percent", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Memory\\Available Bytes", + "unit": "Bytes", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Memory\\Committed Bytes", + "unit": "Bytes", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Memory\\Cache Bytes", + "unit": "Bytes", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Memory\\Pool Paged Bytes", + "unit": "Bytes", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Memory\\Pool Nonpaged Bytes", + "unit": "Bytes", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Memory\\Pages/sec", + "unit": "CountPerSecond", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Memory\\Page Faults/sec", + "unit": "CountPerSecond", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Process(_Total)\\Working Set", + "unit": "Count", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Process(_Total)\\Working Set - Private", + "unit": "Count", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\LogicalDisk(_Total)\\% Disk Time", + "unit": "Percent", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\LogicalDisk(_Total)\\% Disk Read Time", + "unit": "Percent", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\LogicalDisk(_Total)\\% Disk Write Time", + "unit": "Percent", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\LogicalDisk(_Total)\\% Idle Time", + "unit": "Percent", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\LogicalDisk(_Total)\\Disk Bytes/sec", + "unit": "BytesPerSecond", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\LogicalDisk(_Total)\\Disk Read Bytes/sec", + "unit": "BytesPerSecond", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\LogicalDisk(_Total)\\Disk Write Bytes/sec", + "unit": "BytesPerSecond", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\LogicalDisk(_Total)\\Disk Transfers/sec", + "unit": "BytesPerSecond", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\LogicalDisk(_Total)\\Disk Reads/sec", + "unit": "BytesPerSecond", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\LogicalDisk(_Total)\\Disk Writes/sec", + "unit": "BytesPerSecond", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\LogicalDisk(_Total)\\Avg. Disk sec/Transfer", + "unit": "Count", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\LogicalDisk(_Total)\\Avg. Disk sec/Read", + "unit": "Count", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\LogicalDisk(_Total)\\Avg. Disk sec/Write", + "unit": "Count", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\LogicalDisk(_Total)\\Avg. Disk Queue Length", + "unit": "Count", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\LogicalDisk(_Total)\\Avg. Disk Read Queue Length", + "unit": "Count", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\LogicalDisk(_Total)\\Avg. Disk Write Queue Length", + "unit": "Count", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\LogicalDisk(_Total)\\% Free Space", + "unit": "Percent", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\LogicalDisk(_Total)\\Free Megabytes", + "unit": "Count", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Network Interface(*)\\Bytes Total/sec", + "unit": "BytesPerSecond", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Network Interface(*)\\Bytes Sent/sec", + "unit": "BytesPerSecond", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Network Interface(*)\\Bytes Received/sec", + "unit": "BytesPerSecond", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Network Interface(*)\\Packets/sec", + "unit": "BytesPerSecond", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Network Interface(*)\\Packets Sent/sec", + "unit": "BytesPerSecond", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Network Interface(*)\\Packets Received/sec", + "unit": "BytesPerSecond", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Network Interface(*)\\Packets Outbound Errors", + "unit": "Count", + "sampleRate": "PT60S" + }, + { + "counterSpecifier": "\\Network Interface(*)\\Packets Received Errors", + "unit": "Count", + "sampleRate": "PT60S" + } + ] + }, + "WindowsEventLog": { + "scheduledTransferPeriod": "PT1M", + "DataSource": [ + { + "name": "Application!*[System[(Level = 1 or Level = 2 or Level = 3)]]" + }, + { + "name": "Security!*[System[band(Keywords,4503599627370496)]]" + }, + { + "name": "System!*[System[(Level = 1 or Level = 2 or Level = 3)]]" + } + ] + } + } + } + }, + "protectedSettings": { + "storageAccountName": "[parameters('diagnosticsStorageAccountName')]", + "storageAccountSasToken": "[parameters('diagnosticsStorageAccountSasToken')]", + "storageAccountEndPoint": "https://core.windows.net" + } + } + }, + { + "name": "[variables('networkWatcherExtensionName')]", + "properties": { + "publisher": "Microsoft.Azure.NetworkWatcher", + "type": "NetworkWatcherAgentWindows", + "typeHandlerVersion": "1.4", + "autoUpgradeMinorVersion": true + } + }, + { + "name": "[variables('DSCExtensionName')]", + "properties": { + "publisher": "Microsoft.Powershell", + "type": "DSC", + "typeHandlerVersion": "2.9", + "autoUpgradeMinorVersion": true, + "settings": { + "configuration": { + "url": "[concat('https://', parameters('artifactsStorageAccountName'), '.blob.core.windows.net/scripts/Windows/formatDataDisks.zip')]", + "script": "formatDisk.ps1", + "function": "FormatDisk" + }, + "configurationArguments": { + "DataDisks": "[string(parameters('virtualMachineScaleSetsDataDisks'))]" + } + }, + "protectedSettings": { + "configurationUrlSasToken": "[concat('?', parameters('artifactsStorageAccountSasKey'))]" + } + } + } + ] + } + }, + "overprovision": true, + "platformFaultDomainCount": 2, + "proximityPlacementGroup": "[if(empty(parameters('proximityPlacementGroupsId')), json('null'), variables('proximityPlacementGroup'))]" + }, + "resources": [] + }, + { + "type": "Microsoft.Compute/virtualMachineScaleSets/extensions", + "apiVersion": "2019-03-01", + "name": "[concat(parameters('virtualMachineScaleSetsName'), '/', variables('joinToDomainExtensionName'))]", + "condition": "[variables('joinToDomain')]", + "location": "[parameters('location')]", + "properties": { + "publisher": "Microsoft.Compute", + "type": "JsonADDomainExtension", + "typeHandlerVersion": "1.3", + "settings": { + "Name": "[parameters('domainName')]", + "OUPath": "", + "User": "[variables('domainAndUsername')]", + "Restart": "true", + "Options": 3 + }, + "protectedsettings": { + "Password": "[parameters('domainAdminPassword')]" + } + } + }, + { + "type": "Microsoft.Insights/autoscaleSettings", + "apiVersion": "2015-04-01", + "name": "[concat(parameters('virtualMachineScaleSetsName'), '-cpuautoscale')]", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[concat('Microsoft.Compute/virtualMachineScaleSets/', parameters('virtualMachineScaleSetsName'))]" + ], + "properties": { + "name": "[concat(parameters('virtualMachineScaleSetsName'), '-cpuautoscale')]", + "targetResourceUri": "[concat('/subscriptions/',subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Compute/virtualMachineScaleSets/', parameters('virtualMachineScaleSetsName'))]", + "enabled": true, + "profiles": [ + { + "name": "Profile1", + "capacity": { + "minimum": "1", + "maximum": "10", + "default": "1" + }, + "rules": [ + { + "metricTrigger": { + "metricName": "Percentage CPU", + "metricNamespace": "", + "metricResourceUri": "[concat('/subscriptions/',subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Compute/virtualMachineScaleSets/', parameters('virtualMachineScaleSetsName'))]", + "timeGrain": "PT1M", + "statistic": "Average", + "timeWindow": "PT5M", + "timeAggregation": "Average", + "operator": "GreaterThan", + "threshold": 50 + }, + "scaleAction": { + "direction": "Increase", + "type": "ChangeCount", + "value": "1", + "cooldown": "PT5M" + } + }, + { + "metricTrigger": { + "metricName": "Percentage CPU", + "metricNamespace": "", + "metricResourceUri": "[concat('/subscriptions/',subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Compute/virtualMachineScaleSets/', parameters('virtualMachineScaleSetsName'))]", + "timeGrain": "PT1M", + "statistic": "Average", + "timeWindow": "PT5M", + "timeAggregation": "Average", + "operator": "LessThan", + "threshold": 30 + }, + "scaleAction": { + "direction": "Decrease", + "type": "ChangeCount", + "value": "1", + "cooldown": "PT5M" + } + } + ] + } + ] + } + } + ], + "outputs": { + "virtualMachineScaleSetsName": { + "type": "string", + "value": "[parameters('virtualMachineScaleSetsName')]" + }, + "virtualMachineScaleSetsResourceId": { + "type": "string", + "value": "[resourceId('Microsoft.Compute/virtualMachineScaleSets', parameters('virtualMachineScaleSetsName'))]" + }, + "virtualMachineScaleSetsResourceGroup": { + "type": "string", + "value": "[resourceGroup().name]" + } + } +} \ No newline at end of file diff --git a/Modules/VirtualMachineScaleSets/2.0/readme.md b/Modules/VirtualMachineScaleSets/2.0/readme.md new file mode 100644 index 0000000..47965f2 --- /dev/null +++ b/Modules/VirtualMachineScaleSets/2.0/readme.md @@ -0,0 +1,54 @@ +# Active Directory + +This template deploys Active Directory Domain Services. + +## Resources + +- Microsoft.Compute/availabilitySets +- Microsoft.Network/networkInterfaces +- Microsoft.Compute/virtualMachines +- Microsoft.Compute/virtualMachines/extensions +- Microsoft.Compute/virtualMachines/providers/guestConfigurationAssignments + +## Parameters + +| Parameter Name | Default Value | Description | +| :- | :- | :- | +| `virtualMachineName` | | Required. Name for the Active Directory VMs +| `virtualMachineSize` | `Standard_DS2_v2` | Optional. Size of the Active Directory VMs +| `virtualMachineOSImage` | | Required. OS image used for the Active Directory VMs| `artifactsStorageAccountSasKey` | | Required. Shared Access Signature Key used to download custom scripts +| `artifactsStorageAccountName` | | Required. Default storage account name. Storage account that contains output parameters and common scripts +| `artifactsStorageAccountKey` | | Required. Default storage account Key. Storage account that contains output parameters and common scripts +| `workspaceId` | | Required. WorkspaceId or CustomerId value of OMS. This value is referenced in OMS VM Extension +| `logAnalyticsWorkspacePrimarySharedKey` | | Required. WorkspaceKey value of OMS. This value is referenced in OMS VM Extension +| `diagnosticsStorageAccountName` | | Required. Storage account used to store diagnostic information +| `diagnosticsStorageAccountSasToken` | | Required. Diagnostic Storage Account SAS token +| `adIpAddress` | | Required. IP address used as primary Domain Controller IP +| `vNetId` | | Required. Shared services Virtual Network resource identifier +| `domainControllerAsgId` | | Required. ASG associated to Domain Controllers +| `subnetName` | | Required. Name of Shared Services Subnet, this name is used to get the SubnetId +| `cloudZone` | | Required. Cloud Zone to be created, this is useful when using one way trust relationship +| `domainName` | | Required. AD domain name +| `adSitename` | | Required. On-premises Active Directory site name +| `keyVaultId` | `""` | Optional. AKV Resource Id +| `keyVaultURL` | `""` | Optional. AKV URL +| `adKeyEncryptionURL` | `""` | Optional. Active Directory AKV encryption key +| `domainAdminUsername` | | Required. Domain user that has privileges to join a VM into a Domain +| `domainAdminPassword` | | Required. Domain user that has privileges to join a VM into a Domain + +## Outputs + +| Output Name | Description | +| :- | :- | +| `adResourceGroup` | The Resource Group that was deployed to. +| `dnsServers` | DNS Server IP +| `adAvailabilitySetResourceId` | Active Directory Availability Set Resource Identifier + +## Considerations + +*N/A* + +## Additional resources + +- [Active Directory Domain Services](https://docs.microsoft.com/en-us/windows/desktop/ad/active-directory-domain-services) +- [Microsoft.Compute virtualMachines template reference](https://docs.microsoft.com/en-us/azure/templates/microsoft.compute/2019-03-01/virtualmachines) diff --git a/Modules/VirtualMachines/2.0/Tests/linux.enable.pip.parameters.json b/Modules/VirtualMachines/2.0/Tests/linux.enable.pip.parameters.json new file mode 100644 index 0000000..8e47d09 --- /dev/null +++ b/Modules/VirtualMachines/2.0/Tests/linux.enable.pip.parameters.json @@ -0,0 +1,64 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "virtualMachineName": { + "value": "adds" + }, + "virtualMachineSize": { + "value": "Standard_DS2_v2" + }, + "virtualMachineOSImage": { + "value": { + "offer": "WindowsServer", + "publisher": "MicrosoftWindowsServer", + "sku": "2016-Datacenter" + } + }, + "virtualMachineOSType": { + "value": "Linux" + }, + "workspaceId": { + "value": "00000000-0000-0000-0000-000000000000" + }, + "logAnalyticsWorkspaceId": { + "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup/providers/Microsoft.OperationalInsights/workspaces/contoso-example" + }, + "logAnalyticsWorkspacePrimarySharedKey": { + "value": "" + }, + "diagnosticsStorageAccountId": { + "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup/providers/Microsoft.Storage/storageAccounts/contosostrgexmpl" + }, + "diagnosticsStorageAccountName": { + "value": "contoso-diag-storage" + }, + "diagnosticsStorageAccountSasToken": { + "value": "" + }, + "artifactsStorageAccountName": { + "value": "vdcstorage" + }, + "artifactsStorageAccountKey": { + "value": "" + }, + "artifactsStorageAccountSasKey": { + "value": "" + }, + "vNetId": { + "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup/providers/Microsoft.Network/virtualNetworks/contoso-vnet-example" + }, + "subnetName": { + "value": "sharedsvcs" + }, + "adminUsername": { + "value": "contoso" + }, + "sshPublicKey": { + "value": "password" + }, + "enablePublicIP": { + "value": true + } + } +} \ No newline at end of file diff --git a/Modules/VirtualMachines/2.0/Tests/linux.parameters.json b/Modules/VirtualMachines/2.0/Tests/linux.parameters.json index f6a00b4..7be0184 100755 --- a/Modules/VirtualMachines/2.0/Tests/linux.parameters.json +++ b/Modules/VirtualMachines/2.0/Tests/linux.parameters.json @@ -10,9 +10,9 @@ }, "virtualMachineOSImage": { "value": { - "offer": "WindowsServer", - "publisher": "MicrosoftWindowsServer", - "sku": "2016-Datacenter" + "publisher": "Canonical", + "offer": "UbuntuServer", + "sku": "18.04-LTS" } }, "virtualMachineOSType": { diff --git a/Modules/VirtualMachines/2.0/Tests/module.tests.ps1 b/Modules/VirtualMachines/2.0/Tests/module.tests.ps1 index 7e464d6..1739ba2 100644 --- a/Modules/VirtualMachines/2.0/Tests/module.tests.ps1 +++ b/Modules/VirtualMachines/2.0/Tests/module.tests.ps1 @@ -133,7 +133,11 @@ Describe "Template: $template - Virtual Machine" -Tags Unit { | Sort-Object -Property Name ` | ForEach-Object Name $result = @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}); - Write-Host "Invalid parameters: $(ConvertTo-Json $result)"; + + if ($result.Count -gt 0) { + Write-Host "Invalid parameters: $(ConvertTo-Json $result)" + } + @($allParametersInParametersFile| Where-Object {$allParametersInTemplateFile -notcontains $_}).Count | Should Be 0; } } @@ -153,7 +157,14 @@ Describe "Template: $template - Virtual Machine" -Tags Unit { | Sort-Object -Property Name ` | ForEach-Object Name - @($requiredParametersInTemplateFile | Where-Object {$allParametersInParametersFile -notcontains $_}).Count | Should Be 0; + $invalidParameters = ` + $requiredParametersInTemplateFile | Where-Object {$allParametersInParametersFile -notcontains $_} + + if ($invalidParameters.Count -gt 0) { + Write-Host "Parameters not found: $(ConvertTo-Json $invalidParameters)" + } + + $invalidParameters.Count | Should Be 0 } } } diff --git a/Modules/VirtualMachines/2.0/Tests/windows.enable.pip.parameters.json b/Modules/VirtualMachines/2.0/Tests/windows.enable.pip.parameters.json new file mode 100644 index 0000000..b2c08b1 --- /dev/null +++ b/Modules/VirtualMachines/2.0/Tests/windows.enable.pip.parameters.json @@ -0,0 +1,64 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "virtualMachineName": { + "value": "adds" + }, + "virtualMachineSize": { + "value": "Standard_DS2_v2" + }, + "virtualMachineOSImage": { + "value": { + "offer": "WindowsServer", + "publisher": "MicrosoftWindowsServer", + "sku": "2016-Datacenter" + } + }, + "virtualMachineOSType": { + "value": "Windows" + }, + "workspaceId": { + "value": "00000000-0000-0000-0000-000000000000" + }, + "logAnalyticsWorkspaceId": { + "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup/providers/Microsoft.OperationalInsights/workspaces/contoso-example" + }, + "logAnalyticsWorkspacePrimarySharedKey": { + "value": "" + }, + "diagnosticsStorageAccountId": { + "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup/providers/Microsoft.Storage/storageAccounts/contosostrgexmpl" + }, + "diagnosticsStorageAccountName": { + "value": "contoso-diag-storage" + }, + "diagnosticsStorageAccountSasToken": { + "value": "" + }, + "artifactsStorageAccountName": { + "value": "vdcstorage" + }, + "artifactsStorageAccountKey": { + "value": "" + }, + "artifactsStorageAccountSasKey": { + "value": "" + }, + "vNetId": { + "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup/providers/Microsoft.Network/virtualNetworks/contoso-vnet-example" + }, + "subnetName": { + "value": "sharedsvcs" + }, + "adminUsername": { + "value": "contoso" + }, + "adminPassword": { + "value": "password" + }, + "enablePublicIP": { + "value": true + } + } +} \ No newline at end of file diff --git a/Modules/VirtualMachines/2.0/Tests/windows.join.domain.parameters.json b/Modules/VirtualMachines/2.0/Tests/windows.join.domain.parameters.json new file mode 100644 index 0000000..8a84f22 --- /dev/null +++ b/Modules/VirtualMachines/2.0/Tests/windows.join.domain.parameters.json @@ -0,0 +1,70 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "virtualMachineName": { + "value": "adds" + }, + "virtualMachineSize": { + "value": "Standard_DS2_v2" + }, + "virtualMachineOSImage": { + "value": { + "offer": "WindowsServer", + "publisher": "MicrosoftWindowsServer", + "sku": "2016-Datacenter" + } + }, + "virtualMachineOSType": { + "value": "Windows" + }, + "workspaceId": { + "value": "00000000-0000-0000-0000-000000000000" + }, + "logAnalyticsWorkspaceId": { + "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup/providers/Microsoft.OperationalInsights/workspaces/contoso-example" + }, + "logAnalyticsWorkspacePrimarySharedKey": { + "value": "" + }, + "diagnosticsStorageAccountId": { + "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup/providers/Microsoft.Storage/storageAccounts/contosostrgexmpl" + }, + "diagnosticsStorageAccountName": { + "value": "contoso-diag-storage" + }, + "diagnosticsStorageAccountSasToken": { + "value": "" + }, + "artifactsStorageAccountName": { + "value": "vdcstorage" + }, + "artifactsStorageAccountKey": { + "value": "" + }, + "artifactsStorageAccountSasKey": { + "value": "" + }, + "vNetId": { + "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup/providers/Microsoft.Network/virtualNetworks/contoso-vnet-example" + }, + "subnetName": { + "value": "sharedsvcs" + }, + "adminUsername": { + "value": "contoso" + }, + "adminPassword": { + "value": "password" + }, + "domainAdminUsername": { + "value": "contoso" + }, + "domainAdminPassword": { + "value": "password" + }, + "domainName": { + "value": "contoso.com" + } + } +} \ No newline at end of file diff --git a/Modules/VirtualMachines/2.0/Tests/windows.parameters.json b/Modules/VirtualMachines/2.0/Tests/windows.parameters.json index 91f8069..5d4741a 100644 --- a/Modules/VirtualMachines/2.0/Tests/windows.parameters.json +++ b/Modules/VirtualMachines/2.0/Tests/windows.parameters.json @@ -3,59 +3,74 @@ "contentVersion": "1.0.0.0", "parameters": { "virtualMachineName": { - "value": "adds" - }, - "virtualMachineSize": { - "value": "Standard_DS2_v2" - }, - "virtualMachineOSImage": { - "value": { - "offer": "WindowsServer", - "publisher": "MicrosoftWindowsServer", - "sku": "2016-Datacenter" - } - }, - "virtualMachineOSType": { - "value": "Windows" - }, - "workspaceId": { - "value": "00000000-0000-0000-0000-000000000000" - }, - "logAnalyticsWorkspaceId": { - "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup/providers/Microsoft.OperationalInsights/workspaces/contoso-example" + "value": "joined" }, "logAnalyticsWorkspacePrimarySharedKey": { "value": "" }, - "diagnosticsStorageAccountId": { - "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup/providers/Microsoft.Storage/storageAccounts/contosostrgexmpl" + "workspaceId": { + "value": "00000000-0000-0000-0000-000000000000" + }, + "domainName": { + "value": "fontoso.com" + }, + "virtualMachineOSType": { + "value": "Windows" + }, + "logAnalyticsWorkspaceId": { + "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cntsrg/providers/Microsoft.OperationalInsights/workspaces/shrdsvcs-la" + }, + "adminUsername": { + "value": "admin-user" + }, + "virtualMachineOSImage": { + "value": { + "sku": "2016-Datacenter", + "publisher": "MicrosoftWindowsServer", + "offer": "WindowsServer" + } + }, + "vNetId": { + "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/shrdsvcs-network-rg/providers/Microsoft.Network/virtualNetworks/shrdsvcs-vnet" }, "diagnosticsStorageAccountName": { - "value": "contoso-diag-storage" - }, - "diagnosticsStorageAccountSasToken": { - "value": "" - }, - "artifactsStorageAccountName": { - "value": "vdcstorage" + "value": "shrdsvcsdiag01" }, "artifactsStorageAccountKey": { "value": "" }, + "artifactsStorageAccountName": { + "value": "cstmartfcts01" + }, "artifactsStorageAccountSasKey": { "value": "" }, - "vNetId": { - "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup/providers/Microsoft.Network/virtualNetworks/contoso-vnet-example" + "applicationSecurityGroupId": { + "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/shrdsvcs-network-rg/providers/Microsoft.Network/applicationSecurityGroups/jumpbox-asg" + }, + "domainAdminPassword": { + "value": "foo" + }, + "virtualMachineSize": { + "value": "Standard_DS2_v2" + }, + "diagnosticsStorageAccountSasToken": { + "value": "" }, "subnetName": { - "value": "sharedsvcs" + "value": "shrdsvcs" }, - "adminUsername": { - "value": "contoso" + "domainAdminUsername": { + "value": "fontoso" + }, + "virtualMachineCount": { + "value": 1 + }, + "diagnosticsStorageAccountId": { + "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/shrdsvcs-diagnostics-rg/providers/Microsoft.Storage/storageAccounts/shrdsvcsdiag01" }, "adminPassword": { - "value": "password" + "value": "foo" } } } \ No newline at end of file diff --git a/Modules/VirtualMachines/2.0/deploy.json b/Modules/VirtualMachines/2.0/deploy.json index 6bf8dc1..fb5ec16 100644 --- a/Modules/VirtualMachines/2.0/deploy.json +++ b/Modules/VirtualMachines/2.0/deploy.json @@ -7,14 +7,14 @@ "minLength": 1, "maxLength": 13, "metadata": { - "description": "Required. Name for the ADDS VMs" + "description": "Required. Name for the VMs" } }, "virtualMachineSize": { "type": "string", "defaultValue": "Standard_DS2_v2", "metadata": { - "description": "Optional. Size of the ADDS VMs" + "description": "Optional. Size of the VMs" } }, "virtualMachineOSImage": { @@ -160,7 +160,7 @@ "adminUsername": { "type": "securestring", "metadata": { - "description": "Required. Domain user that has privileges to join a VM into a Domain" + "description": "Required. Administrator username" } }, "adminPassword": { @@ -177,15 +177,51 @@ "description": "Optional. SSH public key. When specifying a Linux Virtual Machine, this value should be passed. Linux VMs can be accessed via SSH public key only." } }, + "proximityPlacementGroupsId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. If passed, the VM will be assigned to a Proximity Placement Groups." + } + }, "location": { "type": "string", "defaultValue": "[resourceGroup().location]", "metadata": { "description": "Optional. Location for all resources." } + }, + "enablePublicIP": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enables the creation of a Public IP and assigns it to the Network Interface." + } + }, + "domainAdminUsername": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Optional. Domain user that has privileges to join a VM into a Domain. If joinToDomain is set to true, this value becomes required." + } + }, + "domainAdminPassword": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Optional. Domain user that has privileges to join a VM into a Domain. If joinToDomain is set to true, this value becomes required." + } + }, + "domainName": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Optional. AD domain name. If joinToDomain is set to true, this value becomes required." + } } }, "variables": { + "joinToDomain": "[and(not(empty(parameters('domainAdminUsername'))), not(empty(parameters('domainAdminPassword'))))]", "uniqueString": "[uniqueString(subscription().id, resourceGroup().id, concat(parameters('virtualMachineName'), '-vm'))]", "subnetName": "[parameters('subnetName')]", "availabilitySetName": "vm-availability-set", @@ -207,6 +243,9 @@ } ] }, + "proximityPlacementGroup": { + "id": "[parameters('proximityPlacementGroupsId')]" + }, "linuxConfiguration": { "disablePasswordAuthentication": true, "ssh": { @@ -225,7 +264,7 @@ "MMAExtensionName": "OMSExtension", "DSCExtensionName": "DSCExtension", "tagPatching": "3rdSat7pm", - "windowsPasswordPoliciesExtensionName": "PwshExtension", + "PwshExtensionName": "PwshExtension", "windowsDependencyExtensionName": "DependencyAgent", "windowsDependencyExtensionPublisher": "Microsoft.Azure.Monitoring.DependencyAgent", "windowsDependencyExtensionType": "DependencyAgentWindows", @@ -261,7 +300,8 @@ "input": "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('virtualMachineName'), copyIndex('vmResourceIds', parameters('virtualMachineOffset'))))]" } ] - } + }, + "artifactsStorageAccountSasToken": "[concat('?', parameters('artifactsStorageAccountSasKey'))]" }, "resources": [ { @@ -279,6 +319,23 @@ "name": "Aligned" } }, + { + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2018-08-01", + "name": "[concat(parameters('virtualMachineName'), copyIndex(parameters('virtualMachineOffset')), '-pip')]", + "location": "[parameters('location')]", + "condition": "[parameters('enablePublicIP')]", + "copy": { + "name": "pipLoop", + "count": "[parameters('virtualMachineCount')]" + }, + "sku": { + "name": "Standard" + }, + "properties": { + "publicIPAllocationMethod": "Static" + } + }, { "type": "Microsoft.Network/networkInterfaces", "apiVersion": "2017-09-01", @@ -287,6 +344,9 @@ "name": "nicLoop", "count": "[parameters('virtualMachineCount')]" }, + "dependsOn": [ + "pipLoop" + ], "name": "[concat(parameters('virtualMachineName'), copyIndex(parameters('virtualMachineOffset')), '-nic')]", "properties": { "ipConfigurations": [ @@ -294,6 +354,7 @@ "name": "ipconfig1", "properties": { "privateIPAllocationMethod": "[if(empty(parameters('vmIPAddress')), 'Dynamic', 'Static')]", + "publicIPAddress": "[if(not(parameters('enablePublicIP')), json('null'), json(concat('{\"id\":\"', resourceId('Microsoft.Network/publicIPAddresses', concat(parameters('virtualMachineName'), copyIndex(parameters('virtualMachineOffset')), '-pip')),'\"}')))]", "privateIPAddress": "[if(empty(parameters('vmIPAddress')), json('null'), vdc.nextIP(parameters('vmIPAddress'), copyIndex()))]", "subnet": { "id": "[variables('subnetId')]" @@ -334,7 +395,7 @@ }, { "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2017-03-30", + "apiVersion": "2018-06-01", "location": "[parameters('location')]", "name": "[concat(parameters('virtualMachineName'), copyIndex(parameters('virtualMachineOffset')))]", "copy": { @@ -347,10 +408,12 @@ "UpdateManagement": "[variables('tagPatching')]" }, "dependsOn": [ - "nicDiagnosticLoop" + "nicDiagnosticLoop", + "[variables('availabilitySetName')]" ], "properties": { "availabilitySet": "[if(not(variables('availabilityZonesEnabled')), variables('availabilitySet'), json('null'))]", + "proximityPlacementGroup": "[if(empty(parameters('proximityPlacementGroupsId')), json('null'), variables('proximityPlacementGroup'))]", "osProfile": { "computerName": "[concat(parameters('virtualMachineName'), copyIndex(parameters('virtualMachineOffset')))]", "adminUsername": "[parameters('adminUsername')]", @@ -378,8 +441,7 @@ { "name": "dataDisks", "count": "[length(parameters('virtualMachineDataDisks'))]", - "input": - { + "input": { "lun": "[copyIndex('dataDisks')]", "name": "[replace(toLower(substring(concat(parameters('virtualMachineName'), copyIndex('vmLoop', parameters('virtualMachineOffset')), '-dsk', copyindex('dataDisks', parameters('virtualMachineOffset')), '-', replace(concat(variables('uniqueString'), variables('uniqueString')), '-', '')), 0, 30)), '-', '')]", "diskSizeGB": "[parameters('virtualMachineDataDisks')[copyIndex('dataDisks')].size]", @@ -476,6 +538,35 @@ } } }, + { + "type": "extensions", + "name": "[variables('DSCExtensionName')]", + "apiVersion": "2017-03-30", + "location": "[parameters('location')]", + "condition": "[equals(parameters('virtualMachineOSType'), 'Windows')]", + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('virtualMachineName'), copyIndex(parameters('virtualMachineOffset'))))]" + ], + "properties": { + "publisher": "Microsoft.Powershell", + "type": "DSC", + "typeHandlerVersion": "2.9", + "autoUpgradeMinorVersion": true, + "settings": { + "configuration": { + "url": "[concat('https://', parameters('artifactsStorageAccountName'), '.blob.core.windows.net/scripts/Windows/formatDataDisks.zip')]", + "script": "formatDisk.ps1", + "function": "FormatDisk" + }, + "configurationArguments": { + "DataDisks": "[string(parameters('virtualMachineDataDisks'))]" + } + }, + "protectedSettings": { + "configurationUrlSasToken": "[variables('artifactsStorageAccountSasToken')]" + } + } + }, { "type": "extensions", "name": "[variables('diagnosticsExtensionName')]", @@ -787,7 +878,7 @@ }, { "type": "extensions", - "name": "[variables('windowsPasswordPoliciesExtensionName')]", + "name": "[variables('PwshExtensionName')]", "apiVersion": "2017-03-30", "location": "[parameters('location')]", "condition": "[equals(parameters('virtualMachineOSType'), 'Windows')]", @@ -819,7 +910,7 @@ "condition": "[equals(parameters('virtualMachineOSType'), 'Windows')]", "dependsOn": [ "[resourceId('Microsoft.Compute/virtualMachines', concat(parameters('virtualMachineName'), copyIndex(parameters('virtualMachineOffset'))))]", - "[variables('windowsPasswordPoliciesExtensionName')]" + "[variables('PwshExtensionName')]" ], "properties": { "publisher": "[variables('windowsDependencyExtensionPublisher')]", @@ -1801,35 +1892,57 @@ ] }, { - "type": "Microsoft.Compute/virtualMachines/extensions", - "name": "[concat(parameters('virtualMachineName'), copyIndex(parameters('virtualMachineOffset')), '/', variables('DSCExtensionName'))]", - "apiVersion": "2017-03-30", - "location": "[parameters('location')]", - "condition": "[not(empty(parameters('virtualMachineDataDisks')))]", + "name": "[concat('JoinToDomain-VM', copyIndex(parameters('virtualMachineOffset')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2016-09-01", + "condition": "[and(equals(parameters('virtualMachineOSType'), 'Windows'), variables('joinToDomain'))]", "dependsOn": [ "vmLoop" ], "copy": { - "name": "vmFormatDataDiskLoop", + "name": "vmWindowsJoinDomainLoop", "count": "[parameters('virtualMachineCount')]" }, "properties": { - "publisher": "Microsoft.Powershell", - "type": "DSC", - "typeHandlerVersion": "2.9", - "autoUpgradeMinorVersion": true, - "settings": { - "configuration": { - "url": "[concat('https://', parameters('artifactsStorageAccountName'), '.blob.core.windows.net/scripts/Windows/formatDataDisks.zip')]", - "script": "formatDisk.ps1", - "function": "FormatDisk" - }, - "configurationArguments": { - "DataDisks": "[string(parameters('virtualMachineDataDisks'))]" - } - }, - "protectedSettings": { - "configurationUrlSasToken": "[concat('?', parameters('artifactsStorageAccountSasKey'))]" + "mode": "Incremental", + "template":{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "resources": [ + { + "type": "Microsoft.Compute/virtualMachines/extensions", + "name": "[concat(parameters('virtualMachineName'), copyIndex(parameters('virtualMachineOffset')), '/', variables('DSCExtensionName'))]", + "apiVersion": "2017-03-30", + "location": "[parameters('location')]", + "properties": { + "publisher": "Microsoft.Powershell", + "type": "DSC", + "typeHandlerVersion": "2.9", + "autoUpgradeMinorVersion": true, + "settings": { + "configuration": { + "url": "[concat('https://', parameters('artifactsStorageAccountName'), '.blob.core.windows.net/scripts/Windows/joinComputerToDomain.zip')]", + "script": "joinComputerToDomain.ps1", + "function": "JoinComputerToDomain" + }, + "configurationArguments": { + "DomainName": "[parameters('domainName')]", + "ServerName": "[concat(parameters('virtualMachineName'), copyIndex(parameters('virtualMachineOffset')))]" + } + }, + "protectedSettings": { + "configurationUrlSasToken": "[variables('artifactsStorageAccountSasToken')]", + "configurationArguments": { + "AdminCreds": { + "UserName": "[parameters('domainAdminUsername')]", + "Password": "[parameters('domainAdminPassword')]" + } + } + } + } + } + ] } } } diff --git a/Orchestration/Bootstrap/Initialize.ps1 b/Orchestration/Bootstrap/Initialize.ps1 index 463ea83..3560355 100644 --- a/Orchestration/Bootstrap/Initialize.ps1 +++ b/Orchestration/Bootstrap/Initialize.ps1 @@ -218,9 +218,10 @@ Class Initialize { # Let's check if the life of the sas token expires within an hour # if it does, let's get a new sas token - if($storageAccountDetails.ExpiryTime -le ` - ((Get-Date) - $oneHourDuration)) { + if(($storageAccountDetails.ExpiryTime - (Get-Date)) -le $oneHourDuration) { + Write-Debug "Obtaining new SAS Token, previous expired" + # Setting AZ context to be able to retrieve the proper # SAS token, there are situations where the toolkit # subscription is different than the one from the @@ -234,6 +235,7 @@ Class Initialize { $this.dataStoreName, $this.dataStoreResourceGroupName); + Write-Debug "Sas token acquired, new expiriy time is: $($storageAccountDetails.ExpiryTime)" $storageAccountDetails.StorageAccountSasToken = ` $sasToken.SASToken; $storageAccountDetails.ExpiryTime = ` @@ -275,17 +277,20 @@ Class Initialize { [string] $storageAccountResourceGroup) { try { + Write-Host "Creating a storage account: $storageAccountName in resource group: $storageAccountResourceGroup" $storageAccountAccessKey = $null; $storageAccountAccessKeys = ` (Get-AzStorageAccountKey ` -ResourceGroupName $this.dataStoreResourceGroupName ` -Name $this.dataStoreName).Value; + # Set SAS Token expiration of 2 hours $twoHoursDuration = New-TimeSpan -Hours 3; $expiryTime = (Get-Date) + $twoHoursDuration; if($null -ne $storageAccountAccessKeys) { + Write-Host "Keys acquired successfully" $storageAccountAccessKey = ` $storageAccountAccessKeys[0]; diff --git a/Orchestration/DataService/Implementations/ModuleStateDataService.ps1 b/Orchestration/DataService/Implementations/ModuleStateDataService.ps1 index 7aafe90..753dfc0 100644 --- a/Orchestration/DataService/Implementations/ModuleStateDataService.ps1 +++ b/Orchestration/DataService/Implementations/ModuleStateDataService.ps1 @@ -82,9 +82,8 @@ Class ModuleStateDataService: IModuleStateDataService { $this.stateRepository.GetLatestDeploymentMapping($filters); # If deploymentMapping is null, it means that there is no mapping found - if (!$deploymentMapping) { - Write-Debug "No state information found"; - return $null; + if ($null -eq $deploymentMapping) { + throw "No state information found, make sure that the module definition: $moduleInstanceName in the deployment instance: $archetypeInstanceName was deployed."; } else { # deployment mapping found, let's get the deployment outputs diff --git a/Orchestration/IntegrationService/Implementations/AzureResourceManagerDeploymentService.ps1 b/Orchestration/IntegrationService/Implementations/AzureResourceManagerDeploymentService.ps1 index 382ffaf..f454951 100644 --- a/Orchestration/IntegrationService/Implementations/AzureResourceManagerDeploymentService.ps1 +++ b/Orchestration/IntegrationService/Implementations/AzureResourceManagerDeploymentService.ps1 @@ -365,6 +365,13 @@ Class AzureResourceManagerDeploymentService: IDeploymentService { $tokenCache = $context.TokenCache; $cacheItems = $tokenCache.ReadItems(); $accessToken = ''; + + # Let's filter based on management endpoint (resource + # management) + $cacheItems = + $cacheItems | ` + Where-Object -Property "Resource" -Like "*management*" + $cacheItems | ForEach-Object { # Cache Items object's TenantId is null when run in # an AzDO Agent diff --git a/Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1 b/Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1 index 8423860..b7414e0 100644 --- a/Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1 +++ b/Orchestration/OrchestrationService/ModuleConfigurationDeployment.ps1 @@ -6,7 +6,7 @@ [Parameter(Mandatory=$true)] [string] $DefinitionPath, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory=$false)] [string] $ModuleConfigurationName, [Parameter(Mandatory=$false)] @@ -49,7 +49,7 @@ Function New-Deployment { [Parameter(Mandatory=$true)] [string] $DefinitionPath, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory=$false)] [string] $ModuleConfigurationName, [Parameter(Mandatory=$false)] @@ -108,322 +108,350 @@ Function New-Deployment { -ArchetypeInstance $archetypeInstanceJson ` -ArchetypeInstanceName $ArchetypeInstanceName; - $moduleConfiguration = ` - Get-ModuleConfiguration ` - -ArchetypeInstanceJson $archetypeInstanceJson ` - -ModuleConfigurationName $moduleConfigurationName ` - -ArchetypeInstanceName $ArchetypeInstanceName ` - -Operation @{ "False" = "deploy"; "True" = "validate"; }[$Validate.ToString()];; + $allModules = @() - if ($null -eq $moduleConfiguration) { - throw "Module configuration not found for module name: $moduleConfigurationName"; - } - - Write-Debug "Module instance is: $(ConvertTo-Json $moduleConfiguration)"; - - # Let's make sure we use the updated name - # There are instances when we have a module configuration updating an existing - # module configuration that was already deployed, in this case, let's use - # the name of the existing module configuration. - Write-Debug "Updating module instance name from $ModuleConfigurationName to $($moduleConfiguration.Name)"; - $ModuleConfigurationName = ` - $moduleConfiguration.Name; - - $subscriptionInformation = $null; - $subscriptionInformation = ` - Get-SubscriptionInformation ` - -ArchetypeInstanceJson $archetypeInstanceJson ` - -SubscriptionName $archetypeInstanceJson.Parameters.Subscription ` - -ModuleConfiguration $moduleConfiguration; - - if ($null -eq $subscriptionInformation) { - throw "Subscription: $($archetypeInstanceJson.Parameters.Subscription) not found"; - } - - # Let's get the current subscription context - $sub = Get-AzContext | Select-Object Subscription - - # Do not change the subscription context if the operation is validate. - # This is because the script will expect the validation resource - # group to be present in all the subscriptions we are deploying. - [Guid]$subscriptionCheck = [Guid]::Empty; - [Guid]$tenantIdCheck = [Guid]::Empty; - if($null -ne $subscriptionInformation -and ` - [Guid]::TryParse($subscriptionInformation.SubscriptionId, [ref]$subscriptionCheck) -and ` - [Guid]::TryParse($subscriptionInformation.TenantId, [ref]$tenantIdCheck) -and ` - $subscriptionCheck -ne [Guid]::Empty -and ` - $tenantIdCheck -ne [Guid]::Empty -and - $subscriptionCheck -ne $sub.Subscription.Id) { - Write-Debug "Setting subscription context"; - - Set-SubscriptionContext ` - -SubscriptionId $subscriptionInformation.SubscriptionId ` - -TenantId $subscriptionInformation.TenantId; - } - - # Let's attempt to get the Audit Id from cache - $auditCacheKey = ` - "{0}_AuditId" -f ` - $ArchetypeInstanceName; - - Write-Debug "Audit Id cache key is: $auditCacheKey"; - - $auditId = ` - Get-ItemFromCache ` - -Key $auditCacheKey; - - Write-Debug "Audit Id from cache is: $auditId" - - # If no value is found, let's create - # deployment audit information and cache - # the auditId value - if ($null -eq $auditId) { - # Store deployment audit information - - $auditInformation = ` - Get-AzureDevOpsAuditEnvironmentVariables; - - $auditId = ` - New-DeploymentAuditInformation ` - -BuildId $auditInformation.BuildId ` - -BuildName $auditInformation.BuildName ` - -CommitId $auditInformation.CommitId ` - -CommitMessage $auditInformation.CommitMessage ` - -CommitUsername $auditInformation.CommitUsername ` - -BuildQueuedBy $auditInformation.BuildQueuedBy ` - -ReleaseId $auditInformation.ReleaseId ` - -ReleaseName $auditInformation.ReleaseName ` - -ReleaseRequestedFor $auditInformation.ReleaseRequestedFor ` - -TenantId @("",$subscriptionInformation.TenantId)[$null -ne $subscriptionInformation] ` - -SubscriptionId @("", $subscriptionInformation.SubscriptionId)[$null -ne $subscriptionInformation] ` - -ArchetypeInstance $archetypeInstanceJson ` - -ArchetypeInstanceName $ArchetypeInstanceName ` - -Validate:$($Validate.IsPresent); - Write-Debug "Audit trail created, Id: $auditId"; + if ([string]::IsNullOrEmpty($ModuleConfigurationName)) { - Add-ItemToCache ` - -Key $auditCacheKey ` - -Value $auditId ` - -Validate:$($Validate.IsPresent); - Write-Debug "Audit Id succesfully cached."; - } + $topologicalSortRootPath = ` + Join-Path $rootPath -ChildPath 'TopologicalSort'; + + Invoke-Command -ScriptBlock { dotnet build $topologicalSortRootPath --configuration Release --output ./ } - # Runs a custom script only if Script property is present and -        # we are not in Validation mode -        if($null -ne $ModuleConfiguration.Script ` -            -and ` -           $null -ne $ModuleConfiguration.Script.Command) { -  -            # Orchestrate the deployment of Custom Scripts -            $result = ` -                New-CustomScripts ` -                    -ModuleConfiguration $moduleConfiguration ` -                    -ArchetypeInstanceJson $archetypeInstanceJson ` - -Validate:$($Validate.IsPresent); -  -            # Retrieve the results from the script deployment -            $resourceState = $result[0]; -  -            # Did the ArchetypeInstanceJson change? -            if($null -ne $result[1]) { -                # Set the ArchetypeInstanceJson only if it is -                # modified by the custom script deployment -                $archetypeInstanceJson = $result[1]; -  -                # Re-cache the ArchetypeInstanceJson -                Add-ItemToCache ` -                    -Key $ArchetypeInstanceName ` -                    -Value $archetypeInstanceJson ` - -Validate:$($Validate.IsPresent); -            }      + $topologicalSortAssemblyPath = ` + Join-Path $topologicalSortRootPath "TopologicalSort.dll" + + Add-Type -Path $topologicalSortAssemblyPath + + $graph = [VDC.Core.DirectedGraph]::new() + $orchestrationJson = ` + ConvertTo-Json $archetypeInstanceJson.Orchestration.ModuleConfigurations + $graph.Generate($orchestrationJson) + $graph.DFS() + $graph.TopologicalSort | ForEach-Object { $allModules += $_.Name } } else { + $allModules += $ModuleConfigurationName + } + + foreach($ModuleConfigurationName in $allModules) { + Write-Host "Deploying Module: $ModuleConfigurationName" + $moduleConfiguration = ` + Get-ModuleConfiguration ` + -ArchetypeInstanceJson $archetypeInstanceJson ` + -ModuleConfigurationName $moduleConfigurationName ` + -ArchetypeInstanceName $ArchetypeInstanceName ` + -Operation @{ "False" = "deploy"; "True" = "validate"; }[$Validate.ToString()];; - # Let's get the module's template information first, - # this template will dictate if is a resource group or - # subscription deployment based on the template's schema - $moduleConfigurationDeploymentInformation = ` - Get-DeploymentTemplateFileContents ` - -DeploymentConfiguration $moduleConfiguration.Deployment ` - -ModuleConfigurationsPath $archetypeInstanceJson.Orchestration.ModuleConfigurationsPath ` - -WorkingDirectory $defaultWorkingDirectory; + if ($null -eq $moduleConfiguration) { + throw "Module configuration not found for module name: $moduleConfigurationName"; + } - $moduleConfigurationDeploymentParameters = $null; + Write-Debug "Module instance is: $(ConvertTo-Json $moduleConfiguration)"; - $isSubscriptionDeployment = $false; + # Let's make sure we use the updated name + # There are instances when we have a module configuration updating an existing + # module configuration that was already deployed, in this case, let's use + # the name of the existing module configuration. + Write-Debug "Updating module instance name from $ModuleConfigurationName to $($moduleConfiguration.Name)"; + $ModuleConfigurationName = ` + $moduleConfiguration.Name; - if($null -ne $moduleConfigurationDeploymentInformation) { + $subscriptionInformation = $null; + $subscriptionInformation = ` + Get-SubscriptionInformation ` + -ArchetypeInstanceJson $archetypeInstanceJson ` + -SubscriptionName $archetypeInstanceJson.Parameters.Subscription ` + -ModuleConfiguration $moduleConfiguration; + + if ($null -eq $subscriptionInformation) { + throw "Subscription: $($archetypeInstanceJson.Parameters.Subscription) not found"; + } + + # Let's get the current subscription context + $sub = Get-AzContext | Select-Object Subscription + + # Do not change the subscription context if the operation is validate. + # This is because the script will expect the validation resource + # group to be present in all the subscriptions we are deploying. + [Guid]$subscriptionCheck = [Guid]::Empty; + [Guid]$tenantIdCheck = [Guid]::Empty; + if($null -ne $subscriptionInformation -and ` + [Guid]::TryParse($subscriptionInformation.SubscriptionId, [ref]$subscriptionCheck) -and ` + [Guid]::TryParse($subscriptionInformation.TenantId, [ref]$tenantIdCheck) -and ` + $subscriptionCheck -ne [Guid]::Empty -and ` + $tenantIdCheck -ne [Guid]::Empty -and + $subscriptionCheck -ne $sub.Subscription.Id) { - $moduleConfigurationDeploymentTemplate = ` - $moduleConfigurationDeploymentInformation.Template; + Write-Debug "Setting subscription context"; + + Set-SubscriptionContext ` + -SubscriptionId $subscriptionInformation.SubscriptionId ` + -TenantId $subscriptionInformation.TenantId; + } + + # Let's attempt to get the Audit Id from cache + $auditCacheKey = ` + "{0}_AuditId" -f ` + $ArchetypeInstanceName; + + Write-Debug "Audit Id cache key is: $auditCacheKey"; + + $auditId = ` + Get-ItemFromCache ` + -Key $auditCacheKey; + + Write-Debug "Audit Id from cache is: $auditId" + + # If no value is found, let's create + # deployment audit information and cache + # the auditId value + if ($null -eq $auditId) { + # Store deployment audit information + + $auditInformation = ` + Get-AzureDevOpsAuditEnvironmentVariables; + + $auditId = ` + New-DeploymentAuditInformation ` + -BuildId $auditInformation.BuildId ` + -BuildName $auditInformation.BuildName ` + -CommitId $auditInformation.CommitId ` + -CommitMessage $auditInformation.CommitMessage ` + -CommitUsername $auditInformation.CommitUsername ` + -BuildQueuedBy $auditInformation.BuildQueuedBy ` + -ReleaseId $auditInformation.ReleaseId ` + -ReleaseName $auditInformation.ReleaseName ` + -ReleaseRequestedFor $auditInformation.ReleaseRequestedFor ` + -TenantId @("",$subscriptionInformation.TenantId)[$null -ne $subscriptionInformation] ` + -SubscriptionId @("", $subscriptionInformation.SubscriptionId)[$null -ne $subscriptionInformation] ` + -ArchetypeInstance $archetypeInstanceJson ` + -ArchetypeInstanceName $ArchetypeInstanceName ` + -Validate:$($Validate.IsPresent); + Write-Debug "Audit trail created, Id: $auditId"; - # Let's get the information if is a subscription - # level deployment or resource group level deployment - $isSubscriptionDeployment = ` - $moduleConfigurationDeploymentInformation.IsSubscriptionDeployment; + Add-ItemToCache ` + -Key $auditCacheKey ` + -Value $auditId ` + -Validate:$($Validate.IsPresent); + Write-Debug "Audit Id succesfully cached."; + } - Write-Debug "Deployment template contents is: $moduleConfigurationDeploymentTemplate"; + # Runs a custom script only if Script property is present and +         # we are not in Validation mode +         if($null -ne $ModuleConfiguration.Script ` +             -and ` +            $null -ne $ModuleConfiguration.Script.Command) { +   +             # Orchestrate the deployment of Custom Scripts +             $result = ` +                 New-CustomScripts ` +                     -ModuleConfiguration $moduleConfiguration ` +                     -ArchetypeInstanceJson $archetypeInstanceJson ` + -Validate:$($Validate.IsPresent); +   +             # Retrieve the results from the script deployment +             $resourceState = $result[0]; +   +             # Did the ArchetypeInstanceJson change? +             if($null -ne $result[1]) { +                 # Set the ArchetypeInstanceJson only if it is +                 # modified by the custom script deployment +                 $archetypeInstanceJson = $result[1]; +   +                 # Re-cache the ArchetypeInstanceJson +                 Add-ItemToCache ` +                     -Key $ArchetypeInstanceName ` +                     -Value $archetypeInstanceJson ` + -Validate:$($Validate.IsPresent); +             }      + } + else { - # If a module deployment template exists, - # let's get the deployment parameters. - $moduleConfigurationDeploymentParameters = ` - Get-DeploymentParametersFileContents ` + # Let's get the module's template information first, + # this template will dictate if is a resource group or + # subscription deployment based on the template's schema + $moduleConfigurationDeploymentInformation = ` + Get-DeploymentTemplateFileContents ` -DeploymentConfiguration $moduleConfiguration.Deployment ` -ModuleConfigurationsPath $archetypeInstanceJson.Orchestration.ModuleConfigurationsPath ` -WorkingDirectory $defaultWorkingDirectory; - } - else { - throw "No Resource Manager template found under Deployment."; - } - Write-Debug "Is a subscription deployment: $isSubscriptionDeployment"; + $moduleConfigurationDeploymentParameters = $null; + + $isSubscriptionDeployment = $false; - $moduleConfigurationResourceGroupName = ""; + if($null -ne $moduleConfigurationDeploymentInformation) { + + $moduleConfigurationDeploymentTemplate = ` + $moduleConfigurationDeploymentInformation.Template; + + # Let's get the information if is a subscription + # level deployment or resource group level deployment + $isSubscriptionDeployment = ` + $moduleConfigurationDeploymentInformation.IsSubscriptionDeployment; - # If we are not in a subscription deployment - # proceed to create a resource group - if ($null -ne $subscriptionInformation -and ` - -not $isSubscriptionDeployment) { - $moduleConfigurationResourceGroupName = ` - Get-ResourceGroupName ` - -ArchetypeInstanceName $ArchetypeInstanceName ` - -ModuleConfiguration $moduleConfiguration; - Write-Debug "Resource Group is: $moduleConfigurationResourceGroupName"; - - New-ResourceGroup ` - -ResourceGroupName $moduleConfigurationResourceGroupName ` - -ResourceGroupLocation $subscriptionInformation.Location ` - -Validate:$($Validate.IsPresent); - Write-Debug "Resource Group successfully created"; - } + Write-Debug "Deployment template contents is: $moduleConfigurationDeploymentTemplate"; - # Now continue deploying Policies, RBAC and finally - # the module template - $moduleConfigurationPolicyDeploymentTemplate = ` - Get-PolicyDeploymentTemplateFileContents ` - -DeploymentConfiguration $moduleConfiguration.Policies ` - -ModuleConfigurationsPath $archetypeInstanceJson.Orchestration.ModuleConfigurationsPath ` - -WorkingDirectory $defaultWorkingDirectory; - Write-Debug "Policy Deployment template contents is: $moduleConfigurationPolicyDeploymentTemplate"; + # If a module deployment template exists, + # let's get the deployment parameters. + $moduleConfigurationDeploymentParameters = ` + Get-DeploymentParametersFileContents ` + -DeploymentConfiguration $moduleConfiguration.Deployment ` + -ModuleConfigurationsPath $archetypeInstanceJson.Orchestration.ModuleConfigurationsPath ` + -WorkingDirectory $defaultWorkingDirectory; + } + else { + throw "No Resource Manager template found under Deployment."; + } - $moduleConfigurationPolicyDeploymentParameters = ` - Get-PolicyDeploymentParametersFileContents ` - -DeploymentConfiguration $moduleConfiguration.Policies ` - -ModuleConfigurationsPath $archetypeInstanceJson.Orchestration.ModuleConfigurationsPath ` - -WorkingDirectory $defaultWorkingDirectory; - Write-Debug "Policy Deployment parameters contents is: $moduleConfigurationPolicyDeploymentParameters"; + Write-Debug "Is a subscription deployment: $isSubscriptionDeployment"; - $policyResourceState = @{}; + $moduleConfigurationResourceGroupName = ""; - if ($null -ne $moduleConfigurationPolicyDeploymentTemplate) { + # If we are not in a subscription deployment + # proceed to create a resource group + if ($null -ne $subscriptionInformation -and ` + -not $isSubscriptionDeployment) { + $moduleConfigurationResourceGroupName = ` + Get-ResourceGroupName ` + -ArchetypeInstanceName $ArchetypeInstanceName ` + -ModuleConfiguration $moduleConfiguration; + Write-Debug "Resource Group is: $moduleConfigurationResourceGroupName"; + + New-ResourceGroup ` + -ResourceGroupName $moduleConfigurationResourceGroupName ` + -ResourceGroupLocation $subscriptionInformation.Location ` + -Validate:$($Validate.IsPresent); + Write-Debug "Resource Group successfully created"; + } + + # Now continue deploying Policies, RBAC and finally + # the module template + $moduleConfigurationPolicyDeploymentTemplate = ` + Get-PolicyDeploymentTemplateFileContents ` + -DeploymentConfiguration $moduleConfiguration.Policies ` + -ModuleConfigurationsPath $archetypeInstanceJson.Orchestration.ModuleConfigurationsPath ` + -WorkingDirectory $defaultWorkingDirectory; + Write-Debug "Policy Deployment template contents is: $moduleConfigurationPolicyDeploymentTemplate"; + + $moduleConfigurationPolicyDeploymentParameters = ` + Get-PolicyDeploymentParametersFileContents ` + -DeploymentConfiguration $moduleConfiguration.Policies ` + -ModuleConfigurationsPath $archetypeInstanceJson.Orchestration.ModuleConfigurationsPath ` + -WorkingDirectory $defaultWorkingDirectory; + Write-Debug "Policy Deployment parameters contents is: $moduleConfigurationPolicyDeploymentParameters"; + + $policyResourceState = @{}; + + if ($null -ne $moduleConfigurationPolicyDeploymentTemplate) { + Write-Debug "About to trigger a deployment"; + $policyResourceState = ` + New-AzureResourceManagerDeployment ` + -TenantId $subscriptionInformation.TenantId ` + -SubscriptionId $subscriptionInformation.SubscriptionId ` + -ResourceGroupName $moduleConfigurationResourceGroupName ` + -DeploymentTemplate $moduleConfigurationPolicyDeploymentTemplate ` + -DeploymentParameters $moduleConfigurationPolicyDeploymentParameters ` + -ModuleConfiguration $moduleConfiguration.Policies ` + -ArchetypeInstanceName $ArchetypeInstanceName ` + -Location $subscriptionInformation.Location ` + -Validate:$($Validate.IsPresent); + Write-Debug "Deployment complete, Resource state is: $(ConvertTo-Json -Compress $policyResourceState)"; + } + else { + Write-Debug "No Policy deployment"; + } + + $moduleConfigurationRBACDeploymentTemplate = ` + Get-RbacDeploymentTemplateFileContents ` + -DeploymentConfiguration $moduleConfiguration.RBAC ` + -ModuleConfigurationsPath $archetypeInstanceJson.Orchestration.ModuleConfigurationsPath ` + -WorkingDirectory $defaultWorkingDirectory; + Write-Debug "RBAC Deployment template contents is: $moduleConfigurationRBACDeploymentTemplate"; + + $moduleConfigurationRBACDeploymentParameters = ` + Get-RbacDeploymentParametersFileContents ` + -DeploymentConfiguration $moduleConfiguration.RBAC ` + -ModuleConfigurationsPath $archetypeInstanceJson.Orchestration.ModuleConfigurationsPath ` + -WorkingDirectory $defaultWorkingDirectory; + Write-Debug "RBAC Deployment parameters contents is: $moduleConfigurationRBACDeploymentParameters"; + + $rbacResourceState = @{}; + + if ($null -ne $moduleConfigurationRBACDeploymentTemplate) { Write-Debug "About to trigger a deployment"; - $policyResourceState = ` - New-AzureResourceManagerDeployment ` - -TenantId $subscriptionInformation.TenantId ` - -SubscriptionId $subscriptionInformation.SubscriptionId ` - -ResourceGroupName $moduleConfigurationResourceGroupName ` - -DeploymentTemplate $moduleConfigurationPolicyDeploymentTemplate ` - -DeploymentParameters $moduleConfigurationPolicyDeploymentParameters ` - -ModuleConfiguration $moduleConfiguration.Policies ` - -ArchetypeInstanceName $ArchetypeInstanceName ` - -Location $subscriptionInformation.Location ` - -Validate:$($Validate.IsPresent); - Write-Debug "Deployment complete, Resource state is: $(ConvertTo-Json -Compress $policyResourceState)"; + $rbacResourceState = ` + New-AzureResourceManagerDeployment ` + -TenantId $subscriptionInformation.TenantId ` + -SubscriptionId $subscriptionInformation.SubscriptionId ` + -ResourceGroupName $moduleConfigurationResourceGroupName ` + -DeploymentTemplate $moduleConfigurationRBACDeploymentTemplate ` + -DeploymentParameters $moduleConfigurationRBACDeploymentParameters ` + -ModuleConfiguration $moduleConfiguration.RBAC ` + -ArchetypeInstanceName $ArchetypeInstanceName ` + -Location $subscriptionInformation.Location ` + -Validate:$($Validate.IsPresent); + Write-Debug "Deployment complete, Resource state is: $(ConvertTo-Json -Compress $rbacResourceState)"; + } + else { + Write-Debug "No RBAC deployment"; + } + + # This deployment runs last because it could be + # a Subscription or Resource Group level deployment + if ($null -ne $moduleConfigurationDeploymentTemplate) { + Write-Debug "About to trigger a deployment"; + $resourceState = ` + New-AzureResourceManagerDeployment ` + -TenantId $subscriptionInformation.TenantId ` + -SubscriptionId $subscriptionInformation.SubscriptionId ` + -ResourceGroupName $moduleConfigurationResourceGroupName ` + -DeploymentTemplate $moduleConfigurationDeploymentTemplate ` + -DeploymentParameters $moduleConfigurationDeploymentParameters ` + -ModuleConfiguration $moduleConfiguration.Deployment ` + -ArchetypeInstanceName $ArchetypeInstanceName ` + -Location $subscriptionInformation.Location ` + -Validate:$($Validate.IsPresent); + Write-Debug "Deployment complete, Resource state is: $(ConvertTo-Json -Compress $resourceState)"; + } } - else { - Write-Debug "No Policy deployment"; + + # If there are deployment outputs, cache the values + if ($null -ne $resourceState.DeploymentOutputs) { + + Add-OutputsToCache ` + -ModuleConfigurationName $moduleConfigurationName ` + -Outputs $resourceState.DeploymentOutputs ` + -Validate:$($Validate.IsPresent); } - $moduleConfigurationRBACDeploymentTemplate = ` - Get-RbacDeploymentTemplateFileContents ` - -DeploymentConfiguration $moduleConfiguration.RBAC ` - -ModuleConfigurationsPath $archetypeInstanceJson.Orchestration.ModuleConfigurationsPath ` - -WorkingDirectory $defaultWorkingDirectory; - Write-Debug "RBAC Deployment template contents is: $moduleConfigurationRBACDeploymentTemplate"; - - $moduleConfigurationRBACDeploymentParameters = ` - Get-RbacDeploymentParametersFileContents ` - -DeploymentConfiguration $moduleConfiguration.RBAC ` - -ModuleConfigurationsPath $archetypeInstanceJson.Orchestration.ModuleConfigurationsPath ` - -WorkingDirectory $defaultWorkingDirectory; - Write-Debug "RBAC Deployment parameters contents is: $moduleConfigurationRBACDeploymentParameters"; - - $rbacResourceState = @{}; - - if ($null -ne $moduleConfigurationRBACDeploymentTemplate) { - Write-Debug "About to trigger a deployment"; - $rbacResourceState = ` - New-AzureResourceManagerDeployment ` - -TenantId $subscriptionInformation.TenantId ` - -SubscriptionId $subscriptionInformation.SubscriptionId ` - -ResourceGroupName $moduleConfigurationResourceGroupName ` - -DeploymentTemplate $moduleConfigurationRBACDeploymentTemplate ` - -DeploymentParameters $moduleConfigurationRBACDeploymentParameters ` - -ModuleConfiguration $moduleConfiguration.RBAC ` - -ArchetypeInstanceName $ArchetypeInstanceName ` - -Location $subscriptionInformation.Location ` - -Validate:$($Validate.IsPresent); - Write-Debug "Deployment complete, Resource state is: $(ConvertTo-Json -Compress $rbacResourceState)"; - } - else { - Write-Debug "No RBAC deployment"; - } - - # This deployment runs last because it could be - # a Subscription or Resource Group level deployment - if ($null -ne $moduleConfigurationDeploymentTemplate) { - Write-Debug "About to trigger a deployment"; - $resourceState = ` - New-AzureResourceManagerDeployment ` - -TenantId $subscriptionInformation.TenantId ` - -SubscriptionId $subscriptionInformation.SubscriptionId ` - -ResourceGroupName $moduleConfigurationResourceGroupName ` - -DeploymentTemplate $moduleConfigurationDeploymentTemplate ` - -DeploymentParameters $moduleConfigurationDeploymentParameters ` - -ModuleConfiguration $moduleConfiguration.Deployment ` - -ArchetypeInstanceName $ArchetypeInstanceName ` - -Location $subscriptionInformation.Location ` - -Validate:$($Validate.IsPresent); - Write-Debug "Deployment complete, Resource state is: $(ConvertTo-Json -Compress $resourceState)"; - } + # Store deployment state information + $moduleStateId = ` + New-DeploymentStateInformation ` + -AuditId $auditId ` + -DeploymentId $resourceState.DeploymentId ` + -DeploymentName $resourceState.DeploymentName ` + -ArchetypeInstanceName $ArchetypeInstanceName ` + -ModuleConfigurationName $moduleConfigurationName ` + -ResourceStates $resourceState.ResourceStates ` + -ResourceIds $resourceState.ResourceIds ` + -ResourceGroupName $resourceState.ResourceGroupName ` + -DeploymentTemplate $resourceState.DeploymentTemplate ` + -DeploymentParameters $resourceState.DeploymentParameters ` + -DeploymentOutputs $resourceState.DeploymentOutputs ` + -TenantId @("", $subscriptionInformation.TenantId)[$null -ne $subscriptionInformation] ` + -SubscriptionId @("", $subscriptionInformation.SubscriptionId)[$null -ne $subscriptionInformation] ` + -Policies $policyResourceState ` + -RBAC $rbacResourceState ` + -Validate:$($Validate.IsPresent); + Write-Debug "Module state created, Id: $($moduleStateId)"; } - - # If there are deployment outputs, cache the values - if ($null -ne $resourceState.DeploymentOutputs) { - - Add-OutputsToCache ` - -ModuleConfigurationName $moduleConfigurationName ` - -Outputs $resourceState.DeploymentOutputs ` - -Validate:$($Validate.IsPresent); - } - - # Store deployment state information - $moduleStateId = ` - New-DeploymentStateInformation ` - -AuditId $auditId ` - -DeploymentId $resourceState.DeploymentId ` - -DeploymentName $resourceState.DeploymentName ` - -ArchetypeInstanceName $ArchetypeInstanceName ` - -ModuleConfigurationName $moduleConfigurationName ` - -ResourceStates $resourceState.ResourceStates ` - -ResourceIds $resourceState.ResourceIds ` - -ResourceGroupName $resourceState.ResourceGroupName ` - -DeploymentTemplate $resourceState.DeploymentTemplate ` - -DeploymentParameters $resourceState.DeploymentParameters ` - -DeploymentOutputs $resourceState.DeploymentOutputs ` - -TenantId @("", $subscriptionInformation.TenantId)[$null -ne $subscriptionInformation] ` - -SubscriptionId @("", $subscriptionInformation.SubscriptionId)[$null -ne $subscriptionInformation] ` - -Policies $policyResourceState ` - -RBAC $rbacResourceState ` - -Validate:$($Validate.IsPresent); - Write-Debug "Module state created, Id: $($moduleStateId)"; } catch { Write-Host "An error ocurred while running New-Deployment"; $errorMessage = ` $(Get-Exception -ErrorObject $_); - Write-Host $errorMessage; - throw $errorMessage; + Write-Error $errorMessage; } } @@ -986,8 +1014,8 @@ Function Get-SubscriptionInformation { $subscriptionNameMatch = ` $ArchetypeInstanceJson.$archetypeInstanceSubscriptions.Keys ` -match $SubscriptionName; - if($archetypeInstanceSubscriptions ` - -and $subscriptionNameMatch) { + if($null -ne $archetypeInstanceSubscriptions ` + -and $null -ne $subscriptionNameMatch) { # Retrieve case-sensitive key name from case-insensitive key name using match operation $SubscriptionName = $subscriptionNameMatch[0]; $subscriptionInformation = ` diff --git a/Orchestration/OrchestrationService/TopologicalSort/DirectedGraph.cs b/Orchestration/OrchestrationService/TopologicalSort/DirectedGraph.cs new file mode 100644 index 0000000..e17a440 --- /dev/null +++ b/Orchestration/OrchestrationService/TopologicalSort/DirectedGraph.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using VDC.Core.Models; + +namespace VDC.Core +{ + public class DirectedGraph + { + public List Vertices { get; } = new List(); + public List TopologicalSort { get; private set; } = new List(); + private LinkedList _TopologicalSort { get; } = new LinkedList(); + int Time { get; set; } + public int GraphSize + { + get + { + return Vertices.Count; + } + } + + public void Generate(string jsonGraph) + { + try + { + var moduleConfigurations = + JsonConvert.DeserializeObject>( + jsonGraph); + + foreach (var moduleConfiguration in moduleConfigurations.Where(m => m.Enabled)) + { + // Add to vertices list + var vertex = new Vertex(moduleConfiguration.Name) + { + Color = Color.White + }; + + AddVertex(vertex); + } + + foreach (var moduleConfiguration in moduleConfigurations.Where(m => m.Enabled)) + { + // Let's analyze the dependencies and add them + // as an edge of the parent vertex + if(moduleConfiguration.DependsOn != null && + moduleConfiguration.DependsOn.Count > 0) + { + var childVertex = + Vertices + .Where(v => v.Name.Equals( + moduleConfiguration.Name, + StringComparison.InvariantCultureIgnoreCase)) + .FirstOrDefault(); + + foreach (var dependency in moduleConfiguration.DependsOn) + { + // Find the module configuration in the list + // of vertices + var parentVertex = + Vertices + .Where(v => v.Name.Equals( + dependency, + StringComparison.InvariantCultureIgnoreCase)) + .FirstOrDefault(); + + if(parentVertex == null) + { + throw new Exception($"Parent node: {dependency} not found, make sure it exists and doesn't have Enabled: false."); + } + // Let's add edge like follows: + // parentVertex -> to -> childVertex + parentVertex.AddEdge(childVertex); + } + } + } + } + catch + { + throw; + } + } + + private void AddVertex(Vertex vertex) + { + try + { + if(!Vertices.Any(v => v.Name.Equals(vertex.Name))) + { + Vertices.Add(vertex); + } + } + catch + { + throw; + } + } + + public void DFS() + { + foreach (var vertex in Vertices) + { + if(vertex.Color == Color.White) + { + DFS_Visit(vertex); + } + } + + GenerateTopologicalSort(); + } + + private void GenerateTopologicalSort() + { + // Let's reverse the DFS + TopologicalSort = + _TopologicalSort.OrderByDescending( + i => i.EndTime).ToList(); + } + + private void DFS_Visit(Vertex vertex) + { + Time++; + vertex.StartTime = Time; + vertex.Color = Color.Gray; + foreach (var edge in vertex.Edges) + { + if (edge.Color == Color.White) + { + DFS_Visit(edge); + } + else if(edge.Color == Color.Gray) + { + throw new Exception($"Circular reference detected on node: {vertex.Name}"); + } + } + vertex.Color = Color.Black; + Time++; + vertex.EndTime = Time; + + _TopologicalSort.AddFirst(vertex); + } + + public void ParallelJobScheduling() + { + var allUnscheduled = + Vertices + .Where(v => !v.IsVisited) + .ToList(); + } + + private void ScheduleJobs(List vertices) + { + var x = vertices.SelectMany(i => i.Edges).ToList(); + } + } +} \ No newline at end of file diff --git a/Orchestration/OrchestrationService/TopologicalSort/TopologicalSort.csproj b/Orchestration/OrchestrationService/TopologicalSort/TopologicalSort.csproj new file mode 100644 index 0000000..8264fe0 --- /dev/null +++ b/Orchestration/OrchestrationService/TopologicalSort/TopologicalSort.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/Orchestration/OrchestrationService/TopologicalSort/Vertex.cs b/Orchestration/OrchestrationService/TopologicalSort/Vertex.cs new file mode 100644 index 0000000..31971fa --- /dev/null +++ b/Orchestration/OrchestrationService/TopologicalSort/Vertex.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using System.Linq; +using System; + +namespace VDC.Core.Models +{ + public class GraphModel + { + public string Name { get; set; } + public bool Enabled { get; set; } = true; + public List DependsOn { get; set; } + } + + public class Vertex + { + public Vertex(string name) + { + Name = name; + } + public Vertex(string name, bool isVisited) + { + this.Name = name; + this.IsVisited = isVisited; + + } + public string Name { get; set; } + public LinkedList Edges { get; set; } = new LinkedList(); + public bool IsVisited { get; set; } + public Color Color { get; set; } + public int StartTime { get; set; } + public int EndTime { get; set; } + public int EdgeCount + { + get + { + return Edges.Count; + } + } + + public void AddEdge(Vertex to) + { + if (!Edges.Any( + t => t.Name.Equals( + to.Name, + StringComparison.InvariantCultureIgnoreCase))) + { + Edges.AddLast(to); + } + } + + public void AddEdges(IEnumerable to) + { + var uniqueVertices = + Edges.Except(to, new VertexComparer()); + Edges.ToList().AddRange(uniqueVertices); + } + } + + class VertexComparer : EqualityComparer + { + public override bool Equals(Vertex x, Vertex y) + { + return x.Name.Equals( + y.Name, + StringComparison.InvariantCultureIgnoreCase); + } + + public override int GetHashCode(Vertex obj) + { + return obj.Name.GetHashCode(); + } + } + + public enum Color + { + None = 0, + White = 1, + Gray = 2, + Black = 3 + } +} \ No newline at end of file diff --git a/Orchestration/RepositoryService/Implementations/BlobContainerStateRepository.ps1 b/Orchestration/RepositoryService/Implementations/BlobContainerStateRepository.ps1 index cb07ab4..f096ad7 100644 --- a/Orchestration/RepositoryService/Implementations/BlobContainerStateRepository.ps1 +++ b/Orchestration/RepositoryService/Implementations/BlobContainerStateRepository.ps1 @@ -290,6 +290,7 @@ Class BlobContainerStateRepository: IStateRepository { -AsHashTable).$moduleInstanceName; } else { + Write-Debug "Blob not found" # blob was not found return $null; } @@ -310,11 +311,12 @@ Class BlobContainerStateRepository: IStateRepository { hidden [string] BlobExists([string] $container, [string] $blob) { - + + Write-Debug "About to retrieve Container: $container and Blob: $blob" $temporalFileName = [Guid]::NewGuid(); $temporalFilePath = ` Join-Path $this.temporalRootPath "$temporalFileName.json"; - + Write-Debug "Temporal file path: $temporalFilePath" try { $blobFound = Get-AzStorageBlobContent ` -Container $container ` @@ -323,13 +325,15 @@ Class BlobContainerStateRepository: IStateRepository { -Context $this.storageAccountContext ` -ErrorAction SilentlyContinue - if ($blobFound -eq $null) { + if ($null -eq $blobFound) { + Write-Debug "No blob found" return ""; } else { $contentJson = ` Get-Content $temporalFilePath ` -Raw; + Write-Debug "Blob found: $contentJson" return $contentJson; } } diff --git a/Orchestration/Tests/UnitTests/CustomScriptExecution.Tests.ps1 b/Orchestration/Tests/UnitTests/CustomScriptExecution.Tests.ps1 index e04655d..2605583 100644 --- a/Orchestration/Tests/UnitTests/CustomScriptExecution.Tests.ps1 +++ b/Orchestration/Tests/UnitTests/CustomScriptExecution.Tests.ps1 @@ -72,8 +72,9 @@ Describe "Custom Script Execution Unit Test Cases" { It "Should execute a Bash script" { - $scriptPath = Join-Path $rootPath -ChildPath '..' -AdditionalChildPath @("Samples", "scripts", "sample-script.sh"); - + $bashRootPath = bash -c 'echo $PWD'; + $scriptPath = Join-Path $bashRootPath -ChildPath 'Orchestration' -AdditionalChildPath @("Tests", "Samples", "scripts", "sample-script.sh"); + $scriptPath = $scriptPath.Replace('\', '/') $command = $scriptPath; $arguments = @{ diff --git a/dockerfile b/dockerfile index eed9b66..7048604 100644 --- a/dockerfile +++ b/dockerfile @@ -21,5 +21,7 @@ RUN apt-get update \ && echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ $AZ_REPO main" | tee /etc/apt/sources.list.d/azure-cli.list \ && apt-get install apt-transport-https \ && apt-get update \ - && apt-get install azure-cli + && apt-get install azure-cli \ + && apt-get install -y dotnet-sdk-2.2 \ + && dotnet build Orchestration/OrchestrationService/TopologicalSort/TopologicalSort.csproj --configuration Release ENTRYPOINT [ "pwsh" ]