Skip to content

Commit 858a3c9

Browse files
authored
New-AzVM bug: when image id is provided, fails to find version (#23897)
* dev code * test trying * test manually works but cannot record bc gallery image not found * manual test * changelog * cleanup * cleanup
1 parent 130f102 commit 858a3c9

File tree

4 files changed

+260
-46
lines changed

4 files changed

+260
-46
lines changed

src/Compute/Compute.Test/ScenarioTests/VirtualMachineTests.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,5 +626,12 @@ public void TestVMDefaultsToTrustedLaunchWithNullEncryptionAtHost()
626626
{
627627
TestRunner.RunTestScript("Test-VMDefaultsToTrustedLaunchWithNullEncryptionAtHost");
628628
}
629+
630+
[Fact]
631+
[Trait(Category.AcceptanceType, Category.LiveOnly)]
632+
public void TestVMTLWithGallerySourceImage()
633+
{
634+
TestRunner.RunTestScript("Test-VMTLWithGallerySourceImage");
635+
}
629636
}
630637
}

src/Compute/Compute.Test/ScenarioTests/VirtualMachineTests.ps1

Lines changed: 176 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# ----------------------------------------------------------------------------------
1+
# ----------------------------------------------------------------------------------
22
#
33
# Copyright Microsoft Corporation
44
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -7266,3 +7266,178 @@ function Test-VMDefaultsToTrustedLaunchWithNullEncryptionAtHost
72667266
Clean-ResourceGroup $rgname;
72677267
}
72687268
}
7269+
7270+
<#
7271+
.SYNOPSIS
7272+
Tests that a VM that is created from a Gallery Image source
7273+
does not error out due to TL defaulting code that looks for the image version
7274+
assuming it has a different format.
7275+
#>
7276+
function Test-VMTLWithGallerySourceImage
7277+
{
7278+
# Setup
7279+
$rgname = Get-ComputeTestResourceName;
7280+
$loc = Get-ComputeVMLocation;
7281+
7282+
try
7283+
{
7284+
$location = $loc;
7285+
New-AzResourceGroup -Name $rgname -Location $loc -Force;
7286+
# create credential
7287+
$password = Get-PasswordForVM;
7288+
$securePassword = $password | ConvertTo-SecureString -AsPlainText -Force;
7289+
$user = Get-ComputeTestResourceName;
7290+
$cred = New-Object System.Management.Automation.PSCredential ($user, $securePassword);
7291+
7292+
# Add one VM from creation
7293+
$vmname = 'vm' + $rgname;
7294+
$domainNameLabel = "d1" + $rgname;
7295+
$securityType_TL = "TrustedLaunch";
7296+
$PublisherName = "MicrosoftWindowsServer";
7297+
$Offer = "WindowsServer";
7298+
$SKU = "2022-datacenter-azure-edition";
7299+
$version = "latest";
7300+
$disable = $false;
7301+
$enable = $true;
7302+
$galleryName = "g" + $rgname;
7303+
$vnetname = "vn" + $rgname;
7304+
$vnetAddress = "10.0.0.0/16";
7305+
$subnetname = "slb" + $rgname;
7306+
$subnetAddress = "10.0.2.0/24";
7307+
$pubipname = "p" + $rgname;
7308+
$OSDiskName = $vmname + "-osdisk";
7309+
$NICName = $vmname+ "-nic";
7310+
$NSGName = $vmname + "-NSG";
7311+
$nsgrulename = "nsr" + $rgname;
7312+
$OSDiskSizeinGB = 128;
7313+
$VMSize = "Standard_DS2_v2";
7314+
$vmname2 = "2" + $vmname;
7315+
7316+
# Gallery
7317+
$resourceGroup = $rgname
7318+
$galleryName = 'gl' + $rgname
7319+
$definitionName = 'def' + $rgname
7320+
$skuDetails = @{
7321+
Publisher = 'test'
7322+
Offer = 'test'
7323+
Sku = 'test'
7324+
}
7325+
$osType = 'Windows'
7326+
$osState = 'Specialized'
7327+
[bool]$trustedLaunch = $false
7328+
$storageAccountSku = 'Standard_LRS'
7329+
$hyperVGeneration = 'v1'
7330+
7331+
# create new VM
7332+
$paramNewAzVm = @{
7333+
ResourceGroupName = $resourceGroup
7334+
Name = $vmName
7335+
Credential = $cred
7336+
Location = $location
7337+
ErrorAction = 'Stop'
7338+
}
7339+
if ($trustedLaunch -eq $false) {
7340+
$paramNewAzVm.Add('SecurityType', 'Standard')
7341+
}
7342+
$vm = New-AzVM @paramNewAzVm
7343+
7344+
# Setup Image Gallery
7345+
New-AzGallery -ResourceGroupName $resourceGroup -Name $galleryName -location $location -ErrorAction 'Stop' | Out-Null
7346+
7347+
# Setup Image Definition
7348+
$paramNewAzImageDef = @{
7349+
ResourceGroupName = $resourceGroup
7350+
GalleryName = $galleryName
7351+
Name = $definitionName
7352+
Publisher = $skuDetails.Publisher
7353+
Offer = $skuDetails.Offer
7354+
Sku = $skuDetails.Sku
7355+
Location = $location
7356+
OSState = $osState
7357+
OsType = $osType
7358+
HyperVGeneration = $hyperVGeneration
7359+
ErrorAction = 'Stop'
7360+
}
7361+
7362+
New-AzGalleryImageDefinition @paramNewAzImageDef;
7363+
7364+
# Setup Image Version
7365+
$imageVersionName = "1.0.0";
7366+
$paramNewAzImageVer = @{
7367+
ResourceGroupName = $resourceGroup
7368+
GalleryName = $galleryName
7369+
GalleryImageDefinitionName = $definitionName
7370+
Name = $imageVersionName
7371+
Location = $location
7372+
SourceImageId = $vm.Id
7373+
ErrorAction = 'Stop'
7374+
StorageAccountType = $storageAccountSku
7375+
AsJob = $true
7376+
}
7377+
New-AzGalleryImageVersion @paramNewAzImageVer | Out-Null;
7378+
7379+
$imageDefinition = Get-AzGalleryImageDefinition -ResourceGroupName $rgname -GalleryName $galleryName -Name $definitionName;
7380+
7381+
# Check image version status
7382+
# Looping to wait for the provisioningState to go to Succeeded never ends despite
7383+
# the status changing in portal. Image Definition is never seen by this PS script
7384+
# despite it being there, so making this a manual test.
7385+
Start-Sleep -Seconds 1000 ;
7386+
7387+
# Vm
7388+
$vmSize = "Standard_D2s_v5";
7389+
# Network pieces
7390+
$subnetConfig = New-AzVirtualNetworkSubnetConfig `
7391+
-Name $subnetname `
7392+
-AddressPrefix $subnetAddress;
7393+
$vnet = New-AzVirtualNetwork `
7394+
-ResourceGroupName $rgName -Location $location -Name $vnetname -AddressPrefix $vnetAddress `
7395+
-Subnet $subnetConfig;
7396+
$pip = New-AzPublicIpAddress `
7397+
-ResourceGroupName $rgName `
7398+
-Location $location `
7399+
-Name $pubipname `
7400+
-AllocationMethod Static `
7401+
-IdleTimeoutInMinutes 4;
7402+
$nsgRuleRDP = New-AzNetworkSecurityRuleConfig `
7403+
-Name $nsgrulename `
7404+
-Protocol Tcp `
7405+
-Direction Inbound `
7406+
-Priority 1000 `
7407+
-SourceAddressPrefix * `
7408+
-SourcePortRange * `
7409+
-DestinationAddressPrefix * `
7410+
-DestinationPortRange 3389 `
7411+
-Access Deny;
7412+
$nsg = New-AzNetworkSecurityGroup `
7413+
-ResourceGroupName $rgName `
7414+
-Location $location `
7415+
-Name $NSGName `
7416+
-SecurityRules $nsgRuleRDP;
7417+
$nic = New-AzNetworkInterface `
7418+
-Name $NICName `
7419+
-ResourceGroupName $rgName `
7420+
-Location $location `
7421+
-SubnetId $vnet.Subnets[0].Id `
7422+
-PublicIpAddressId $pip.Id `
7423+
-NetworkSecurityGroupId $nsg.Id;
7424+
$vm=New-AzVMConfig-vmName $vmname2-vmSize $vmSize | `
7425+
Set-AzVMSourceImage -Id $imageDefinition.Id | `
7426+
Add-AzVMNetworkInterface -Id $nic.Id;
7427+
7428+
New-AzVM `
7429+
-ResourceGroupName $rgName `
7430+
-Location $location `
7431+
-VM $vm;
7432+
7433+
$vm = Get-AzVM -ResourceGroupName $rgname -Name $vmname2;
7434+
7435+
# Validate
7436+
Assert-Null $vm.SecurityProfile;
7437+
}
7438+
finally
7439+
{
7440+
# Cleanup
7441+
Clean-ResourceGroup $rgname;
7442+
}
7443+
}

src/Compute/Compute/ChangeLog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
* Fixed `New-AzVmss` to correctly work when using `-EdgeZone` by creating the Load Balancer in the correct edge zone.
2626
* Removed references to image aliases in `New-AzVM` and `New-AzVmss` to images that were removed.
2727
* Az.Compute is updated to use the 2023-09-01 ComputeRP REST API calls.
28+
* Fixed `New-AzVM` when a source image is specified to avoid an error on the `Version` value.
2829

2930
## Version 7.1.0
3031
* Added new parameter `-ElasticSanResourceId` to `New-AzSnapshotConfig` cmdlet.

src/Compute/Compute/VirtualMachine/Operation/NewAzureVMCommand.cs

Lines changed: 76 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
using System.Security.AccessControl;
5555
using System.Security.Principal;
5656
using Microsoft.Azure.Commands.Common.Strategies.Compute;
57+
using System.Security.Policy;
58+
using System.Text.RegularExpressions;
5759

5860
namespace Microsoft.Azure.Commands.Compute
5961
{
@@ -936,14 +938,12 @@ public void DefaultExecuteCmdlet()
936938
&& this.VM.StorageProfile?.ImageReference?.SharedGalleryImageId == null) //had to add this
937939
{
938940
defaultTrustedLaunchAndUefi();
939-
940941
setTrustedLaunchImage();
941942
}
942-
943943
// Disk attached scenario for TL defaulting
944944
// Determines if the disk has SecurityType enabled.
945945
// If so, turns on TrustedLaunch for this VM.
946-
if (this.VM.SecurityProfile?.SecurityType == null
946+
else if (this.VM.SecurityProfile?.SecurityType == null
947947
&& this.VM.StorageProfile?.OsDisk?.ManagedDisk?.Id != null)
948948
{
949949
var mDiskId = this.VM.StorageProfile?.OsDisk?.ManagedDisk.Id.ToString();
@@ -957,48 +957,65 @@ public void DefaultExecuteCmdlet()
957957
defaultTrustedLaunchAndUefi();
958958
}
959959
}
960-
961-
// Guest Attestation extension defaulting scenario check.
962-
// And SecureBootEnabled and VtpmEnabled defaulting scenario.
963-
if (this.VM.SecurityProfile?.SecurityType != null
964-
&& (this.VM.SecurityProfile?.SecurityType?.ToLower() == ConstantValues.TrustedLaunchSecurityType
965-
|| this.VM.SecurityProfile?.SecurityType?.ToLower() == ConstantValues.ConfidentialVMSecurityType))
966-
{
967-
if (this.VM?.SecurityProfile?.UefiSettings != null)
968-
{
969-
this.VM.SecurityProfile.UefiSettings.SecureBootEnabled = this.VM.SecurityProfile.UefiSettings.SecureBootEnabled ?? true;
970-
this.VM.SecurityProfile.UefiSettings.VTpmEnabled = this.VM.SecurityProfile.UefiSettings.VTpmEnabled ?? true;
971-
}
972-
else
973-
{
974-
this.VM.SecurityProfile.UefiSettings = new UefiSettings(true, true);
975-
}
976-
}
977-
978-
979960
// ImageReference provided, TL defaulting occurs if image is Gen2.
980-
if (this.VM.SecurityProfile?.SecurityType == null
961+
// This will handle when the Id is provided in a URI format and
962+
// when the image segments are provided individually.
963+
else if (this.VM.SecurityProfile?.SecurityType == null
981964
&& this.VM.StorageProfile?.ImageReference != null)
982965
{
983-
if (this.VM.StorageProfile?.ImageReference?.Id != null)//This code should never happen apparently
966+
if (this.VM.StorageProfile?.ImageReference?.Id != null)
984967
{
985968
string imageRefString = this.VM.StorageProfile.ImageReference.Id.ToString();
986969

987-
var parts = imageRefString.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
988-
989-
string imagePublisher = parts[Array.IndexOf(parts, "Publishers") + 1];
990-
string imageOffer = parts[Array.IndexOf(parts, "Offers") + 1];
991-
string imageSku = parts[Array.IndexOf(parts, "Skus") + 1];
992-
string imageVersion = parts[Array.IndexOf(parts, "Versions") + 1];
993-
//location is required when config object provided.
994-
var imgResponse = ComputeClient.ComputeManagementClient.VirtualMachineImages.GetWithHttpMessagesAsync(
995-
this.Location.Canonicalize(),
996-
imagePublisher,
997-
imageOffer,
998-
imageSku,
999-
version: imageVersion).GetAwaiter().GetResult();
1000-
1001-
setHyperVGenForImageCheckAndTLDefaulting(imgResponse);
970+
string galleryImgIdPattern = @"/subscriptions/(?<subscriptionId>[^/]+)/resourceGroups/(?<resourceGroup>[^/]+)/providers/Microsoft.Compute/galleries/(?<gallery>[^/]+)/images/(?<image>[^/]+)";
971+
string managedImageIdPattern = @"/subscriptions/(?<subscriptionId>[^/]+)/resourceGroups/(?<resourceGroup>[^/]+)/providers/Microsoft.Compute/images/(?<image>[^/]+)";
972+
string defaultExistingImagePattern = @"/Subscriptions/(?<subscriptionId>[^/]+)/Providers/Microsoft.Compute/Locations/(?<location>[^/]+)/Publishers/(?<publisher>[^/]+)/ArtifactTypes/VMImage/Offers/(?<offer>[^/]+)/Skus/(?<sku>[^/]+)/Versions/(?<version>[^/]+)";
973+
974+
//Gallery Id
975+
Regex galleryRgx = new Regex(galleryImgIdPattern, RegexOptions.IgnoreCase);
976+
Match galleryMatch = galleryRgx.Match(imageRefString);
977+
// Managed Image Id
978+
Regex managedImageRgx = new Regex(managedImageIdPattern, RegexOptions.IgnoreCase);
979+
Match managedImageMatch = managedImageRgx.Match(imageRefString);
980+
// Default Image Id
981+
Regex defaultImageRgx = new Regex(defaultExistingImagePattern, RegexOptions.IgnoreCase);
982+
Match defaultImageMatch = defaultImageRgx.Match(imageRefString);
983+
984+
if (defaultImageMatch.Success)
985+
{
986+
var parts = imageRefString.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
987+
// It's a default existing image
988+
string imagePublisher = parts[Array.IndexOf(parts, "Publishers") + 1];
989+
string imageOffer = parts[Array.IndexOf(parts, "Offers") + 1];
990+
string imageSku = parts[Array.IndexOf(parts, "Skus") + 1];
991+
string imageVersion = parts[Array.IndexOf(parts, "Versions") + 1];
992+
//location is required when config object provided.
993+
var imgResponse = ComputeClient.ComputeManagementClient.VirtualMachineImages.GetWithHttpMessagesAsync(
994+
this.Location.Canonicalize(),
995+
imagePublisher,
996+
imageOffer,
997+
imageSku,
998+
version: imageVersion).GetAwaiter().GetResult();
999+
1000+
setHyperVGenForImageCheckAndTLDefaulting(imgResponse);
1001+
}
1002+
// This scenario might have additional logic added later, so making its own if check fo now.
1003+
else if (galleryMatch.Success || managedImageMatch.Success)
1004+
{
1005+
// do nothing, send message to use TL.
1006+
if (this.AsJobPresent() == false) // to avoid a failure when it is a job. Seems to fail when it is a job.
1007+
{
1008+
WriteInformation(HelpMessages.TrustedLaunchUpgradeMessage, new string[] { "PSHOST" });
1009+
}
1010+
}
1011+
else
1012+
{
1013+
// Default behavior is to remind customer to use TrustedLaunch.
1014+
if (this.AsJobPresent() == false) // to avoid a failure when it is a job. Seems to fail when it is a job.
1015+
{
1016+
WriteInformation(HelpMessages.TrustedLaunchUpgradeMessage, new string[] { "PSHOST" });
1017+
}
1018+
}
10021019
}
10031020
else
10041021
{
@@ -1007,17 +1024,31 @@ public void DefaultExecuteCmdlet()
10071024
setHyperVGenForImageCheckAndTLDefaulting(specificImageRespone);
10081025
}
10091026
}
1010-
1011-
if (this.VM.SecurityProfile?.SecurityType == ConstantValues.TrustedLaunchSecurityType
1012-
&& this.VM.StorageProfile?.ImageReference == null
1013-
&& this.VM.StorageProfile?.OsDisk?.ManagedDisk?.Id == null //had to add this
1014-
&& this.VM.StorageProfile?.ImageReference?.SharedGalleryImageId == null)
1027+
else if (this.VM.SecurityProfile?.SecurityType == ConstantValues.TrustedLaunchSecurityType
1028+
&& this.VM.StorageProfile?.ImageReference == null
1029+
&& this.VM.StorageProfile?.OsDisk?.ManagedDisk?.Id == null //had to add this
1030+
&& this.VM.StorageProfile?.ImageReference?.SharedGalleryImageId == null)
10151031
{
10161032
defaultTrustedLaunchAndUefi();
1017-
10181033
setTrustedLaunchImage();
10191034
}
10201035

1036+
// SecureBootEnabled and VtpmEnabled defaulting scenario.
1037+
if (this.VM.SecurityProfile?.SecurityType != null
1038+
&& (this.VM.SecurityProfile?.SecurityType?.ToLower() == ConstantValues.TrustedLaunchSecurityType
1039+
|| this.VM.SecurityProfile?.SecurityType?.ToLower() == ConstantValues.ConfidentialVMSecurityType))
1040+
{
1041+
if (this.VM?.SecurityProfile?.UefiSettings != null)
1042+
{
1043+
this.VM.SecurityProfile.UefiSettings.SecureBootEnabled = this.VM.SecurityProfile.UefiSettings.SecureBootEnabled ?? true;
1044+
this.VM.SecurityProfile.UefiSettings.VTpmEnabled = this.VM.SecurityProfile.UefiSettings.VTpmEnabled ?? true;
1045+
}
1046+
else
1047+
{
1048+
this.VM.SecurityProfile.UefiSettings = new UefiSettings(true, true);
1049+
}
1050+
}
1051+
10211052
// Standard security type removing value since API does not support it yet.
10221053
if (this.VM.SecurityProfile?.SecurityType != null
10231054
&& this.VM.SecurityProfile?.SecurityType?.ToString().ToLower() == ConstantValues.StandardSecurityType)

0 commit comments

Comments
 (0)