Skip to content

Commit

Permalink
Migrate ConnectedKubernetes from generation to main (#26287)
Browse files Browse the repository at this point in the history
* Move ConnectedKubernetes to main

* Update ChangeLog.md

---------

Co-authored-by: azure-powershell-bot <[email protected]>
Co-authored-by: NanxiangLiu <[email protected]>
  • Loading branch information
3 people authored Oct 11, 2024
1 parent 693a9e9 commit 91ace9e
Show file tree
Hide file tree
Showing 17 changed files with 343 additions and 80 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Develpoing ConnectedKubernetes Powershell Cmdlets
> These notes are intended to compliment and extend the common instructions for this process. If you spot a sensible common location where part of this document could live, please do move the information out of here.
# Overview
## Why Custom Cmdlets?
Powerhsll cmdlets can be created almost totally automatically for many products but ConnectedKubernetes is special. The standard cmdlet interations are one or more ([Swagger]) REST API exchanges with Azure but ConnectedKubernetes also has to install Azure Arc support into a Kubernetes cluster and this requires work to be performed using [helm].

For this reason, the ConnectedKubernetes cmdlets have two or more steps such as:
- Interact with Azure using the REST APIs; this often involves just calling the autogenerated cmdlets
- Now interact with Kubernetes using [helm].

## (Part) Autogeneration Process
The autogeneration process uses [autorest.powershell], an [autorest] extension for creating Powershell cmdlets based on a (Swagger) REST API definition. this is typically as follows:

1. Carefully craft your [Swagger] definition of the REST API
1. Read the [Quickstart for Azure PowerShell development using code generator]
1. Clone the [azure-powershell] repo
1. Create a develpoment branch based on the `generate` branch **and not based on `main`**!
1. Run the [autorest] Docker image; if you have no local image for [autorest], refer to
1. Run [autorest] to generate configuration and files that will result in the autogenerated cmdlets
1. Run the build process (`pwsh build-module.ps1`) which completes the build process.

### Building the [autorest] Docker image
> Do **NOT** build an [autorest] image based on the Dockerfile contained in the `tools/autorest` directory below the [azure-powershell] repo as this does not produce a working image!
- Clone the [autorest.powershell] repo
- Navigate to the `tools/docker` directory
- Follow the instructions in the README file in that directory

## Special Aspects for ConnectedKubernetes
The autogenerated cmdlets are created in C# with Powershell wrappers that are placed into the `internal` folder. This is because we are **NOT** exposing the autogenerated functions to the user, rather er export our custom versions.
> As described earlier, the custom versions often call-through to the autogenerated version to perform the ARM REST API portion of their work.
### Gotchas
#### You Want a New Cmdlet?
If you are creating a whole new command, then you need to get the [autorest] process and the build process to work together to create the underlying `internal` command for you and this is not trivial.

When we tried to add the `Set-` cmdlet, we found it never appeared but eventually we discovered these nuggets of knowledge.
- [autorest] will look at the `operationId` field in the [Swagger] for each REST API method and determine what commands to create. So in our case `ConnectedCluster_Create` only causes `New-` cmdlets to be created and we had to update the [Swagger] to say `ConnectedCluster_CreateOrUpdate` before any `Set-` cmdlets were created
- The `internal` cmdlets are really just Powershell wrappers but these are not created until the `pwsh build-module-ps1` step
- Between the steps above sits the [autorest] configuration found in the XML at the end of [README.md]. This does stuff like:
- Stops the generation of various versions of cmdlets that are not required
- **hides** the autogenerated cmdlets, which is what causes them to be created in `internal`; we had to add `set` to the list of cmdlets so hidden before the `internal` `Set-` cmdlet appeared.

[autorest.powershell]: https://github.com/Azure/autorest.powershell
[autorest]: https://github.com/Azure/autorest
[helm]: https://helm.sh/
[Swagger]: https://swagger.io/
[README.md]: ./README.md
[Quickstart for Azure PowerShell development using code generator]: https://eng.ms/docs/cloud-ai-platform/azure-core/azure-management-and-platforms/control-plane-bburns/azure-cli-tools-azure-cli-powershell-and-terraform/azure-cli-tools/onboarding/azurepowershell/quickstart_codegen
[azure-powershell]: https://github.com/azure/azure-powershell
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ function New-AzConnectedKubernetes {
$Null = $PSBoundParameters.Remove('KubeConfig')
}
elseif (Test-Path Env:KUBECONFIG) {
$KubeConfig = Get-ChildItem -Path Env:KUBECONFIG
$KubeConfig = Get-ChildItem -Path $Env:KUBECONFIG
}
elseif (Test-Path Env:Home) {
$KubeConfig = Join-Path -Path $Env:Home -ChildPath '.kube' | Join-Path -ChildPath 'config'
Expand Down Expand Up @@ -364,7 +364,11 @@ function New-AzConnectedKubernetes {
$PSBoundParameters.Add('IdentityType', $IdentityType)

#Region check helm install
Confirm-HelmVersion -KubeConfig $KubeConfig
Confirm-HelmVersion `
-KubeConfig $KubeConfig `
-Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) `
-Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -eq $true)


#EndRegion
$helmClientLocation = 'helm'
Expand Down Expand Up @@ -421,8 +425,13 @@ function New-AzConnectedKubernetes {
if ($PSVersionTable.PSVersion.Major -eq 5) {
try {
. "$PSScriptRoot/helpers/RSAHelper.ps1"
$AgentPublicKey = ExportRSAPublicKeyBase64($RSA)
$AgentPrivateKey = ExportRSAPrivateKeyBase64($RSA)
$AgentPublicKey = ExportRSAPublicKeyBase64($RSA) `
-Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) `
-Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -eq $true)
$AgentPrivateKey = ExportRSAPrivateKeyBase64($RSA) `
-Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) `
-Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -eq $true)

$AgentPrivateKey = "-----BEGIN RSA PRIVATE KEY-----`n" + $AgentPrivateKey + "`n-----END RSA PRIVATE KEY-----"
}
catch {
Expand Down Expand Up @@ -541,15 +550,25 @@ function New-AzConnectedKubernetes {

# A lot of what follows relies on knowing the cloud we are using and the
# various endpoints so get that information now.
$cloudMetadata = Get-AzCloudMetadata
$cloudMetadata = Get-AzCloudMetadata `
-Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) `
-Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -eq $true)

# Perform DP health check
$configDpinfo = Get-ConfigDPEndpoint -location $Location -Cloud $cloudMetadata
$configDpinfo = Get-ConfigDPEndpoint `
-location $Location `
-Cloud $cloudMetadata `
-Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) `
-Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -eq $true)

$configDPEndpoint = $configDpInfo.configDPEndpoint

# If the health check fails (not 200 response), an exception is thrown
# so we can ignore the output.
$null = Invoke-ConfigDPHealthCheck -configDPEndpoint $configDPEndpoint
$null = Invoke-ConfigDPHealthCheck `
-configDPEndpoint $configDPEndpoint `
-Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) `
-Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -eq $true)

