Created
April 12, 2016 19:08
-
-
Save Pyromaniaxxx/c762844a3361ae59c26da84d25d88bbe to your computer and use it in GitHub Desktop.
Nested Azure Stack 環境を展開するためのスクリプト的な何か
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.DESCRIPTION | |
Nested Azure Stack Deployment Script | |
.EXAMPLE | |
.\Deploy-NestedAzureStack.ps1 -Verbose | |
.EXAMPLE | |
.\Deploy-NestedAzureStack.ps1 -Verbose -VMName "HostName" -Remove | |
.EXAMPLE | |
.\Deploy-NestedAzureStack.ps1 -VMName "MAS" -CPUCore 32 -RAMSize 184 -DiskSize 200 -Verbose | |
.INPUTS | |
[System.String] | |
[System.Int] | |
.NOTES | |
Auther : S.Hiro | |
Create : 2016/02/01 | |
version: 2.0.0 | |
スクリプトと同じフォルダに「MAS_POC_Data」フォルダを作成し | |
Azure Stack TP1 の解凍したデータをすべて配置する。 | |
"Deploy Parameter" の項目を各自の環境の合わせ修正する。 | |
#> | |
param | |
( | |
[CmdletBinding()] | |
[Parameter(Mandatory=$false)] | |
[String]$VMName = "MAS101", | |
[Parameter(Mandatory=$false)] | |
[int]$CPUCore = 32, | |
[Parameter(Mandatory=$false)] | |
[int]$RAMSize = 120, | |
[Parameter(Mandatory=$false)] | |
[int]$DiskSize = 200, | |
[Parameter(Mandatory=$false)] | |
[switch]$Remove , | |
[Parameter(Mandatory=$false)] | |
[switch]$HostSetupOnlyMode | |
) | |
$Error.Clear() | |
$ErrorActionPreference = "Stop" | |
$Msg = "┏━━━━━━━━━━━━━━━━━━━━━━━┓`r`n ━╋━━┃ ┃┃ ━━┓ `r`n ┏╋┳┓┃ ┣╋┓ ━━┻┓`r`n ┃┃┃┃┃ ┃┃┃━━━ ┏━━┫`r`n ┗┻┛┛┗━━┛ ┃┃┛ ┗━━┛`r`n ━━━╋ ╋━━ ┏┛`r`n ┏━┫ ┃━━┓ ━━┓┏┛ `r`n ┗━┫ ┃ ┃┗┓ `r`n ━┛ ┃┗━━ ━┛ ┗┓`r`n Power of Azure in your datacenter!! `r`n┗━━━━━━━━━━━━━━━━━━━━━━━┛`r`n " | |
Write-Host $Msg -ForegroundColor Cyan | |
#### Deploy Parameter #### | |
$DeployPath = "C:\VMData" | |
$DeployDataDiskPath = "D:\VMData" | |
$VlanID = 0 | |
$vSwitchName = "External" | |
# MAS Host settings | |
$HostVMIP = "10.10.100.1" | |
$HostVMMASK = "24" | |
$HostVMGW = "10.10.100.250" | |
$HostVMDNS = "10.10.100.249" | |
# MAS NATVM settings | |
$NATVMIP = "10.10.100.11/24" | |
$NATVMGW = "10.10.100.250" | |
### credential | |
# MAS Host | |
$LoginUser = "Administrator" | |
$LoginPass = "password!" | |
# VM Password | |
$VMPassword = "password!" | |
# AAD | |
$AADAccount = "MASAdmin@testTenant.onmicrosoft.com" | |
$AADPassword = "password!" | |
$initializeMessage = "initialize" | |
Write-Progress $initializeMessage | |
#region ## initialize | |
$MasterVHDPath = $PSScriptRoot | |
$MasterVHDName = "AzureStackBoot.vhdx" | |
$mp1 = "$MasterVHDPath\MAS_POC_Data\WindowsServer2016Datacenter.vhdx" | |
$MasterPath = (Join-Path $MasterVHDPath $MasterVHDName) | |
$VMPath = (Join-Path $DeployPath $VMName) | |
$VMRAMSize = $RAMSize * 1GB | |
$VMDiskSize = $DiskSize * 1GB | |
$VMDiskCount = 4 | |
$NicName = "Eth01" | |
$AADTenant = $AADAccount.Split('@')[1].ToString() | |
$Sstr = ConvertTo-SecureString $LoginPass -AsPlainText -Force | |
$psc = New-Object System.Management.Automation.PsCredential($LoginUser, $Sstr) | |
$Cred = Get-Credential -Credential $psc | |
$Unattend = @' | |
<unattend xmlns="urn:schemas-microsoft-com:unattend" xmlns:ms="urn:schemas-microsoft-com:asm.v3"> | |
<settings pass="specialize"> | |
<component name="Microsoft-Windows-Security-Licensing-SLC-UX" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" processorArchitecture="amd64"> | |
<SkipAutoActivation>true</SkipAutoActivation> | |
</component> | |
<component name="Microsoft-Windows-Shell-Setup" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" processorArchitecture="amd64"> | |
<ComputerName>SamplePOCHost</ComputerName> | |
</component> | |
<component name="Microsoft-Windows-ServerManager-SvrMgrNc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |
<DoNotOpenServerManagerAtLogon>true</DoNotOpenServerManagerAtLogon> | |
</component> | |
<component name="Microsoft-Windows-IE-ESC" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |
<IEHardenAdmin>false</IEHardenAdmin> | |
<IEHardenUser>false</IEHardenUser> | |
</component> | |
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |
<fDenyTSConnections>false</fDenyTSConnections> | |
</component> | |
<component name="Microsoft-Windows-TerminalServices-RDP-WinStationExtensions" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |
<UserAuthentication>0</UserAuthentication> | |
</component> | |
</settings> | |
<settings pass="oobeSystem"> | |
<component name="Microsoft-Windows-Security-Licensing-SLC-UX" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" processorArchitecture="amd64"> | |
<SkipAutoActivation>true</SkipAutoActivation> | |
</component> | |
<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |
<InputLocale>en-US</InputLocale> | |
<SystemLocale>en-US</SystemLocale> | |
<UILanguage>en-US</UILanguage> | |
<UserLocale>en-US</UserLocale> | |
</component> | |
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |
<AutoLogon> | |
<Enabled>true</Enabled> | |
<Username>Administrator</Username> | |
<Domain>.</Domain> | |
<Password> | |
<Value>SampleVMPassword</Value> | |
<PlainText>true</PlainText> | |
</Password> | |
</AutoLogon> | |
<UserAccounts> | |
<AdministratorPassword> | |
<Value>SampleVMPassword</Value> | |
<PlainText>true</PlainText> | |
</AdministratorPassword> | |
</UserAccounts> | |
<Display> | |
<HorizontalResolution>1024</HorizontalResolution> | |
<VerticalResolution>768</VerticalResolution> | |
</Display> | |
<OOBE> | |
<SkipMachineOOBE>true</SkipMachineOOBE> | |
<SkipUserOOBE>true</SkipUserOOBE> | |
<HideEULAPage>true</HideEULAPage> | |
<HideLocalAccountScreen>true</HideLocalAccountScreen> | |
<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> | |
<HideOnlineAccountScreens>true</HideOnlineAccountScreens> | |
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> | |
<NetworkLocation>Other</NetworkLocation> | |
<ProtectYourPC>3</ProtectYourPC> | |
</OOBE> | |
<TimeZone>Pacific Standard Time</TimeZone> | |
<!--TODO: azure pack self-signed certificate has wrong valid time--> | |
</component> | |
</settings> | |
</unattend> | |
'@ | |
$Unattend = $Unattend.Replace("SamplePOCHost","POCHost01") | |
$Unattend = $Unattend.Replace("SampleVMPassword",$VMPassword) | |
$DeployScript = @' | |
# Admin credential | |
$AdminSstr = ConvertTo-SecureString 'SampleVMPassword' -AsPlainText -Force | |
# AAD credential | |
$LoginUser = 'SampleAADAccount' | |
$LoginPass = 'SampleAADPassword' | |
$Sstr = ConvertTo-SecureString $LoginPass -AsPlainText -Force | |
$psc = New-Object System.Management.Automation.PsCredential($LoginUser, $Sstr) | |
$AADCred = Get-Credential -Credential $psc | |
C:\MAS_POC_Data\DeployAzureStack.ps1 -AdminPassword $AdminSstr -AADCredential $AADCred -AADTenant "SampleAADTenant" -NATVMStaticIP "SampleNATVMIP" -NATVMStaticGateway "SampleNATVMGW" -Verbose -Force | |
'@ | |
$DeployScript = $DeployScript.Replace("SampleVMPassword",$VMPassword) | |
$DeployScript = $DeployScript.Replace("SampleAADAccount",$AADAccount) | |
$DeployScript = $DeployScript.Replace("SampleAADPassword",$AADPassword) | |
$DeployScript = $DeployScript.Replace("SampleAADTenant",$AADTenant) | |
$DeployScript = $DeployScript.Replace("SampleNATVMIP",$NATVMIP) | |
$DeployScript = $DeployScript.Replace("SampleNATVMGW",$NATVMGW) | |
#endregion | |
Write-Progress $initializeMessage -Completed | |
## remove switch process | |
if ($Remove) | |
{ | |
Write-Host " Starting Nested Azure Stack vm remove ! " -ForegroundColor Yellow | |
if (@(Get-VM | Where-Object {$_.Name -eq $VMName}).Count -eq 1) | |
{ | |
Stop-VM -Name $VMName -Force | |
Remove-VM -Name $VMName -Force -Confirm:$false | |
} | |
if (Test-Path $VMPath ) | |
{ | |
Remove-Item -Path $VMPath -Force -Confirm:$false -Recurse | |
} | |
Get-ChildItem $DeployDataDiskPath | Remove-Item -Force -Confirm:$false -Recurse | |
Write-Host " complete ! " -ForegroundColor Yellow | |
return $true | |
} | |
else | |
{ | |
Write-Host " Starting Nested Azure Stack Deployment! " -ForegroundColor Green | |
} | |
## Master VHDX Copy | |
if (-not(Test-Path $MasterPath)) | |
{ | |
Write-Verbose -Message (" Master VHDX Copy") | |
Copy-Item $mp1 $MasterPath | |
} | |
#region ## pre check | |
Write-Verbose -Message (" pre check ") | |
if (@(Get-VM | Where-Object {$_.Name -eq $VMName}).Count -ne 0) | |
{ | |
Write-Warning -Message "Already there is a VM with the same name." | |
return $false | |
} | |
if (Test-Path $VMPath ) | |
{ | |
Write-Warning -Message "Already folder of the same name exists." | |
return $false | |
} | |
$mem = Get-WmiObject -Class Win32_ComputerSystem | |
$totalMemory = $mem.TotalPhysicalMemory / 1GB | |
if ($totalMemory -lt ($RAMSize + 5 )) | |
{ | |
Write-Warning -Message ("Check system memory requirement failed. At least {0}GB physical memory is required." -f ( $RAMSize + 5)) | |
return $false | |
} | |
if ($RAMSize -lt 65) | |
{ | |
$MemChkErrMsg = "`r`n Azure Stack Host に割り当てるメモリが不足しています。`r`n Azure Stack Technical Preview 1の展開には65GB以上のメモリを指定する必要があります。" | |
Write-Error -Message $MemChkErrMsg | |
return $false | |
} | |
#endregion | |
Write-Verbose -Message (" ----- ----- ") | |
Write-Verbose -Message (" VM Name : {0}" -f $VMName) | |
Write-Verbose -Message (" CPU Core : {0}" -f $CPUCore) | |
Write-Verbose -Message (" Memory : {0} ({1})" -f $RAMSize, $VMRAMSize) | |
Write-Verbose -Message (" Disk Size: {0} ({1})" -f $DiskSize, $VMDiskSize) | |
Write-Verbose -Message (" vSwitch : {0}" -f $vSwitchName) | |
Write-Verbose -Message (" VLAN : {0}" -f $VlanID) | |
Write-Verbose -Message (" VM Path : {0}" -f $DeployPath) | |
Write-Verbose -Message (" ----- ----- ") | |
$MountVHDMessage = "Mounting Nested Azure Stack HOST VHDX" | |
Write-Progress $MountVHDMessage | |
#region ## MASTER VHDX settings | |
Write-Progress $MountVHDMessage -Status " Mount Master VHDX" -PercentComplete 0 | |
Dismount-DiskImage -ImagePath $MasterPath | |
$MountDisk = Mount-DiskImage -ImagePath $MasterPath -StorageType vhdx | |
$DriveLetter = (Get-DiskImage -ImagePath $MasterPath | Get-Disk | Get-Partition | Get-Volume).DriveLetter | |
Write-Verbose -Message (" Mounted Master VHDX") | |
Write-Progress $MountVHDMessage -Status " unattend.xml updating" -PercentComplete 20 | |
$Unattend | Out-File "$DriveLetter`:\Windows\Panther\unattend.xml" -Force | |
Write-Verbose -Message (" unattend.xml updated") | |
if (-not(Test-Path "$DriveLetter`:\MAS_POC_Data")) | |
{ | |
Write-Progress $MountVHDMessage -Status " Copy PoC Data" -PercentComplete 40 | |
Copy-Item "$PSScriptRoot\MAS_POC_Data\" "$DriveLetter`:\MAS_POC_Data\" -Recurse | |
Write-Verbose -Message (" Copied PoC Data") | |
} | |
Write-Progress $MountVHDMessage -Status " Copy DeployStart.ps1 " -PercentComplete 60 | |
$DeployScript | Out-File "$DriveLetter`:\MAS_POC_Data\DeployStart.ps1" | |
Write-Verbose -Message (" Copied DeployStart.ps1") | |
Write-Progress $MountVHDMessage -Status " Dismount Master VHDX" -PercentComplete 80 | |
Dismount-DiskImage -ImagePath $MasterPath | |
Write-Verbose -Message (" Dismount Master VHDX") | |
#endregion | |
Write-Progress $MountVHDMessage -Completed | |
$NewVMMessage = "create virtual machine" | |
Write-Progress $NewVMMessage | |
#region ## create vm | |
Write-Progress $NewVMMessage -Status " Create Virtual Machine" -PercentComplete 0 | |
$VM = New-VM -Name $VMName -MemoryStartupBytes $VMRAMSize -Path $DeployPath -NoVHD -Generation 2 | |
Remove-VMNetworkAdapter -VM $VM | |
Write-Verbose -Message (" Created VM") | |
Set-VMProcessor -VM $VM -Count $CPUCore | |
Write-Progress $NewVMMessage -Status " Copy master VHDX" -PercentComplete 16 | |
Copy-Item $MasterPath $VMPath | |
Write-Verbose -Message (" Copied master VHDX ") | |
Write-Progress $NewVMMessage -Status " Copy master VHDX" -PercentComplete 33 | |
Add-VMHardDiskDrive -VM $VM -Path (Join-Path $VMPath $MasterVHDName) | |
Write-Verbose -Message (" Attachd master VHDX ") | |
Write-Progress $NewVMMessage -Status " Change Boot Device" -PercentComplete 50 | |
Set-VMFirmware -VM $VM -FirstBootDevice (Get-VMHardDiskDrive -VM $VM) | |
Write-Verbose -Message (" Changed Boot Device ") | |
Write-Progress $NewVMMessage -Status " Add Network Adapter" -PercentComplete 66 | |
Add-VMNetworkAdapter -VM $VM -SwitchName $vSwitchName -Name $NicName | |
Write-Verbose -Message (" Added a network adapter") | |
Write-Progress $NewVMMessage -Status " set NIC Device Naming" -PercentComplete 83 | |
Set-VMNetworkAdapter -VM $VM -Name $NicName -DeviceNaming On | |
Write-Verbose -Message (" NIC Device Naming configuration ") | |
Write-Progress $NewVMMessage -Status " set VLAN" -PercentComplete 100 | |
if ($VlanID -eq 0) | |
{ | |
Write-Verbose -Message (" None VLAN configuration") | |
} | |
else | |
{ | |
Set-VMNetworkAdapterVlan -VM $VM -VMNetworkAdapterName $NicName -VlanId $VlanID -Access | |
Write-Verbose -Message (" VLAN configuration : $VlanID") | |
} | |
#endregion | |
Write-Progress $NewVMMessage -Completed | |
$NewDiskMessage = "Create & Attach Data Disk" | |
Write-Progress $NewDiskMessage | |
#region ## Create & Attach Data Disk | |
$Disk = @{} | |
for ($i = 1; $i -le $VMDiskCount; $i++) | |
{ | |
Write-Progress -id 2 "Create VHDX" -Status " Create $i/$VMDiskCount" -PercentComplete ((100/$VMDiskCount) * $i) | |
$Disk[$i] = New-VHD -Path (Join-Path $DeployDataDiskPath "MASDATA_0$i.vhdx") -SizeBytes $VMDiskSize -Fixed | |
Write-Verbose -Message (" Created Data Disk $i") | |
} | |
Write-Progress -id 2 "Create VHDX" -Completed | |
Write-Progress $NewDiskMessage -PercentComplete 50 | |
for ($i = 1; $i -le $VMDiskCount; $i++) | |
{ | |
Write-Progress -id 2 "Attach VHDX" -Status " Attach VHDX $i/$VMDiskCount" -PercentComplete ((100/$VMDiskCount) * $i) | |
Add-VMHardDiskDrive -VM $VM -ControllerType SCSI -Path $Disk[$i].Path | |
Write-Verbose -Message (" Attached Data Disk $i") | |
} | |
Write-Progress -id 2 "Attach VHDX" -Completed | |
#endregion | |
Write-Progress $NewDiskMessage -Completed | |
$EnableNestedVmMessage = " Enable Nest Hyper-V" | |
Write-Progress $EnableNestedVmMessage | |
Set-VMProcessor -VMName $VMName -ExposeVirtualizationExtensions $true | |
Set-VMNetworkAdapter -VMName $VMName -MacAddressSpoofing on | |
Write-Progress $EnableNestedVmMessage -Completed | |
$VMinitialSettingsMessage = " Start VM Initial Settings" | |
Write-Progress $VMinitialSettingsMessage | |
#region ## VM Initial Settings | |
Start-VM -VMName $VMName | |
Write-Progress $VMinitialSettingsMessage -PercentComplete 11 | |
$SleepTime = 120 | |
$VMBootCheckMessage = " VM boot check" | |
Write-Progress -Id 1 $VMBootCheckMessage -Status " Process Sleep $SleepTime Seconds" | |
for ($i = 1; $i -lt ($SleepTime/10); $i++) | |
{ | |
sleep -Seconds ($SleepTime/10) | |
Write-Progress -Id 1 $VMBootCheckMessage -Status " Process Sleep $SleepTime Seconds" -PercentComplete ($i * (1000/$SleepTime)) | |
} | |
Write-Progress -Id 1 $VMBootCheckMessage -Completed | |
Write-Verbose -Message (" boot host vm ") | |
Write-Progress $VMinitialSettingsMessage -Status " Invoke Get Process" -PercentComplete 22 | |
$Proc = Invoke-Command -VMName $VMName -ScriptBlock { Get-Process | Where-Object {$_.ProcessName -like "*Sys*"} } -Credential $Cred | |
Write-Progress $VMinitialSettingsMessage -Status " IP address configuration" -PercentComplete 33 | |
$ret3 = Invoke-Command -VMName $VMName -ScriptBlock { New-NetIPAddress -InterfaceAlias ((Get-NetAdapterAdvancedProperty | Where-Object {$_.DisplayValue -EQ $args[0]}).Name) -IPAddress $args[1] -PrefixLength $args[2] -DefaultGateway $args[3]} -Credential $Cred -ArgumentList $NicName,$HostVMIP,$HostVMMASK,$HostVMGW | |
Write-Verbose -Message (" IP address configuration") | |
Write-Progress $VMinitialSettingsMessage -Status " DNS configuration" -PercentComplete 44 | |
$ret4 = Invoke-Command -VMName $VMName -ScriptBlock { Set-DnsClientServerAddress -InterfaceIndex ((Get-NetIPAddress |Where-Object {$_.Interfacealias -eq ((Get-NetAdapterAdvancedProperty | Where-Object {$_.DisplayValue -EQ $args[0]}).Name)}).InterfaceIndex) -ServerAddresses $args[1]} -Credential $Cred -ArgumentList $NicName,$HostVMDNS | |
Write-Verbose -Message (" DNS configuration") | |
Write-Progress $VMinitialSettingsMessage -Status " disable IPv6" -PercentComplete 56 | |
$ret2 = Invoke-Command -VMName $VMName -ScriptBlock { Get-NetAdapter | ForEach-Object {Disable-NetAdapterBinding -Name $_.Name -ComponentID ms_tcpip6 }} -Credential $Cred | |
Write-Verbose -Message (" disable IPv6") | |
Write-Progress $VMinitialSettingsMessage -Status " set Admin account never Password Expires" -PercentComplete 67 | |
$ret1 = Invoke-Command -VMName $VMName -ScriptBlock { wmic useraccount where "Name='Administrator'" Set PasswordExpires=FALSE} -Credential $Cred | |
Write-Verbose -Message (" set Admin account never Password Expires ") | |
Write-Progress $VMinitialSettingsMessage -Status " Enable Remote Desktop FirewallRules " -PercentComplete 78 | |
$ret1 = Invoke-Command -VMName $VMName -ScriptBlock { Enable-NetFirewallRule -DisplayGroup "Remote Desktop" } -Credential $Cred | |
Write-Verbose -Message (" Enable Remote Desktop FirewallRules ") | |
Write-Progress $VMinitialSettingsMessage -Status " delete unattend.xml file" -PercentComplete 89 | |
Invoke-Command -VMName $VMName -ScriptBlock { Remove-Item "c:\Windows\Panther\unattend.xml" -Force} -Credential $Cred | |
Write-Verbose -Message (" remove unattend.xml") | |
#endregion | |
Write-Progress $VMinitialSettingsMessage -Completed | |
Write-Verbose -Message (" starting vmconnect.exe") | |
vmconnect.exe localhost $VMName | |
if ($HostSetupOnlyMode) | |
{ | |
Write-Verbose -Message (" host setup only mode") | |
return " Host Deploy Script complete !" | |
} | |
else | |
{ | |
Write-Verbose -Message (" process sleep 10 Second...") | |
sleep -Seconds 10 | |
Write-Verbose -Message (" Starting Azure Stack Deploy Script") | |
Invoke-Command -VMName $VMName -ScriptBlock { C:\MAS_POC_Data\DeployStart.ps1 } -Credential $Cred | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment