Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/ConnectedKubernetes/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
- Additional information about change #1
-->
## Upcoming Release
* Added optional configs (-ProxyHttp, -ProxyHttps, -NoProxy, -ProxyCert) for connection behind outbound proxy server.
* Added optional configs (-ContainerLogPath, -DisableAutoUpgrade, -NoWait, -OnboardingTimeout).
* Fixed invalid URI issue with display name of location.
* Fixed response can't be parsed issue with UseBasicParsing.

## Version 0.7.1
* Made `New-AzConnectedKubernetes` support PowerShell 5.
Expand Down
150 changes: 140 additions & 10 deletions src/ConnectedKubernetes/custom/New-AzConnectedKubernetes.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,55 @@ function New-AzConnectedKubernetes {
# The ID of the target subscription.
${SubscriptionId},

[Parameter()]
[Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Path')]
[System.Uri]
# The http URI of the proxy server for the kubernetes cluster to use
${ProxyHttp},

[Parameter()]
[Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Path')]
[System.Uri]
# The https URI of the proxy server for the kubernetes cluster to use
${ProxyHttps},

[Parameter()]
[Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Path')]
[System.String]
# The comma-separated list of hostnames that should be excluded from the proxy server for the kubernetes cluster to use
${ProxyNo},

[Parameter()]
[Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Path')]
[System.String]
# The path to the certificate file for proxy or custom Certificate Authority.
${ProxyCert},

[Parameter()]
[Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Path')]
[ValidateRange(0,3600)]
[Int]
# The time required (in seconds) for the arc-agent pods to be installed on the kubernetes cluster.
${OnboardingTimeout} = 600,

[Parameter()]
[Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Path')]
[System.Management.Automation.SwitchParameter]
# Flag to disable auto upgrade of arc agents.
${DisableAutoUpgrade},

[Parameter()]
[Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Path')]
[System.String]
# Override the default container log path to enable fluent-bit logging.
${ContainerLogPath},

[Parameter(HelpMessage="Path to the kube config file")]
[Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Body')]
[System.String]
# Path to the kube config file
${KubeConfig},

[Parameter(HelpMessage="Kubconfig context from current machine")]
[Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Body')]
[System.String]
Expand Down Expand Up @@ -172,14 +215,14 @@ function New-AzConnectedKubernetes {
[Parameter(DontShow)]
[Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Runtime')]
[System.Uri]
# The URI for the proxy server to use
# The URI of the proxy server for host os to use
${Proxy},

[Parameter(DontShow)]
[ValidateNotNull()]
[Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Runtime')]
[System.Management.Automation.PSCredential]
# Credentials for a proxy server to use for the remote call
# The credential of the proxy server for host os to use
${ProxyCredential},

[Parameter(DontShow)]
Expand Down Expand Up @@ -224,7 +267,7 @@ function New-AzConnectedKubernetes {
if ($PSBoundParameters.ContainsKey('KubeContext')) {
$Null = $PSBoundParameters.Remove('KubeContext')
}
if (($KubeContext -eq $null) -or ($KubeContext -eq '')) {
if (($null -eq $KubeContext) -or ($KubeContext -eq '')) {
$KubeContext = kubectl config current-context
}

Expand Down Expand Up @@ -260,9 +303,10 @@ function New-AzConnectedKubernetes {
#EndRegion

#Region get release namespace
Set-Variable ReleaseInstallNamespace -option Constant -value "azure-arc-release"
$ReleaseNamespace = $null
try {
$ReleaseNamespace = (helm status azure-arc -o json --kubeconfig $KubeConfig --kube-context $KubeContext | ConvertFrom-Json).namespace
$ReleaseNamespace = (helm status azure-arc -o json --kubeconfig $KubeConfig --kube-context $KubeContext -n $ReleaseInstallNamespace | ConvertFrom-Json).namespace
} catch {
Write-Error "Fail to find the namespace for azure-arc."
}
Expand Down Expand Up @@ -302,6 +346,15 @@ function New-AzConnectedKubernetes {
} else {
$ReleaseTrain = 'stable'
}

$AzLocation = Get-AzLocation | Where-Object { ($_.DisplayName -ieq $Location) -or ($_.Location -ieq $Location)}
$Region = $AzLocation.Location
if ($null -eq $Region) {
Write-Error "Invalid location: $Location"
return
} else {
$Location = $Region
}
$ChartLocationUrl = "https://${Location}.dp.kubernetesconfiguration.azure.com/azure-arc-k8sagents/GetLatestHelmPackagePath?api-version=2019-11-01-preview&releaseTrain=${ReleaseTrain}"

$Uri = [System.Uri]::New($ChartLocationUrl)
Expand All @@ -315,7 +368,7 @@ function New-AzConnectedKubernetes {
$HeaderParameter = @{
"Authorization" = "Bearer $AccessToken"
}
$Response = Invoke-WebRequest -Uri $Uri -Headers $HeaderParameter -Method Post
$Response = Invoke-WebRequest -Uri $Uri -Headers $HeaderParameter -Method Post -UseBasicParsing
if ($Response.StatusCode -eq 200) {
$RegisteryPath = ($Response.Content | ConvertFrom-Json).repositoryPath
} else {
Expand Down Expand Up @@ -354,27 +407,104 @@ function New-AzConnectedKubernetes {
. "$PSScriptRoot/../utils/RSAHelper.ps1"
$AgentPublicKey = ExportRSAPublicKeyBase64($RSA)
$AgentPrivateKey = ExportRSAPrivateKeyBase64($RSA)
$AgentPrivateKey = "-----BEGIN RSA PRIVATE KEY-----`n" + $AgentPrivateKey + "`n-----END RSA PRIVATE KEY-----"
} catch {
Write-Error "Unable to generate RSA keys"
throw
}
} else {
$AgentPublicKey = [System.Convert]::ToBase64String($RSA.ExportRSAPublicKey())
$AgentPrivateKey = "-----BEGIN RSA PRIVATE KEY-----`n" + [System.Convert]::ToBase64String($RSA.ExportRSAPrivateKey()) + "`n-----END RSA PRIVATE KEY-----"
$AgentPrivateKey = "-----BEGIN RSA PRIVATE KEY-----`n" + [System.Convert]::ToBase64String($RSA.ExportRSAPrivateKey()) + "`n-----END RSA PRIVATE KEY-----"
}

$HelmChartPath = Join-Path -Path $ChartExportPath -ChildPath 'azure-arc-k8sagents'
if (Test-Path Env:HELMCHART) {
$ChartPath = Get-ChildItem -Path Env:HELMCHART
} else {
$ChartPath = $HelmChartPath
}

#Region helm options
$options = ""
$proxyEnableState = $false
if (-not ([string]::IsNullOrEmpty($ProxyHttp))) {
$ProxyHttpStr = $ProxyHttp.ToString()
$ProxyHttpStr = $ProxyHttpStr -replace ',','\,'
$ProxyHttpStr = $ProxyHttpStr -replace '/','\/'
$options += " --set global.httpProxy=$ProxyHttpStr"
$proxyEnableState = $true
$Null = $PSBoundParameters.Remove('ProxyHttp')
}
if (-not ([string]::IsNullOrEmpty($ProxyHttps))) {
$ProxyHttpsStr = $ProxyHttps.ToString()
$ProxyHttpsStr = $ProxyHttpsStr -replace ',','\,'
$ProxyHttpsStr = $ProxyHttpsStr -replace '/','\/'
$options += " --set global.httpsProxy=$ProxyHttpsStr"
$proxyEnableState = $true
$Null = $PSBoundParameters.Remove('ProxyHttps')
}
if (-not ([string]::IsNullOrEmpty($ProxyNo))) {
$ProxyNo = $ProxyNo -replace ',','\,'
$ProxyNo = $ProxyNo -replace '/','\/'
$options += " --set global.noProxy=$ProxyNo"
$proxyEnableState = $true
$Null = $PSBoundParameters.Remove('ProxyNo')
}
if ($proxyEnableState) {
$options += " --set global.isProxyEnabled=true"
}
try {
if ((-not ([string]::IsNullOrEmpty($ProxyCert))) -and (Test-Path $ProxyCert)) {
$options += " --set-file global.proxyCert=$ProxyCert"
$options += " --set global.isCustomCert=true"
}
} catch {
Write-Error "Unable to find ProxyCert from file path"
throw
}
if ($DisableAutoUpgrade) {
$options += " --set systemDefaultValues.azureArcAgents.autoUpdate=false"
$Null = $PSBoundParameters.Remove('DisableAutoUpgrade')
}
if (-not ([string]::IsNullOrEmpty($ContainerLogPath))) {
$options += " --set systemDefaultValues.fluent-bit.containerLogPath=$ContainerLogPath"
$Null = $PSBoundParameters.Remove('ContainerLogPath')
}
if (-not ([string]::IsNullOrEmpty($KubeConfig))) {
$options += " --kubeconfig $KubeConfig"
}
if (-not ([string]::IsNullOrEmpty($KubeContext))) {
$options += " --kube-context $KubeContext"
}
if (!$NoWait) {
$options += " --wait --timeout $OnboardingTimeout"
$options += "s"
}
#Endregion
if ($PSBoundParameters.ContainsKey('OnboardingTimeout')) {
$PSBoundParameters.Remove('OnboardingTimeout')
}
if ((-not ([string]::IsNullOrEmpty($Proxy))) -and (-not $PSBoundParameters.ContainsKey('ProxyCredential'))) {
if (-not ([string]::IsNullOrEmpty($Proxy.UserInfo))) {
try{
$userInfo = $Proxy.UserInfo -Split ':'
$pass = ConvertTo-SecureString $userInfo[1] -AsPlainText -Force
$ProxyCredential = New-Object System.Management.Automation.PSCredential ($userInfo[0] , $pass)
$PSBoundParameters.Add('ProxyCredential', $ProxyCredential)
} catch {
Write-Warning "Please set ProxyCredential or provide username and password in the Proxy parameter"
throw
}
} else {
Write-Warning "If the proxy is a private proxy, pass ProxyCredential parameter or provide username and password in the Proxy parameter"
}
}

$PSBoundParameters.Add('AgentPublicKeyCertificate', $AgentPublicKey)
$Response = Az.ConnectedKubernetes.internal\New-AzConnectedKubernetes @PSBoundParameters

$TenantId = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext.Tenant.Id
helm upgrade --install azure-arc $ChartPath --set global.subscriptionId=$SubscriptionId --set global.resourceGroupName=$ResourceGroupName --set global.resourceName=$ClusterName --set global.tenantId=$TenantId --set global.location=$Location --set global.onboardingPrivateKey=$AgentPrivateKey --set systemDefaultValues.spnOnboarding=false --set global.azureEnvironment=AZUREPUBLICCLOUD --set systemDefaultValues.clusterconnect-agent.enabled=true --set global.kubernetesDistro=$Distribution --set global.kubernetesInfra=$Infrastructure --kubeconfig $KubeConfig --kube-context $KubeContext --wait --timeout 600s
$TenantId = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext.Tenant.Id
helm upgrade --install azure-arc $ChartPath --namespace $ReleaseInstallNamespace --create-namespace --set global.subscriptionId=$SubscriptionId --set global.resourceGroupName=$ResourceGroupName --set global.resourceName=$ClusterName --set global.tenantId=$TenantId --set global.location=$Location --set global.onboardingPrivateKey=$AgentPrivateKey --set systemDefaultValues.spnOnboarding=false --set global.azureEnvironment=AZUREPUBLICCLOUD --set systemDefaultValues.clusterconnect-agent.enabled=true --set global.kubernetesDistro=$Distribution --set global.kubernetesInfra=$Infrastructure (-split $options)
Return $Response
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,10 @@ param(
#Endregion

#Region get release namespace
Set-Variable ReleaseInstallNamespace -option Constant -value "azure-arc-release"
$ReleaseNamespace = $null
try {
$ReleaseNamespace = (helm status azure-arc -o json --kubeconfig $KubeConfig --kube-context $KubeContext | ConvertFrom-Json).namespace
$ReleaseNamespace = (helm status azure-arc -o json --kubeconfig $KubeConfig --kube-context $KubeContext -n $ReleaseInstallNamespace | ConvertFrom-Json).namespace
} catch {
Write-Error "Fail to find the namespace for azure-arc."
}
Expand All @@ -211,7 +212,7 @@ param(
}
if (($ResourceGroupName -eq $ConfigmapRgName) -and ($ClusterName -eq $ConfigmapClusterName)) {
Az.ConnectedKubernetes.internal\Remove-AzConnectedKubernetes @PSBoundParameters
helm delete azure-arc --namespace $ReleaseNamespace --kubeconfig $KubeConfig --kube-context $KubeContext
helm delete azure-arc --namespace $ReleaseInstallNamespace --kubeconfig $KubeConfig --kube-context $KubeContext
} else {
Write-Error "The current context in the kubeconfig file does not correspond to the connected cluster resource specified. Agents installed on this cluster correspond to the resource group name '$ConfigmapRgName' and resource name '$ConfigmapClusterName'."
}
Expand Down
56 changes: 55 additions & 1 deletion src/ConnectedKubernetes/examples/New-AzConnectedKubernetes.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,58 @@ Location Name ResourceGroupName
eastus azps_test_cluster_ahb azps_test_group
```

Using [-AcceptEULA] will default to your acceptance of the terms of our legal agreement and create a connected kubernetes.
Using [-AcceptEULA] will default to your acceptance of the terms of our legal agreement and create a connected kubernetes.

### Example 5: Create a connected kubernetes with parameters ProxyHttp, ProxyHttps, ProxyNo and Proxy.
```powershell
New-AzConnectedKubernetes -ClusterName azps_test_cluster_ahb -ResourceGroupName azps_test_group -Location eastus -KubeConfig $HOME\.kube\config -KubeContext azps_aks_t01 -ProxyHttp "http://proxy-user:proxy-password@proxy-ip:port" -ProxyHttps "http://proxy-user:proxy-password@proxy-ip:port" -ProxyNo "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,10.43.0.0/24,.svc" -Proxy "http://proxy-user:proxy-password@proxy-ip:port"
```

```output
Location Name ResourceGroupName
-------- ---- -----------------
eastus azps_test_cluster_ahb azps_test_group
```

This command creates a connected kubernetes with parameters ProxyHttp, ProxyHttps, ProxyNo and Proxy.

### Example 6: Create a connected kubernetes with parameters ProxyHttp, ProxyHttps, ProxyNo, Proxy and ProxyCredential.
```powershell
$pwd = ConvertTo-SecureString "proxy-password" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential ("proxy-user", $pwd)
New-AzConnectedKubernetes -ClusterName azps_test_cluster_ahb -ResourceGroupName azps_test_group -Location eastus -KubeConfig $HOME\.kube\config -KubeContext azps_aks_t01 -ProxyHttp "http://proxy-user:proxy-password@proxy-ip:port" -ProxyHttps "http://proxy-user:proxy-password@proxy-ip:port" -ProxyNo "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,10.43.0.0/24,.svc" -Proxy "http://proxy-ip:port" -ProxyCredential $cred
```

```output
Location Name ResourceGroupName
-------- ---- -----------------
eastus azps_test_cluster_ahb azps_test_group
```

This command creates a connected kubernetes with parameters ProxyHttp, ProxyHttps, ProxyNo, Proxy and ProxyCredential.

### Example 7: Create a connected kubernetes and disable auto upgrade of arc agents.
```powershell
New-AzConnectedKubernetes -ClusterName azps_test_cluster -ResourceGroupName azps_test_group -Location eastus -DisableAutoUpgrade
```

```output
Location Name ResourceGroupName
-------- ---- -----------------
eastus azps_test_cluster azps_test_group
```

This command creates a connected kubernetes and disable auto upgrade of arc agents.

### Example 8: Create a connected kubernetes with custom onboarding timeout.
```powershell
New-AzConnectedKubernetes -ClusterName azps_test_cluster -ResourceGroupName azps_test_group -Location eastus -OnboardingTimeout 600
```

```output
Location Name ResourceGroupName
-------- ---- -----------------
eastus azps_test_cluster azps_test_group
```

This command creates a connected kubernetes with custom onboarding timeout.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Describe 'AzConnectedKubernetes' {
$config.ProvisioningState | Should -Be 'Succeeded'

# Clear helm azure-arc environment
helm delete azure-arc --no-hooks
helm delete azure-arc -n azure-arc-release --no-hooks
Copy link
Contributor

@BethanyZhou BethanyZhou Feb 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you tested your command on you local?

  • To run command on your local, please use ./run-module.ps1 after build-module.ps1
  • To record test cases, remove -Skip from It 'CreateExpanded' -skip, see L15 and I'm expecting there is a json file under test folder.

Test always is the best way to ensure quality of commands. Your test case is always skipped with -Skip Tag

Copy link
Contributor Author

@yinghsugn yinghsugn Feb 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing out this. We've tested the code with integration test and end-to-end test with the product team for GA.
I think the -Skip was there because of the test case needs pre-requisites on the environment before it started.
1.Prepare your machines for AKS Edge Essentials
2.Create a kubernetes cluster on the machine
3.Set file path for helm.exe

Copy link
Contributor

@BethanyZhou BethanyZhou Feb 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. I'd recommend preparing the environment in

if possible or make this test as -Live. -Live means this test case only can be tested in real test environment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the dev and test env are separated at this moment.
Maybe we can enhance the test once the product have better integration with the dev machine.

} | Should -Not -Throw
}

Expand Down
30 changes: 3 additions & 27 deletions src/ConnectedKubernetes/utils/RSAHelper.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,7 @@ function ExportRSAPrivateKeyBase64{

$base64 = [Convert]::ToBase64String($stream.GetBuffer(), 0, ([int]($stream.Length)))

$offset = 0
$line_length = 64

$sb = [System.Text.StringBuilder]::new()
[void]$sb.AppendLine("-----BEGIN RSA PRIVATE KEY-----")
while ($offset -lt $base64.Length) {
$line_end = [Math]::Min($offset + $line_length, $base64.Length)
[void]$sb.AppendLine($base64.Substring($offset, $line_end - $offset))
$offset = $line_end
}

[void]$sb.AppendLine("-----END RSA PRIVATE KEY-----")

return $sb.ToString()
return $base64
}
}

Expand All @@ -58,7 +45,7 @@ function ExportRSAPublicKeyBase64{
[byte]$Sequence = 0x30
$stream = [System.IO.MemoryStream]::new()
$writer = [System.IO.BinaryWriter]::new($stream)
$writer.Write($Sequence); # SEQUENCE
$writer.Write($Sequence);
$innerStream = [System.IO.MemoryStream]::new()
$innerWriter = [System.IO.BinaryWriter]::new($innerStream)
EncodeIntegerBigEndian $innerWriter $RSAParams.Modulus
Expand All @@ -70,18 +57,7 @@ function ExportRSAPublicKeyBase64{

$base64 = [Convert]::ToBase64String($stream.GetBuffer(), 0, ([int]($stream.Length)))

$offset = 0
$line_length = 64

$sb = [System.Text.StringBuilder]::new()

while ($offset -lt $base64.Length) {
$line_end = [Math]::Min($offset + $line_length, $base64.Length)
[void]$sb.AppendLine($base64.Substring($offset, $line_end - $offset))
$offset = $line_end
}

return $sb.ToString()
return $base64
}
}

Expand Down