# This call does the "pure ARM" update of the ARM objects.
Write-Debug "Writing Connected Kubernetes ARM objects."
Expand Down Expand Up @@ -586,7 +605,12 @@ function New-AzConnectedKubernetes {
# needs to change and not the Powershell script (or az CLI).
#
# Do not send protected settings to CCRP
$arcAgentryConfigs = ConvertTo-ArcAgentryConfiguration -ConfigurationSetting $ConfigurationSetting -RedactedProtectedConfiguration @{} -CCRP $true
$arcAgentryConfigs = ConvertTo-ArcAgentryConfiguration `
-ConfigurationSetting $ConfigurationSetting `
-RedactedProtectedConfiguration @{} `
-CCRP $true `
-Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) `
-Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -eq $true)

# It is possible to set an empty value for these parameters and then
# the code above gets skipped but we still need to remove the empty
Expand All @@ -601,14 +625,20 @@ function New-AzConnectedKubernetes {
$PSBoundParameters.Add('ArcAgentryConfiguration', $arcAgentryConfigs)

Write-Output "Creating 'Kubernetes - Azure Arc' object in Azure"
Write-Debug "PSBoundParameters: $PSBoundParameters"
$Response = Az.ConnectedKubernetes.internal\New-AzConnectedKubernetes @PSBoundParameters

if ((-not $WhatIfPreference) -and (-not $Response)) {
Write-Error "Failed to create the 'Kubernetes - Azure Arc' resource."
return
}

$arcAgentryConfigs = ConvertTo-ArcAgentryConfiguration -ConfigurationSetting $ConfigurationSetting -RedactedProtectedConfiguration $RedactedProtectedConfiguration -CCRP $false
$arcAgentryConfigs = ConvertTo-ArcAgentryConfiguration `
-ConfigurationSetting $ConfigurationSetting `
-RedactedProtectedConfiguration $RedactedProtectedConfiguration `
-CCRP $false `
-Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) `
-Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -eq $true)

