From b1c69032b3ed4aaf01e02ae6bc01d46e4e045c8d Mon Sep 17 00:00:00 2001 From: Helder Pinto Date: Tue, 23 Jul 2024 17:46:47 +0100 Subject: [PATCH 1/3] Added multi-tenant schedule registration tool --- ...egister-MultitenantAutomationSchedules.ps1 | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 src/optimization-engine/Register-MultitenantAutomationSchedules.ps1 diff --git a/src/optimization-engine/Register-MultitenantAutomationSchedules.ps1 b/src/optimization-engine/Register-MultitenantAutomationSchedules.ps1 new file mode 100644 index 000000000..1ecfc6f19 --- /dev/null +++ b/src/optimization-engine/Register-MultitenantAutomationSchedules.ps1 @@ -0,0 +1,144 @@ +<# +.SYNOPSIS +This script adds job schedules for all Azure Optimization Engine data collection runbooks for a given tenant/cloud. + +.DESCRIPTION +This script resets the Azure Optimization Engine schedules to a new base time and optionally changes the Hybrid Worker Group for all schedules. + +.PARAMETER AzureEnvironment +The Azure environment to use. Possible values are AzureCloud, AzureChinaCloud, AzureUSGovernment. + +.PARAMETER AutomationAccountName +The name of the Automation Account where the Azure Optimization Engine is deployed. + +.PARAMETER ResourceGroupName +The name of the Resource Group where the Automation Account is located. + +.EXAMPLE +.\Reset-AutomationSchedules.ps1 -AutomationAccountName "MyAutomationAccount" -ResourceGroupName "MyResourceGroup" + +.LINK +https://aka.ms/AzureOptimizationEngine/customize +#> +param( + [Parameter(Mandatory = $false)] + [String] $AzureEnvironment = "AzureCloud", + + [Parameter(Mandatory = $true)] + [String] $AutomationAccountName, + + [Parameter(Mandatory = $true)] + [String] $ResourceGroupName, + + [Parameter(Mandatory = $true)] + [String] $TargetSchedulesSuffix, + + [Parameter(Mandatory = $false)] + [int] $TargetSchedulesOffsetMinutes = 0, + + [Parameter(Mandatory = $false)] + [String] $TargetAzureEnvironment = "AzureCloud", + + [Parameter(Mandatory = $true)] + [String] $TargetTenantId, + + [Parameter(Mandatory = $true)] + [String] $TargetTenantCredentialName, + + [Parameter(Mandatory = $false)] + [String[]] $ExcludedRunbooks = @(), + + [Parameter(Mandatory = $false)] + [String[]] $IncludedRunbooks = @() +) + +$ErrorActionPreference = "Stop" + +$ctx = Get-AzContext +if (-not($ctx)) { + Connect-AzAccount -Environment $AzureEnvironment + $ctx = Get-AzContext +} +else { + if ($ctx.Environment.Name -ne $AzureEnvironment) { + Disconnect-AzAccount -ContextName $ctx.Name + Connect-AzAccount -Environment $AzureEnvironment + $ctx = Get-AzContext + } +} + +try +{ + $scheduledRunbooks = Get-AzAutomationScheduledRunbook -AutomationAccountName $AutomationAccountName -ResourceGroupName $ResourceGroupName +} +catch +{ + throw "$AutomationAccountName Automation Account not found in Resource Group $ResourceGroupName in Subscription $($ctx.Subscription.Name). If we are not in the right subscription, use Set-AzContext to switch to the correct one." +} + +$dataCollectionRunbooks = $scheduledRunbooks | Where-Object { $_.RunbookName -like "Export-*" -and $_.RunbookName -notin $ExcludedRunbooks -and $_.RunbookName -ne "Export-ReservationsPriceToBlobStorage" } +if ($IncludedRunbooks.Count -gt 0) +{ + $dataCollectionRunbooks = $dataCollectionRunbooks | Where-Object { $_.RunbookName -in $IncludedRunbooks } +} + +if (-not($dataCollectionRunbooks)) +{ + throw "The $AutomationAccountName Automation Account does not contain any scheduled data collection runbook. It might not be associated to the Azure Optimization Engine." +} + +foreach ($jobSchedule in $dataCollectionRunbooks) +{ + Write-Host "Processing $($jobSchedule.RunbookName) runbook for $($jobSchedule.ScheduleName) schedule..." -ForegroundColor Green + $schedule = Get-AzAutomationSchedule -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName -ScheduleName $jobSchedule.ScheduleName + $newScheduleName = "$($schedule.Name)$TargetSchedulesSuffix" + $newSchedule = Get-AzAutomationSchedule -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName ` + -ScheduleName $newScheduleName -ErrorAction SilentlyContinue + if (-not($newSchedule)) + { + Write-Host "Creating new schedule $newScheduleName..." -ForegroundColor Green + $newScheduleParameters = @{ + ResourceGroupName = $ResourceGroupName; + AutomationAccountName = $AutomationAccountName; + Name = $newScheduleName; + StartTime = $schedule.NextRun.AddMinutes($TargetSchedulesOffsetMinutes); + ExpiryTime = $schedule.ExpiryTime; + Timezone = $schedule.TimeZone + } + switch ($schedule.Frequency) + { + "Hour" { + $newScheduleParameters['HourInterval'] = $schedule.Interval + } + "Day" { + $newScheduleParameters['DayInterval'] = $schedule.Interval + } + "Week" { + $newScheduleParameters['WeekInterval'] = $schedule.Interval + $newScheduleParameters['DaysOfWeek'] = $schedule.WeeklyScheduleOptions.DaysOfWeek + } + default { + throw "Unsupported frequency: $($schedule.Frequency)" + } + } + New-AzAutomationSchedule @newScheduleParameters | Out-Null + } + + Write-Host "Associating schedule $newScheduleName to $($jobSchedule.RunbookName) runbook..." -ForegroundColor Green + $jobScheduleDetails = Get-AzAutomationScheduledRunbook -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName ` + -JobScheduleId $jobSchedule.JobScheduleId + $jobScheduleDetails.Parameters.Add("externalCloudEnvironment", $TargetAzureEnvironment) + $jobScheduleDetails.Parameters.Add("externalTenantId", $TargetTenantId) + $jobScheduleDetails.Parameters.Add("externalCredentialName", $TargetTenantCredentialName) + $newJobScheduleParameters = @{ + ResourceGroupName = $ResourceGroupName; + AutomationAccountName = $AutomationAccountName; + RunbookName = $jobScheduleDetails.RunbookName; + ScheduleName = $newScheduleName; + RunOn = $jobScheduleDetails.HybridWorker; + Parameters = $jobScheduleDetails.Parameters + } + Register-AzAutomationScheduledRunbook @newJobScheduleParameters | Out-Null +} + +Write-Host "DONE" -ForegroundColor Green \ No newline at end of file From 982851ad00becd96afc13c06c7bd77061014a06a Mon Sep 17 00:00:00 2001 From: Helder Pinto Date: Thu, 25 Jul 2024 13:36:36 +0100 Subject: [PATCH 2/3] Improved parameters documentation and defaults --- ...egister-MultitenantAutomationSchedules.ps1 | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/optimization-engine/Register-MultitenantAutomationSchedules.ps1 b/src/optimization-engine/Register-MultitenantAutomationSchedules.ps1 index 1ecfc6f19..59ca6f713 100644 --- a/src/optimization-engine/Register-MultitenantAutomationSchedules.ps1 +++ b/src/optimization-engine/Register-MultitenantAutomationSchedules.ps1 @@ -3,7 +3,11 @@ This script adds job schedules for all Azure Optimization Engine data collection runbooks for a given tenant/cloud. .DESCRIPTION -This script resets the Azure Optimization Engine schedules to a new base time and optionally changes the Hybrid Worker Group for all schedules. +This script adds job schedules for all Azure Optimization Engine data collection runbooks for a given tenant/cloud. +It is useful when you want to collect data from multiple tenants/clouds in a single AOE deployment. +The script will create new schedules with a suffix and offset for each existing schedule, and associate them to the corresponding runbooks. +It requires you to create first a service principal in the target tenant and grant it the necessary permissions to the target subscriptions/tenant. +You also need to create an Automation Credential in the AOE Automation Account with the service principal credentials (client ID and secret). .PARAMETER AzureEnvironment The Azure environment to use. Possible values are AzureCloud, AzureChinaCloud, AzureUSGovernment. @@ -14,8 +18,29 @@ The name of the Automation Account where the Azure Optimization Engine is deploy .PARAMETER ResourceGroupName The name of the Resource Group where the Automation Account is located. +.PARAMETER TargetSchedulesSuffix +The suffix to append to the new schedules names. + +.PARAMETER TargetSchedulesOffsetMinutes +The offset in minutes to apply to the new schedules start times (defaults to 0). + +.PARAMETER TargetAzureEnvironment +The target Azure environment to collect data from. Possible values are AzureCloud, AzureChinaCloud, AzureUSGovernment. Defaults to AzureCloud. + +.PARAMETER TargetTenantId +The target tenant ID to collect data from. + +.PARAMETER TargetTenantCredentialName +The name of the Automation Credential in the AOE Automation Account containing the service principal credentials (client ID and secret). + +.PARAMETER ExcludedRunbooks +An array of runbook names to exclude from the process. Defaults to runbooks that export data at the EA/MCA level, which should only run on the source tenant. + +.PARAMETER IncludedRunbooks +An array of runbook names to include in the process. If none are provided, all data collection runbooks will be processed. + .EXAMPLE -.\Reset-AutomationSchedules.ps1 -AutomationAccountName "MyAutomationAccount" -ResourceGroupName "MyResourceGroup" +Register-MultitenantAutomationSchedules.ps1 -AutomationAccountName "AOE" -ResourceGroupName "AOE-RG" -TargetSchedulesSuffix "-Tenant1" -TargetTenantId "00000000-0000-0000-0000-000000000000" -TargetTenantCredentialName "Tenant1" -ExcludedRunbooks @("Export-ReservationsPriceToBlobStorage") .LINK https://aka.ms/AzureOptimizationEngine/customize @@ -46,7 +71,7 @@ param( [String] $TargetTenantCredentialName, [Parameter(Mandatory = $false)] - [String[]] $ExcludedRunbooks = @(), + [String[]] $ExcludedRunbooks = @("Export-ReservationsPriceToBlobStorage","Export-PriceSheetToBlobStorage","Export-ReservationsUsageToBlobStorage","Export-SavingsPlansUsageToBlobStorage"), [Parameter(Mandatory = $false)] [String[]] $IncludedRunbooks = @() From e65851466542a2824de9bb4694daa7fef3b1d53e Mon Sep 17 00:00:00 2001 From: Helder Pinto Date: Tue, 30 Jul 2024 22:34:12 +0100 Subject: [PATCH 3/3] Updated customization docs --- docs/_optimize/optimization-engine/customize.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/_optimize/optimization-engine/customize.md b/docs/_optimize/optimization-engine/customize.md index 273885ac8..5657d235b 100644 --- a/docs/_optimize/optimization-engine/customize.md +++ b/docs/_optimize/optimization-engine/customize.md @@ -29,6 +29,17 @@ By default, the Azure Automation Managed Identity is assigned the Reader role on In the context of augmented VM right-size recommendations, you may have your VMs reporting to multiple workspaces. If you need to include other workspaces - besides the main one AOE is using - in the recommendations scope, you just have to add their workspace IDs to the `AzureOptimization_RightSizeAdditionalPerfWorkspaces` variable (see more details in [Configuring workspaces](./configuring-workspaces.md)). +If you are a multi-tenant customer, you can extend the reach of AOE to a tenant other than the one where it was deployed. To achieve this, you must ensure the following pre-requisites: + +* Create a service principal (App registration) and a secret in the secondary tenant. +* Grant the required permissions to the service principal in the secondary tenant, namely **Reader** in Azure subscriptions/management groups and **Global Reader** in Entra ID. +* Create an [Automation credential](https://learn.microsoft.com/azure/automation/shared-resources/credentials?tabs=azure-powershell#create-a-new-credential-asset) in the AOE's Automation Account, with the service principal's client ID as username and the secret as password. +* Execute the `Register-MultitenantAutomationSchedules.ps1` script (available in the [AOE root folder](https://aka.ms/AzureOptimizationEngine/code)) in the context of the subscription where AOE was deployed. This script will create new job schedules for each of the export runbooks and configure them to query the secondary tenant. You just have to call the script following the syntax below: + +```powershell +./Register-MultitenantAutomationSchedules.ps1 -AutomationAccountName -ResourceGroupName -TargetSchedulesSuffix -TargetTenantId -TargetTenantCredentialName [-TargetSchedulesOffsetMinutes ] [-TargetAzureEnvironment ] [-ExcludedRunbooks ] [-IncludedRunbooks ] +``` +
## ⏰ Adjust schedules