From 132413389f626cd0b007d2af2317f69e738c4a2e Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 25 Feb 2025 13:09:55 -0500 Subject: [PATCH 1/4] Get metadata properties when finding a PSResource from a ContainerRegistry --- src/code/PSResourceInfo.cs | 53 ++++++++++++++----- ...SResourceContainerRegistryServer.Tests.ps1 | 20 +++++++ 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/code/PSResourceInfo.cs b/src/code/PSResourceInfo.cs index 547828393..205eac4b4 100644 --- a/src/code/PSResourceInfo.cs +++ b/src/code/PSResourceInfo.cs @@ -852,9 +852,9 @@ public static bool TryConvertFromContainerRegistryJson( pkgVersion = ParseHttpVersion(versionValue, out string prereleaseLabel); metadata["Version"] = pkgVersion; - if (rootDom.TryGetProperty("PrivateData", out JsonElement privateDataElement) && privateDataElement.TryGetProperty("PSData", out JsonElement psDataElement)) + if (rootDom.TryGetProperty("PrivateData", out JsonElement versionPrivateDataElement) && versionPrivateDataElement.TryGetProperty("PSData", out JsonElement versionPSDataElement)) { - if (psDataElement.TryGetProperty("Prerelease", out JsonElement pkgPrereleaseLabelElement) && !String.IsNullOrEmpty(pkgPrereleaseLabelElement.ToString().Trim())) + if (versionPSDataElement.TryGetProperty("Prerelease", out JsonElement pkgPrereleaseLabelElement) && !String.IsNullOrEmpty(pkgPrereleaseLabelElement.ToString().Trim())) { prereleaseLabel = pkgPrereleaseLabelElement.ToString().Trim(); versionValue += $"-{prereleaseLabel}"; @@ -884,19 +884,19 @@ public static bool TryConvertFromContainerRegistryJson( metadata["NormalizedVersion"] = parsedNormalizedVersion.ToNormalizedString(); // License Url - if (rootDom.TryGetProperty("LicenseUrl", out JsonElement licenseUrlElement) || rootDom.TryGetProperty("licenseUrl", out licenseUrlElement)) + if (rootDom.TryGetProperty("LicenseUrl", out JsonElement licenseUrlElement) || rootDom.TryGetProperty("licenseUrl", out licenseUrlElement) || rootDom.TryGetProperty("LicenseUri", out licenseUrlElement)) { metadata["LicenseUrl"] = ParseHttpUrl(licenseUrlElement.ToString()) as Uri; } // Project Url - if (rootDom.TryGetProperty("ProjectUrl", out JsonElement projectUrlElement) || rootDom.TryGetProperty("projectUrl", out projectUrlElement)) + if (rootDom.TryGetProperty("ProjectUrl", out JsonElement projectUrlElement) || rootDom.TryGetProperty("projectUrl", out projectUrlElement) || rootDom.TryGetProperty("ProjectUri", out projectUrlElement)) { metadata["ProjectUrl"] = ParseHttpUrl(projectUrlElement.ToString()) as Uri; } // Icon Url - if (rootDom.TryGetProperty("IconUrl", out JsonElement iconUrlElement) || rootDom.TryGetProperty("iconUrl", out iconUrlElement)) + if (rootDom.TryGetProperty("IconUrl", out JsonElement iconUrlElement) || rootDom.TryGetProperty("iconUrl", out iconUrlElement) || rootDom.TryGetProperty("IconUri", out iconUrlElement)) { metadata["IconUrl"] = ParseHttpUrl(iconUrlElement.ToString()) as Uri; } @@ -938,14 +938,19 @@ public static bool TryConvertFromContainerRegistryJson( } // Author - if (rootDom.TryGetProperty("Authors", out JsonElement authorsElement) || rootDom.TryGetProperty("authors", out authorsElement)) + if (rootDom.TryGetProperty("Authors", out JsonElement authorsElement) || rootDom.TryGetProperty("authors", out authorsElement) || rootDom.TryGetProperty("Author", out authorsElement)) { metadata["Authors"] = authorsElement.ToString(); + } - // CompanyName - // CompanyName is not provided in v3 pkg metadata response, so we've just set it to the author, - // which is often the company - metadata["CompanyName"] = authorsElement.ToString(); + if (rootDom.TryGetProperty("CompanyName", out JsonElement companyNameElement)) + { + metadata["CompanyName"] = companyNameElement.ToString(); + } + else + { + // if CompanyName property is not provided set it to the Author value which is often the same. + metadata["CompanyName"] = metadata["Authors"]; } // Copyright @@ -978,15 +983,39 @@ public static bool TryConvertFromContainerRegistryJson( { metadata["Dependencies"] = ParseContainerRegistryDependencies(moduleListDepsElement, out errorMsg).ToArray(); } - else if (rootDom.TryGetProperty("PrivateData", out JsonElement privateDataElement) && privateDataElement.TryGetProperty("PSData", out JsonElement psDataElement)) + else if (rootDom.TryGetProperty("PrivateData", out JsonElement depsPrivateDataElement) && depsPrivateDataElement.TryGetProperty("PSData", out JsonElement depsPSDataElement)) { - if (psDataElement.TryGetProperty("ModuleList", out JsonElement privateDataModuleListDepsElement)) + if (depsPSDataElement.TryGetProperty("ModuleList", out JsonElement privateDataModuleListDepsElement)) { metadata["Dependencies"] = ParseContainerRegistryDependencies(privateDataModuleListDepsElement, out errorMsg).ToArray(); } } } + if (rootDom.TryGetProperty("PrivateData", out JsonElement privateDataElement) && privateDataElement.TryGetProperty("PSData", out JsonElement psDataElement)) + { + // some properties that may be in PrivateData.PSData: LicenseUri, ProjectUri, IconUri, ReleaseNotes + if (!(metadata.ContainsKey("LicenseUrl") || metadata.ContainsKey("licenseUrl")) && psDataElement.TryGetProperty("LicenseUri", out JsonElement psDataLicenseUriElement)) + { + metadata["LicenseUrl"] = ParseHttpUrl(psDataLicenseUriElement.ToString()) as Uri; + } + + if (!(metadata.ContainsKey("ProjectUrl") || metadata.ContainsKey("ProjectUrl")) && psDataElement.TryGetProperty("ProjectUri", out JsonElement psDataProjectUriElement)) + { + metadata["ProjectUrl"] = ParseHttpUrl(psDataProjectUriElement.ToString()) as Uri; + } + + if (!(metadata.ContainsKey("IconUrl") || metadata.ContainsKey("IconUrl")) && psDataElement.TryGetProperty("IconUri", out JsonElement psDataIconUriElement)) + { + metadata["IconUrl"] = ParseHttpUrl(psDataIconUriElement.ToString()) as Uri; + } + + if (!metadata.ContainsKey("ReleaseNotes") && psDataElement.TryGetProperty("ReleaseNotes", out JsonElement psDataReleaseNotesElement)) + { + metadata["ReleaseNotes"] = psDataReleaseNotesElement.ToString(); + } + } + var additionalMetadataHashtable = new Dictionary { { "NormalizedVersion", metadata["NormalizedVersion"].ToString() } diff --git a/test/FindPSResourceTests/FindPSResourceContainerRegistryServer.Tests.ps1 b/test/FindPSResourceTests/FindPSResourceContainerRegistryServer.Tests.ps1 index 6bdeeecab..601de8769 100644 --- a/test/FindPSResourceTests/FindPSResourceContainerRegistryServer.Tests.ps1 +++ b/test/FindPSResourceTests/FindPSResourceContainerRegistryServer.Tests.ps1 @@ -232,6 +232,26 @@ Describe 'Test HTTP Find-PSResource for ACR Server Protocol' -tags 'CI' { $res.Dependencies.Length | Should -Be 1 $res.Dependencies[0].Name | Should -Be "Az.Accounts" } + + It "Should find resource and its associated author, licenseUri, projectUri, releaseNotes, etc properties" { + $res = Find-PSResource -Name "Az.Storage" -Version "8.0.0" -Repository $ACRRepoName + $res.Author | Should -Be "Microsoft Corporation" + $res.CompanyName | Should -Be "Microsoft Corporation" + $res.LicenseUri | Should -Be "https://aka.ms/azps-license" + $res.ProjectUri | Should -Be "https://github.com/Azure/azure-powershell" + $res.ReleaseNotes.Length | Should -Not -Be 0 + } + + It "Install script with companyname, copyright, and repository source location and validate" { + Install-PSResource -Name "Install-VSCode" -Version "1.4.2" -Repository $PSGalleryName -TrustRepository + + $res = Get-InstalledPSResource "Install-VSCode" -Version "1.4.2" + $res.Name | Should -Be "Install-VSCode" + $res.Version | Should -Be "1.4.2" + $res.CompanyName | Should -Be "Microsoft Corporation" + $res.Copyright | Should -Be "(c) Microsoft Corporation" + $res.RepositorySourceLocation | Should -Be $PSGalleryUri + } } Describe 'Test Find-PSResource for MAR Repository' -tags 'CI' { From 02ab696dd405fa242d0edeeaab3962144e0928ca Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 25 Feb 2025 13:21:54 -0500 Subject: [PATCH 2/4] Retrieve Tags property which can be in PrivateData.PSData but is not as common and add test for it --- src/code/PSResourceInfo.cs | 35 +++++++++++++++---- ...SResourceContainerRegistryServer.Tests.ps1 | 1 + 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/code/PSResourceInfo.cs b/src/code/PSResourceInfo.cs index 205eac4b4..cbab137e6 100644 --- a/src/code/PSResourceInfo.cs +++ b/src/code/PSResourceInfo.cs @@ -884,19 +884,19 @@ public static bool TryConvertFromContainerRegistryJson( metadata["NormalizedVersion"] = parsedNormalizedVersion.ToNormalizedString(); // License Url - if (rootDom.TryGetProperty("LicenseUrl", out JsonElement licenseUrlElement) || rootDom.TryGetProperty("licenseUrl", out licenseUrlElement) || rootDom.TryGetProperty("LicenseUri", out licenseUrlElement)) + if (rootDom.TryGetProperty("LicenseUrl", out JsonElement licenseUrlElement) || rootDom.TryGetProperty("licenseUrl", out licenseUrlElement)) { metadata["LicenseUrl"] = ParseHttpUrl(licenseUrlElement.ToString()) as Uri; } // Project Url - if (rootDom.TryGetProperty("ProjectUrl", out JsonElement projectUrlElement) || rootDom.TryGetProperty("projectUrl", out projectUrlElement) || rootDom.TryGetProperty("ProjectUri", out projectUrlElement)) + if (rootDom.TryGetProperty("ProjectUrl", out JsonElement projectUrlElement) || rootDom.TryGetProperty("projectUrl", out projectUrlElement)) { metadata["ProjectUrl"] = ParseHttpUrl(projectUrlElement.ToString()) as Uri; } // Icon Url - if (rootDom.TryGetProperty("IconUrl", out JsonElement iconUrlElement) || rootDom.TryGetProperty("iconUrl", out iconUrlElement) || rootDom.TryGetProperty("IconUri", out iconUrlElement)) + if (rootDom.TryGetProperty("IconUrl", out JsonElement iconUrlElement) || rootDom.TryGetProperty("iconUrl", out iconUrlElement)) { metadata["IconUrl"] = ParseHttpUrl(iconUrlElement.ToString()) as Uri; } @@ -995,17 +995,17 @@ public static bool TryConvertFromContainerRegistryJson( if (rootDom.TryGetProperty("PrivateData", out JsonElement privateDataElement) && privateDataElement.TryGetProperty("PSData", out JsonElement psDataElement)) { // some properties that may be in PrivateData.PSData: LicenseUri, ProjectUri, IconUri, ReleaseNotes - if (!(metadata.ContainsKey("LicenseUrl") || metadata.ContainsKey("licenseUrl")) && psDataElement.TryGetProperty("LicenseUri", out JsonElement psDataLicenseUriElement)) + if (!metadata.ContainsKey("LicenseUrl") && psDataElement.TryGetProperty("LicenseUri", out JsonElement psDataLicenseUriElement)) { metadata["LicenseUrl"] = ParseHttpUrl(psDataLicenseUriElement.ToString()) as Uri; } - if (!(metadata.ContainsKey("ProjectUrl") || metadata.ContainsKey("ProjectUrl")) && psDataElement.TryGetProperty("ProjectUri", out JsonElement psDataProjectUriElement)) + if (!metadata.ContainsKey("ProjectUrl") && psDataElement.TryGetProperty("ProjectUri", out JsonElement psDataProjectUriElement)) { metadata["ProjectUrl"] = ParseHttpUrl(psDataProjectUriElement.ToString()) as Uri; } - if (!(metadata.ContainsKey("IconUrl") || metadata.ContainsKey("IconUrl")) && psDataElement.TryGetProperty("IconUri", out JsonElement psDataIconUriElement)) + if (!metadata.ContainsKey("IconUrl") && psDataElement.TryGetProperty("IconUri", out JsonElement psDataIconUriElement)) { metadata["IconUrl"] = ParseHttpUrl(psDataIconUriElement.ToString()) as Uri; } @@ -1014,6 +1014,29 @@ public static bool TryConvertFromContainerRegistryJson( { metadata["ReleaseNotes"] = psDataReleaseNotesElement.ToString(); } + + if (!metadata.ContainsKey("Tags") && psDataElement.TryGetProperty("Tags", out JsonElement psDataTagsElement)) + { + string[] pkgTags = Utils.EmptyStrArray; + if (psDataTagsElement.ValueKind == JsonValueKind.Array) + { + var arrayLength = psDataTagsElement.GetArrayLength(); + List tags = new List(arrayLength); + foreach (var tag in psDataTagsElement.EnumerateArray()) + { + tags.Add(tag.ToString()); + } + + pkgTags = tags.ToArray(); + } + else if (psDataTagsElement.ValueKind == JsonValueKind.String) + { + string tagStr = psDataTagsElement.ToString(); + pkgTags = tagStr.Split(Utils.WhitespaceSeparator, StringSplitOptions.RemoveEmptyEntries); + } + + metadata["Tags"] = pkgTags; + } } var additionalMetadataHashtable = new Dictionary diff --git a/test/FindPSResourceTests/FindPSResourceContainerRegistryServer.Tests.ps1 b/test/FindPSResourceTests/FindPSResourceContainerRegistryServer.Tests.ps1 index 601de8769..89da28899 100644 --- a/test/FindPSResourceTests/FindPSResourceContainerRegistryServer.Tests.ps1 +++ b/test/FindPSResourceTests/FindPSResourceContainerRegistryServer.Tests.ps1 @@ -240,6 +240,7 @@ Describe 'Test HTTP Find-PSResource for ACR Server Protocol' -tags 'CI' { $res.LicenseUri | Should -Be "https://aka.ms/azps-license" $res.ProjectUri | Should -Be "https://github.com/Azure/azure-powershell" $res.ReleaseNotes.Length | Should -Not -Be 0 + $res.Tags.Length | Should -Be 5 } It "Install script with companyname, copyright, and repository source location and validate" { From 1c4e7d45fef32866d434f43b8d04c1eb8b72d88e Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 25 Feb 2025 13:23:21 -0500 Subject: [PATCH 3/4] clean up code --- .../FindPSResourceContainerRegistryServer.Tests.ps1 | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/test/FindPSResourceTests/FindPSResourceContainerRegistryServer.Tests.ps1 b/test/FindPSResourceTests/FindPSResourceContainerRegistryServer.Tests.ps1 index 89da28899..c9fda5637 100644 --- a/test/FindPSResourceTests/FindPSResourceContainerRegistryServer.Tests.ps1 +++ b/test/FindPSResourceTests/FindPSResourceContainerRegistryServer.Tests.ps1 @@ -242,17 +242,6 @@ Describe 'Test HTTP Find-PSResource for ACR Server Protocol' -tags 'CI' { $res.ReleaseNotes.Length | Should -Not -Be 0 $res.Tags.Length | Should -Be 5 } - - It "Install script with companyname, copyright, and repository source location and validate" { - Install-PSResource -Name "Install-VSCode" -Version "1.4.2" -Repository $PSGalleryName -TrustRepository - - $res = Get-InstalledPSResource "Install-VSCode" -Version "1.4.2" - $res.Name | Should -Be "Install-VSCode" - $res.Version | Should -Be "1.4.2" - $res.CompanyName | Should -Be "Microsoft Corporation" - $res.Copyright | Should -Be "(c) Microsoft Corporation" - $res.RepositorySourceLocation | Should -Be $PSGalleryUri - } } Describe 'Test Find-PSResource for MAR Repository' -tags 'CI' { From ecce602b03eb675bb834fcc013c40239f32393a4 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 25 Feb 2025 15:47:36 -0500 Subject: [PATCH 4/4] check privateData JsonElement kind, if Object check for its properties, if string (as in the case of script PSResourceInfo) do not check as will throw error --- src/code/PSResourceInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code/PSResourceInfo.cs b/src/code/PSResourceInfo.cs index cbab137e6..dd840b62d 100644 --- a/src/code/PSResourceInfo.cs +++ b/src/code/PSResourceInfo.cs @@ -992,7 +992,7 @@ public static bool TryConvertFromContainerRegistryJson( } } - if (rootDom.TryGetProperty("PrivateData", out JsonElement privateDataElement) && privateDataElement.TryGetProperty("PSData", out JsonElement psDataElement)) + if (rootDom.TryGetProperty("PrivateData", out JsonElement privateDataElement) && privateDataElement.ValueKind == JsonValueKind.Object && privateDataElement.TryGetProperty("PSData", out JsonElement psDataElement)) { // some properties that may be in PrivateData.PSData: LicenseUri, ProjectUri, IconUri, ReleaseNotes if (!metadata.ContainsKey("LicenseUrl") && psDataElement.TryGetProperty("LicenseUri", out JsonElement psDataLicenseUriElement))