# Convert the $Response object into a nested hashtable.

Expand Down Expand Up @@ -643,7 +673,7 @@ function New-AzConnectedKubernetes {
Write-Debug "OCI Artifact location: ${helmValuesDp.repositoryPath}."

$registryPath = if ($env:HELMREGISTRY) { $env:HELMREGISTRY } else { $helmValuesDp.repositoryPath }
Write-Debug "RegistryPath: ${registryPath}."
Write-Debug "RegistryPath: ${registryPath}"

$helmValuesContent = $helmValuesDp.helmValuesContent
Write-Debug "Helm values: ${helmValuesContent}."
Expand All @@ -654,7 +684,7 @@ function New-AzConnectedKubernetes {
# hashtable.
$optionsFromDp = ""
foreach ($field in $helmValuesContent.PSObject.Properties) {
if($field.Value.StartsWith($ProtectedSettingsPlaceholderValue)){
if ($field.Value.StartsWith($ProtectedSettingsPlaceholderValue)) {
$parsedValue = $field.Value.Split(":")
# "${ProtectedSettingsPlaceholderValue}:${feature}:${setting}"
$field.Value = $ConfigurationProtectedSetting[$parsedValue[1]][$parsedValue[2]]
Expand All @@ -670,9 +700,15 @@ function New-AzConnectedKubernetes {

# Get helm chart path (within the OCI registry).
if ($PSCmdlet.ShouldProcess("configDP", "request Helm chart")) {
$chartPath = Get-HelmChartPath -registryPath $registryPath -kubeConfig $KubeConfig -kubeContext $KubeContext -helmClientLocation $HelmClientLocation
$chartPath = Get-HelmChartPath `
-registryPath $registryPath `
-kubeConfig $KubeConfig `
-kubeContext $KubeContext `
-helmClientLocation $HelmClientLocation `
-Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) `
-Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -eq $true)
if (Test-Path Env:HELMCHART) {
$ChartPath = Get-ChildItem -Path Env:HELMCHART
$ChartPath = Get-ChildItem -Path $Env:HELMCHART
}
}

Expand Down Expand Up @@ -722,9 +758,11 @@ function New-AzConnectedKubernetes {

if ($ExistConnectedKubernetes.ArcAgentProfileAgentState -eq "Succeeded") {
Write-Output "Cluster configuration succeeded."
} elseif ($ExistConnectedKubernetes.ArcAgentProfileAgentState -eq "Failed") {
}
elseif ($ExistConnectedKubernetes.ArcAgentProfileAgentState -eq "Failed") {
Write-Error "Cluster configuration failed."
} else {
}
else {
Write-Error "Cluster configuration timed out after 60 minutes."
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ param(
if ($PSBoundParameters.ContainsKey('KubeConfig')) {
$Null = $PSBoundParameters.Remove('KubeConfig')
} elseif (Test-Path Env:KUBECONFIG) {
$KubeConfig = Get-ChildItem -Path Env:KUBECONFIG
$KubeConfig = Get-ChildItem -Path $Env:KUBECONFIG
} elseif (Test-Path Env:Home) {
$KubeConfig = Join-Path -Path $Env:Home -ChildPath '.kube' | Join-Path -ChildPath 'config'
} else {
Expand All @@ -175,7 +175,10 @@ param(

#Region check helm install
try {
Set-HelmClientLocation
Set-HelmClientLocation `
-Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) `
-Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -eq $true)

$HelmVersion = helm version --short --kubeconfig $KubeConfig
if ($HelmVersion.Contains("v2")) {
Write-Error "Helm version 3+ is required. Ensure that you have installed the latest version of Helm. Learn more at https://aka.ms/arc/k8s/onboarding-helm-install"
Expand All @@ -187,7 +190,10 @@ param(
#Endregion

#Region get release namespace
$ReleaseInstallNamespace = Get-ReleaseInstallNamespace
$ReleaseInstallNamespace = Get-ReleaseInstallNamespace `
-Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) `
-Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -eq $true)

$ReleaseNamespace = $null
try {
$ReleaseNamespace = (helm status azure-arc -o json --kubeconfig $KubeConfig --kube-context $KubeContext -n $ReleaseInstallNamespace | ConvertFrom-Json).namespace
Expand Down
Loading

0 comments on commit 91ace9e

Please sign in to comment.