tools/modules/AksEdgeDeploy/AksEdge-Arc.ps1 (545 lines of code) (raw):
<#
.DESCRIPTION
This module contains the Arc functions to use on Edge Essentials platforms (ArcEdge)
#>
#Requires -RunAsAdministrator
if (! [Environment]::Is64BitProcess) {
Write-Host "Error: Run this in 64bit Powershell session" -ForegroundColor Red
exit -1
}
#Hashtable to store session information
$arciotSession = @{
"WorkspacePath" = (Get-Location)
"azSession" = $null
"ClusterName" = ""
}
New-Variable -Option Constant -ErrorAction SilentlyContinue -Name arciotEnvConfig -Value @{
"RPNamespaces" = @("Microsoft.HybridCompute", "Microsoft.GuestConfiguration", "Microsoft.HybridConnectivity",
"Microsoft.Kubernetes", "Microsoft.KubernetesConfiguration", "Microsoft.ExtendedLocation")
"ArcExtensions" = @("MicrosoftMonitoringAgent", "CustomScriptExtension")
"ReqRoles" = @("Azure Connected Machine Onboarding", "Kubernetes Cluster - Azure Arc Onboarding")
"AzExtensions" = @("connectedmachine", "connectedk8s", "customlocation", "k8s-extension")
"ArcIotSchema" = @("SubscriptionName", "SubscriptionId", "TenantId", "ResourceGroupName", "Location", "Auth")
}
New-Variable -Option Constant -ErrorAction SilentlyContinue -Name azMinVersions -Value @{
"azure-cli" = "2.41.0"
"azure-cli-core" = "2.41.0"
"connectedk8s" = "1.3.1"
"connectedmachine" = "0.5.1"
"customlocation" = "0.1.3"
"k8s-extension" = "1.3.3"
}
function Test-AzVersions {
#Function to check if the installed az versions are greater or equal to minVersions
$retval = $true
$curVersion = (az version) | ConvertFrom-Json
if (-not $curVersion) { return $false }
foreach ($item in $azMinVersions.Keys ) {
Write-Host " Checking $item minVersion $($azMinVersions.$item).." -NoNewline
$fgcolor = 'Green'
if ($curVersion.$item) {
Write-Verbose " Comparing $($curVersion.$item) -lt $($azMinVersions.$item)."
if ([version]$($curVersion.$item) -lt [version]$($azMinVersions.$item)) {
$retval = $false
$fgcolor = 'Red'
}
Write-Host "found $($curVersion.$item)" -ForegroundColor $fgcolor
} elseif ($curVersion.extensions.$item) {
Write-Verbose " Comparing $($curVersion.extensions.$item) -lt $($azMinVersions.$item)"
if ([version]$($curVersion.extensions.$item) -lt [version]$($azMinVersions.$item)) {
$retval = $false
$fgcolor = 'Red'
}
Write-Host "found $($curVersion.extensions.$item)" -ForegroundColor $fgcolor
} else {
Write-Host "Error: $item is not installed" -ForegroundColor Red
$retval = $false
}
}
return $retval
}
function Install-AideAzCli {
<#
.SYNOPSIS
Installs Azure CLI and required extensions
.DESCRIPTION
Checks if Azure CLI is installed (az) and installs the latest version of Azure CLI from "https://aka.ms/installazurecliwindows".
This also checks and installs the following extensions
"connectedmachine", "connectedk8s", "customlocation", "k8s-extension"
.OUTPUTS
None
.EXAMPLE
Install-AideAzCli
#>
#Check if Az CLI is installed. If not install it.
$AzCommand = Get-Command -Name az -ErrorAction SilentlyContinue
if (!$AzCommand) {
Write-Host "> Installing AzCLI..."
Push-Location $env:TEMP
$progressPreference = 'silentlyContinue'
Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi -UseBasicParsing
$progressPreference = 'Continue'
Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /passive'
Remove-Item .\AzureCLI.msi
Pop-Location
#Refresh the env variables to include path from installed MSI
$Env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine")
az config set core.disable_confirm_prompt=yes
az config set core.only_show_errors=yes
#az config set auto-upgrade.enable=yes
}
Write-Host "> Azure CLI installed" -ForegroundColor Green
$extlist = (az extension list --query [].name | ConvertFrom-Json -ErrorAction SilentlyContinue)
foreach ($ext in $arciotEnvConfig.AzExtensions) {
if ($extlist -and $extlist.Contains($ext)) {
Write-Host "> az extension $ext installed" -ForegroundColor Green
} else {
Write-Host "Installing az extension $ext"
az extension add --name $ext
}
}
if (-not (Test-AzVersions)) {
Write-Host "> Required Az versions are not installed. Attempting az upgrade. This may take a while."
az upgrade --all --yes
if (-not (Test-AzVersions)) {
Write-Host "Error: Required versions not found after az upgrade. Please try uninstalling and reinstalling" -ForegroundColor Red
}
}
}
function Enter-AideArcSession {
<#
.SYNOPSIS
Logs into Azure using the service principal credentials supplied.
.DESCRIPTION
Logs into Azure using the service principal credentials supplied in the json file (Azure.Auth.ServicePrincipalId and Azure.Auth.Password).
.OUTPUTS
None
.EXAMPLE
Enter-AideArcSession
#>
$aicfg = Get-AideArcUserConfig
if (!$arciotSession.azSession) {
if (-not $aicfg.Auth) {
Write-Host "Error: no valid credentials." -ForegroundColor Red
return $false
}
$aiauth = $aicfg.Auth
if ($aiauth.ServicePrincipalId) {
Write-Host "Using service principal id to login"
if ($aiauth.Password) {
$ret = az login --service-principal -u $aiauth.ServicePrincipalId -p $aiauth.Password --tenant $aicfg.TenantId
if (-not $ret) {
Write-Host "Error: ServicePrincipalId/Password possibly expired." -ForegroundColor Red
return $false
}
} else {
Write-Host "Error: password not specified." -ForegroundColor Red
return $false
}
} else {
Write-Host "Error: no valid Auth parameters." -ForegroundColor Red
return $false
}
}
(az account set --subscription $aicfg.SubscriptionId) | Out-Null
#az configure --defaults group=$aicfg.ResourceGroupName
$session = (az account show | ConvertFrom-Json -ErrorAction SilentlyContinue)
Write-Host "Logged in $($session.name) subscription as $($session.user.name) ($($session.user.type))"
$roles = (az role assignment list --all --assignee $($session.user.name)) | ConvertFrom-Json
if (-not $roles) {
Write-Host "Error: No roles enabled for this account in this subscription" -ForegroundColor Red
Exit-AideArcSession
return $false
}
Write-Host "Roles enabled for this account are:" -ForegroundColor Cyan
foreach ($role in $roles) {
Write-Host "$($role.roleDefinitionName) for scope $($role.scope)" -ForegroundColor Cyan
}
$arciotSession.azSession = $session
return $true
}
function Exit-AideArcSession {
<#
.SYNOPSIS
Logs out of Azure session and clears account cache.
.DESCRIPTION
Logs out of Azure session and clears account cache.
.OUTPUTS
None
.EXAMPLE
Exit-AideArcSession
#>
az logout
az account clear
$arciotSession.azSession = $null
}
function Initialize-AideArc {
<#
.SYNOPSIS
Checks and installs Azure CLI and validates the Azure configuration using the service principal credentials.
.DESCRIPTION
This command checks and installs Azure CLI by invoking Install-AideAzCli and validates the Azure configuration such as resource group, resource provider status using the service principal credentials..
.OUTPUTS
Boolean
True if all ok.
.EXAMPLE
Initialize-AideArc
#>
$status = Test-AideArcUserConfig
if (!$status) { return $false }
$aicfg = Get-AideArcUserConfig
if (! $aicfg) {
Write-Host "Error: UserConfig not set. Use Set-AideUserConfig to set" -Foreground Red
return $false
}
Write-Host "Azure configuration:"
Write-Host $aicfg
Install-AideAzCli
$spLoginSuccess = Enter-AideArcSession
if (-not $spLoginSuccess) {
Write-Host "Error: Failed to login into Azure. Check Auth parameters. Initialize-AideArc failed." -ForegroundColor Red
return
} else {
$status = Test-AzureResourceGroup $aicfg.ResourceGroupName $aicfg.Location
$retval = Test-AzureResourceProviders $arciotEnvConfig.RPNamespaces
if ($status) { $status = $retval }
}
if ($status) {
Write-Host "Initialize-AideArc successful." -ForegroundColor Green
} else {
Write-Host "Initialize-AideArc failed." -ForegroundColor Red
}
return $status
}
function Test-AzureResourceGroup {
Param
(
[Parameter(Position = 0, Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String]$rgname,
[Parameter(Position = 1, Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String]$location
)
# Check if resource group already exists
$retval = $false
Write-Host "Checking $rgname..."
$rgexists = az group exists --name $rgname
if ($rgexists -ieq 'true') {
Write-Host "* $rgname exists" -ForegroundColor Green
$retval = $true
} else {
Write-Host "Error: $rgname not found" -ForegroundColor Red
}
return $retval
}
function Test-AzureResourceProviders {
Param(
[System.Array]$namespaces = $null
)
$retval = $false
if ($namespaces) {
$retval = $true
foreach ($namespace in $namespaces) {
Write-Host "Checking $namespace..."
$provider = (az provider show -n $namespace | ConvertFrom-Json -ErrorAction SilentlyContinue)
if ($provider.registrationState -ieq "Registered") {
Write-Host "* $namespace provider registered" -ForegroundColor Green
} else {
Write-Host "Error: $namespace provider not registered" -ForegroundColor Red
$retval = $false
}
}
}
return $retval
}
function Test-AzureRoles {
Param(
[Parameter(Position = 0, Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String]$appId,
[Parameter(Position = 1, Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.Array]$roles,
[Parameter(Position = 1, Mandatory = $false)]
[Switch]$Add
)
$retval = $true
$aicfg = Get-AideArcUserConfig
#$reqRole = "Azure Connected Machine Onboarding"
Write-Host "Using principalName $appId"
Write-Host "Checking for role assignment"
$query = "[?principalName=='$appId'].roleDefinitionName"
$scope = "/subscriptions/$($aicfg.SubscriptionId)/resourceGroups/$($aicfg.ResourceGroupName)"
$curRoles = (az role assignment list --scope $scope --query $query) | ConvertFrom-Json -ErrorAction SilentlyContinue
foreach ($reqRole in $roles) {
if ($curRoles.Contains($reqRole)) {
Write-Host "$reqRole role enabled"
} else {
if ($Add) {
$res = (az role assignment create --assignee $appId --scope $scope --role $reqRole) | ConvertFrom-Json -ErrorAction SilentlyContinue
Write-Host "Role added. $($res.principalId)"
} else {
Write-Host "$reqRole role is not enabled" -ForegroundColor Red
$retval = $false
}
}
}
return $retval
}
#########################################
# Arc-enabled Kubernetes - Connected Clusters
#########################################
function Get-AideArcClusterName {
<#
.SYNOPSIS
Returns the cluster name for the deployed cluster.
.DESCRIPTION
This command returns the cluster name for the deployed cluster. If the user has specified Clustername in the aide-userconfig.json, the same is returned.
If there is no user specifcation, it returns the clustername as hostname-k8s or hostname-k3s based on the kubernetes flavour installed.
.OUTPUTS
String
.EXAMPLE
Get-AideArcClusterName
#>
if (-not $arciotSession.ClusterName) {
$aicfg = Get-AideArcUserConfig
if ($aicfg.ClusterName) {
$arciotSession.ClusterName = $aicfg.ClusterName
} else {
#$clustername = $(kubectl get configmap -n aksedge aksedge -o jsonpath="{.data.clustername}")
#if (!$clustername){
$clustername = hostname
$k3s = (kubectl get nodes) | Where-Object { $_ -match "k3s"}
if ($k3s) {
$clustername += "-k3s"
} else {
$clustername += "-k8s"
}
#}
$arciotSession.ClusterName = $clustername
}
}
return $arciotSession.ClusterName
}
function Test-AideArcKubernetes {
<#
.SYNOPSIS
Tests if the running kubernetes cluster is connected to Azure Arc-enabled kubernetes.
.DESCRIPTION
This command tests if Arc-enabled Kubernetes is connected. It checks whether the cluster name is present in the list of arc-enabled kubernetes in the given resource group.
The inputs required are consumed from the aide-userconfig.json file.
.OUTPUTS
Boolean
True when connected.
.EXAMPLE
Test-AideArcKubernetes
#>
$retval = $false
if ((!$arciotSession.azSession) -and (!(Enter-AideArcSession))) { return $retval }
$aicfg = Get-AideArcUserConfig
# check if this cluster is already registered
$k8slist = (az connectedk8s list -g $aicfg.ResourceGroupName --query [].name | ConvertFrom-Json -ErrorAction SilentlyContinue)
$arciotClusterName = Get-AideArcClusterName
if ($k8slist -and ($k8slist.Contains($arciotClusterName))) {
Write-Host "$arciotClusterName is connected to Arc" -ForegroundColor Green
$retval = $true
} else {
Write-Host "$arciotClusterName is not connected to Arc" -ForegroundColor Yellow
}
return $retval
}
function Connect-AideArcKubernetes {
<#
.SYNOPSIS
Connects the running kubernetes cluster to Azure Arc.
.DESCRIPTION
This command connects the kubernetes cluster running on the machine (should be running control plane) to Arc-enabled Kubernetes.
The inputs required are consumed from the aide-userconfig.json file.
.OUTPUTS
Boolean
True if the connection is successful.
.EXAMPLE
Connect-AideArcKubernetes
#>
if ((!$arciotSession.azSession) -and (!(Enter-AideArcSession))) { return $false }
$aicfg = Get-AideArcUserConfig
# check if this cluster is already registered
$k8slist = (az connectedk8s list -g $aicfg.ResourceGroupName --query [].name | ConvertFrom-Json -ErrorAction SilentlyContinue)
$arciotClusterName = Get-AideArcClusterName
if ($k8slist -and ($k8slist.Contains($arciotClusterName))) {
Write-Host "$arciotClusterName is already connected to Arc" -ForegroundColor Green
} else {
# Get the credentials before connecting to ensure that we have the latest file.
Write-Host "Updating kubeconfig file with Get-AksEdgeKubeConfig..."
Get-AksEdgeKubeConfig -KubeConfigPath $($arciotSession.WorkspacePath) -Confirm:$false
Write-Host "Establishing Azure Connected Kubernetes for $arciotClusterName"
$connectargs = @(
"--name", "$($arciotClusterName)",
"--resource-group", "$($aicfg.ResourceGroupName)",
"--kube-config", "$($arciotSession.WorkspacePath)\config"
)
<#
$connectargs += @(
"--distribution","aks_edge",
"--infrastructure","TBF"
)
#>
if ($($aicfg.CustomLocationOID)) {
$connectargs += @( "--custom-locations-oid", "$($aicfg.CustomLocationOID)")
}
$tags = @("SKU=AKSEdgeEssentials")
$modVersion = (Get-Module AksEdge).Version
if ($modVersion) { $tags += @("Version=$modVersion") }
$infra = Get-AideInfra
if ($infra) { $tags += @("Infra=$infra") }
$clusterid = $(kubectl get configmap -n aksedge aksedge -o jsonpath="{.data.clustername}")
if ($clusterid) { $tags += @("ClusterId=$clusterid") }
<#$hostname = hostname
if ($hostname) { $tags += @("Hostname=$hostname") }#>
$aideConfig = Get-AideUserConfig
if ($aideConfig) {
$isProxySet = $false
$httpsProxy = $($aideConfig.Network.Proxy.Https)
$httpProxy = $($aideConfig.Network.Proxy.Http)
if ($httpsProxy) {
$connectargs += @( "--proxy-https", "$httpsProxy")
$isProxySet = $true
}
if ($httpProxy) {
$connectargs += @( "--proxy-http", "$httpProxy")
$isProxySet = $true
}
if ($isProxySet) {
$no_proxy = $($aideConfig.Network.Proxy.No)
$kubenet = $(kubectl get services kubernetes -o jsonpath="{$.spec.clusterIP}")
$octets = $kubenet.Split(".")
$octets[2] = 0;$octets[3] = 0
$kubeSubnet = $($octets -join ".") + "/16"
if ($no_proxy){
$no_proxy = "$no_proxy,$kubeSubnet"
} else {
$no_proxy = "localhost,127.0.0.0/8,192.168.0.0/16,172.17.0.0/16,10.96.0.0/12,10.244.0.0/16,,kubernetes.default.svc,.svc.cluster.local,.svc,$kubeSubnet"
}
$connectargs += @( "--proxy-skip-range",$no_proxy)
}
}
$connectargs += @( "--tags", $tags)
$result = (az connectedk8s connect @connectargs ) | ConvertFrom-Json -ErrorAction SilentlyContinue
if (!$result) {
Write-Host "Error: arc connect failed." -ForegroundColor Red
return $false
}
Write-Verbose ($result | Out-String)
#Update the Arc-enabled server tag if connected
if (Test-AideArcServer) {
$cmInfo = Get-AideArcServerInfo
$resource = "/subscriptions/$($cmInfo.SubscriptionId)/resourceGroups/$($cmInfo.ResourceGroupName)/providers/Microsoft.HybridCompute/machines/$($cmInfo.Name)"
$result= $(az tag update --resource-id $resource --operation Merge --tags "AKSEE=$arciotClusterName")
if ($result) {
Write-Host "Arc-enabled server tag updated with cluster id"
} else {
Write-Host "Error: Arc-enabled server tag update failed" -ForegroundColor Red
}
}
$token = Get-AideArcKubernetesServiceToken
$proxyinfo = @{
resourcegroup = $aicfg.ResourceGroupName
clustername = $arciotClusterName
token = $token
}
$proxyjson = ConvertTo-Json -InputObject $proxyinfo
Set-Content -Path "$($arciotSession.WorkspacePath)\proxyinfo.json" -Value $proxyjson -Force
Remove-Item -Path "$($arciotSession.WorkspacePath)\config" | Out-Null
}
return $true
}
function Disconnect-AideArcKubernetes {
<#
.SYNOPSIS
Disconnects the running kubernetes cluster from Azure Arc.
.DESCRIPTION
This command disconnects from Arc-enabled Kubernetes,if connected.
The inputs required are consumed from the aide-userconfig.json file.
.OUTPUTS
Boolean
True if the disconnection is successful.
.EXAMPLE
Disconnect-AideArcKubernetes
#>
if ((!$arciotSession.azSession) -and (!(Enter-AideArcSession))) { return $false }
$aicfg = Get-AideArcUserConfig
$k8slist = (az connectedk8s list -g $aicfg.ResourceGroupName --query [].name | ConvertFrom-Json -ErrorAction SilentlyContinue)
$arciotClusterName = Get-AideArcClusterName
if ($k8slist -and ($k8slist.Contains($arciotClusterName))) {
# Get the credentials before connecting to ensure that we have the latest file.
Write-Host "Updating kubeconfig file with Get-AksEdgeKubeConfig..."
Get-AksEdgeKubeConfig -KubeConfigPath $($arciotSession.WorkspacePath) -Confirm:$false
Write-Host "Deleting Arc resource for $arciotClusterName"
$result = (az connectedk8s delete -g $aicfg.ResourceGroupName -n $arciotClusterName --kube-config "$($arciotSession.WorkspacePath)\config" --yes) | ConvertFrom-Json -ErrorAction SilentlyContinue
Write-Verbose ($result | Out-String)
Remove-Item -Path "$($arciotSession.WorkspacePath)\config" | Out-Null
Write-Host "Arc connect for cluster $clusername removed."
} else {
Write-Host "$arciotClusterName not connected to Azure Arc." -ForegroundColor Yellow
}
return $true
}
function Get-AideArcKubernetesServiceToken {
<#
.SYNOPSIS
Returns the service account token of the aksedge-admin-user from the deployed cluster.
.DESCRIPTION
This command the service account token of the aksedge-admin-user from the deployed cluster. It also stores the same value in a servicetoken.txt file.
.OUTPUTS
String
.EXAMPLE
Get-AideArcKubernetesServiceToken
#>
$servicetoken = Get-AksEdgeManagedServiceToken
$servicetokenfile = "$($arciotSession.WorkspacePath)\servicetoken.txt"
Set-Content -Path $servicetokenfile -Value "$servicetoken"
return $servicetoken
}
function Connect-AideArc {
<#
.SYNOPSIS
Connects the machine and the running kubernetes cluster to Azure Arc.
.DESCRIPTION
This command invokes Connect-AideArcServer which installs and connects Azure Arc Connected machine agent to Arc-enabled Server.
Then it invokes Connect-AideArcKubernetes to connect the kubernetes cluster running on the machine (should be running control plane) to Arc-enabled Kubernetes.
The inputs required are consumed from the aide-userconfig.json file.
.OUTPUTS
Boolean
True if both the connection is successful and false if either one fails.
.EXAMPLE
Connect-AideArc
#>
$serverStatus = $true
Write-Host "Connecting Azure Arc-enabled Server.."
$serverStatus = Connect-AideArcServer
Write-Host "Connecting Azure Arc-enabled Kubernetes.."
$kubernetesStatus = Connect-AideArcKubernetes
return ($serverStatus -and $kubernetesStatus)
}
function Disconnect-AideArc {
<#
.SYNOPSIS
Disconnects the machine and the running kubernetes cluster from Azure Arc.
.DESCRIPTION
This command invokes Disconnect-AideArcServer which disconnects from Arc-enabled Server, if connected.
Then it invokes Disconnect-AideArcKubernetes to disconnect from Arc-enabled Kubernetes,if connected.
The inputs required are consumed from the aide-userconfig.json file.
.OUTPUTS
Boolean
True if both the disconnection is successful and false if either one fails.
.EXAMPLE
Disconnect-AideArc
#>
$serverStatus = $true
Write-Host "Disconnecting Azure Arc-enabled Server.."
$serverStatus = Disconnect-AideArcServer
Write-Host "Disconnecting Azure Arc-enabled Kubernetes.."
$kubernetesStatus = Disconnect-AideArcKubernetes
return ($serverStatus -and $kubernetesStatus)
}