From bc7e0c5cd66f72c8f9df3364b139cac41982faa4 Mon Sep 17 00:00:00 2001 From: Nirjan Chapagain Date: Fri, 13 Mar 2026 09:05:03 -0700 Subject: [PATCH 1/3] fixing layout error --- src/VirtualClient/VirtualClient.Main/ExecuteProfileCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VirtualClient/VirtualClient.Main/ExecuteProfileCommand.cs b/src/VirtualClient/VirtualClient.Main/ExecuteProfileCommand.cs index bf5c5f655b..7ee7ec731d 100644 --- a/src/VirtualClient/VirtualClient.Main/ExecuteProfileCommand.cs +++ b/src/VirtualClient/VirtualClient.Main/ExecuteProfileCommand.cs @@ -669,7 +669,7 @@ protected void SetHostMetadataTelemetryProperties(IEnumerable profiles, new Dictionary { { "exitWait", this.ExitWait }, - { "layout", this.Layout.ToString() }, + { "layout", this.Layout?.ToString() }, { "logToFile", this.LogToFile }, { "iterations", this.Iterations?.ProfileIterations }, { "profiles", string.Join(",", profiles.Select(p => Path.GetFileName(p))) }, From 624436697d48fb0cccbdd2c55fa78e8d5a1b8807 Mon Sep 17 00:00:00 2001 From: Nirjan Chapagain Date: Mon, 16 Mar 2026 10:13:24 -0700 Subject: [PATCH 2/3] Adding UT --- .../VirtualClient.Main/BootstrapCommand.cs | 6 --- .../VirtualClient.Main/CommandLineParser.cs | 5 -- .../BootstrapCommandTests.cs | 46 ++++++++++++++++--- .../ExecuteProfileCommandTests.cs | 13 ++++++ 4 files changed, 52 insertions(+), 18 deletions(-) diff --git a/src/VirtualClient/VirtualClient.Main/BootstrapCommand.cs b/src/VirtualClient/VirtualClient.Main/BootstrapCommand.cs index d86560b7c9..d12eed8a96 100644 --- a/src/VirtualClient/VirtualClient.Main/BootstrapCommand.cs +++ b/src/VirtualClient/VirtualClient.Main/BootstrapCommand.cs @@ -145,12 +145,6 @@ protected void Validate() "The Azure tenant ID must be provided on the command line (--tenant-id) to install a certificate."); } } - - if (!string.IsNullOrWhiteSpace(this.PackageName) && this.PackageStore == null) - { - throw new ArgumentException( - "A package store must be provided on the command line (--package-store) when installing packages."); - } } } } diff --git a/src/VirtualClient/VirtualClient.Main/CommandLineParser.cs b/src/VirtualClient/VirtualClient.Main/CommandLineParser.cs index b21f54fae9..4ac552f139 100644 --- a/src/VirtualClient/VirtualClient.Main/CommandLineParser.cs +++ b/src/VirtualClient/VirtualClient.Main/CommandLineParser.cs @@ -395,11 +395,6 @@ private static Command CreateBootstrapSubcommand(string[] args, CancellationToke "Use --package to install a package or --cert-name to install a certificate."); } - if (package != null && packageStore == null) - { - throw new ArgumentException("The package store URI must be provided (--package-store) when installing a package."); - } - // Certificate installation requires both --cert-name and --key-vault. if (certName != null) { diff --git a/src/VirtualClient/VirtualClient.UnitTests/BootstrapCommandTests.cs b/src/VirtualClient/VirtualClient.UnitTests/BootstrapCommandTests.cs index d00dd247c3..fba90ad60f 100644 --- a/src/VirtualClient/VirtualClient.UnitTests/BootstrapCommandTests.cs +++ b/src/VirtualClient/VirtualClient.UnitTests/BootstrapCommandTests.cs @@ -55,15 +55,12 @@ public void BootstrapCommandValidatesRequiredParametersForCertificateInstallatio } [Test] - public void BootstrapCommandValidatesRequiredParametersForPackageDownloads() + public void BootstrapCommandDoesNotRequirePackageStoredParameterForPackageDownloads() { + // It will use default vc package store. var command = new TestBootstrapCommand(); command.PackageName = "any-package.zip"; - Exception error = Assert.Throws(() => command.Validate()); - - Assert.AreEqual( - "A package store must be provided on the command line (--package-store) when installing packages.", - error.Message); + Assert.DoesNotThrow(() => command.Validate()); } [Test] @@ -116,7 +113,7 @@ public void BootstrapCommandExecutesTheExpectedProfileToBootstrapPackages() var command = new TestBootstrapCommand { PackageName = "any-package.zip", - PackageStore = new DependencyBlobStore(DependencyStore.Packages, "https://any.storage"), + // PackageStore is no longer required Name = "any-package" }; @@ -148,6 +145,41 @@ public void BootstrapCommandProvidesTheExpectedParametersToTheProfileToBootstrap } } + [Test] + public void BootstrapCommandDoesNotRequirePackageStoreForPackageInstallation() + { + // Arrange - Create command with only package name, no package store + var command = new TestBootstrapCommand + { + PackageName = "any-package.zip" + }; + + // Act & Assert - Should not throw ArgumentException + Assert.DoesNotThrow(() => command.Validate()); + } + + [Test] + public void BootstrapCommandExecutesTheExpectedProfileToBootstrapPackagesWithoutPackageStore() + { + using (CancellationTokenSource tokenSource = new CancellationTokenSource()) + { + // Arrange - No PackageStore provided + var command = new TestBootstrapCommand + { + PackageName = "any-package.zip", + Name = "any-package" + }; + + // Act + command.Initialize(Array.Empty(), this.mockFixture.PlatformSpecifics); + + // Assert - Profile should still be set up correctly + Assert.IsNotEmpty(command.Profiles); + Assert.AreEqual(1, command.Profiles.Count()); + Assert.AreEqual(1, command.Profiles.Count(p => p.ProfileName == "BOOTSTRAP-PACKAGE.json")); + } + } + internal class TestBootstrapCommand : BootstrapCommand { /// diff --git a/src/VirtualClient/VirtualClient.UnitTests/ExecuteProfileCommandTests.cs b/src/VirtualClient/VirtualClient.UnitTests/ExecuteProfileCommandTests.cs index debbd18436..0d513d4002 100644 --- a/src/VirtualClient/VirtualClient.UnitTests/ExecuteProfileCommandTests.cs +++ b/src/VirtualClient/VirtualClient.UnitTests/ExecuteProfileCommandTests.cs @@ -538,5 +538,18 @@ private static Tuple GetAccessTokenPair() return new Tuple(decodedOriginalBytes, decodedObscuredBytes); } + + [Test] + public void SetHostMetadataTelemetryPropertiesDoesNotThrowWhenLayoutIsNull() + { + // Layout is not mandatory for setting host metadata properties. + this.command.Layout = null; + + string profile = "TEST-WORKLOAD-PROFILE.json"; + List profiles = new List { this.mockFixture.GetProfilesPath(profile) }; + + // Act & Assert - Should not throw expection + Assert.DoesNotThrow(() => this.command.SetHostMetadataTelemetryProperties(profiles, this.mockFixture.Dependencies)); + } } } From 208a751e81aa92e48eebbbeb0ca676455b81f3b4 Mon Sep 17 00:00:00 2001 From: Nirjan Chapagain Date: Mon, 16 Mar 2026 13:15:06 -0700 Subject: [PATCH 3/3] Keeping this PR short. I've removed the following feature: Remove --package-store requirement in bootstrap subcommand. --- .../VirtualClient.Main/BootstrapCommand.cs | 12 +++-- .../VirtualClient.Main/CommandLineParser.cs | 7 ++- .../BootstrapCommandTests.cs | 46 +++---------------- 3 files changed, 22 insertions(+), 43 deletions(-) diff --git a/src/VirtualClient/VirtualClient.Main/BootstrapCommand.cs b/src/VirtualClient/VirtualClient.Main/BootstrapCommand.cs index d12eed8a96..fbedca5ef4 100644 --- a/src/VirtualClient/VirtualClient.Main/BootstrapCommand.cs +++ b/src/VirtualClient/VirtualClient.Main/BootstrapCommand.cs @@ -136,15 +136,21 @@ protected void Validate() // // e.g. // --key-vault="https://any.vault.azure.net?cid=8cdebecc...&tid=42005d4d...&crti=ANY&crts=any.corp.azure.com" - if (string.IsNullOrWhiteSpace(this.AccessToken) + if (string.IsNullOrWhiteSpace(this.AccessToken) && string.IsNullOrWhiteSpace(this.TokenFilePath) - && string.IsNullOrWhiteSpace(this.TenantId) + && string.IsNullOrWhiteSpace(this.TenantId) && (this.KeyVaultStore as DependencyKeyVaultStore)?.Credentials == null) { throw new ArgumentException( "The Azure tenant ID must be provided on the command line (--tenant-id) to install a certificate."); } } + + if (!string.IsNullOrWhiteSpace(this.PackageName) && this.PackageStore == null) + { + throw new ArgumentException( + "A package store must be provided on the command line (--package-store) when installing packages."); + } } } -} +} \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Main/CommandLineParser.cs b/src/VirtualClient/VirtualClient.Main/CommandLineParser.cs index 4ac552f139..6daf8791fe 100644 --- a/src/VirtualClient/VirtualClient.Main/CommandLineParser.cs +++ b/src/VirtualClient/VirtualClient.Main/CommandLineParser.cs @@ -395,6 +395,11 @@ private static Command CreateBootstrapSubcommand(string[] args, CancellationToke "Use --package to install a package or --cert-name to install a certificate."); } + if (package != null && packageStore == null) + { + throw new ArgumentException("The package store URI must be provided (--package-store) when installing a package."); + } + // Certificate installation requires both --cert-name and --key-vault. if (certName != null) { @@ -637,4 +642,4 @@ private static string[] PreprocessArguments(params string[] args) return preprocessedArgs; } } -} +} \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.UnitTests/BootstrapCommandTests.cs b/src/VirtualClient/VirtualClient.UnitTests/BootstrapCommandTests.cs index fba90ad60f..d00dd247c3 100644 --- a/src/VirtualClient/VirtualClient.UnitTests/BootstrapCommandTests.cs +++ b/src/VirtualClient/VirtualClient.UnitTests/BootstrapCommandTests.cs @@ -55,12 +55,15 @@ public void BootstrapCommandValidatesRequiredParametersForCertificateInstallatio } [Test] - public void BootstrapCommandDoesNotRequirePackageStoredParameterForPackageDownloads() + public void BootstrapCommandValidatesRequiredParametersForPackageDownloads() { - // It will use default vc package store. var command = new TestBootstrapCommand(); command.PackageName = "any-package.zip"; - Assert.DoesNotThrow(() => command.Validate()); + Exception error = Assert.Throws(() => command.Validate()); + + Assert.AreEqual( + "A package store must be provided on the command line (--package-store) when installing packages.", + error.Message); } [Test] @@ -113,7 +116,7 @@ public void BootstrapCommandExecutesTheExpectedProfileToBootstrapPackages() var command = new TestBootstrapCommand { PackageName = "any-package.zip", - // PackageStore is no longer required + PackageStore = new DependencyBlobStore(DependencyStore.Packages, "https://any.storage"), Name = "any-package" }; @@ -145,41 +148,6 @@ public void BootstrapCommandProvidesTheExpectedParametersToTheProfileToBootstrap } } - [Test] - public void BootstrapCommandDoesNotRequirePackageStoreForPackageInstallation() - { - // Arrange - Create command with only package name, no package store - var command = new TestBootstrapCommand - { - PackageName = "any-package.zip" - }; - - // Act & Assert - Should not throw ArgumentException - Assert.DoesNotThrow(() => command.Validate()); - } - - [Test] - public void BootstrapCommandExecutesTheExpectedProfileToBootstrapPackagesWithoutPackageStore() - { - using (CancellationTokenSource tokenSource = new CancellationTokenSource()) - { - // Arrange - No PackageStore provided - var command = new TestBootstrapCommand - { - PackageName = "any-package.zip", - Name = "any-package" - }; - - // Act - command.Initialize(Array.Empty(), this.mockFixture.PlatformSpecifics); - - // Assert - Profile should still be set up correctly - Assert.IsNotEmpty(command.Profiles); - Assert.AreEqual(1, command.Profiles.Count()); - Assert.AreEqual(1, command.Profiles.Count(p => p.ProfileName == "BOOTSTRAP-PACKAGE.json")); - } - } - internal class TestBootstrapCommand : BootstrapCommand { ///