Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
15 changes: 15 additions & 0 deletions src/DependencyManagement/IPowerShellGallerySearchInvoker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

namespace Microsoft.Azure.Functions.PowerShellWorker.DependencyManagement
{
using System;
using System.IO;

internal interface IPowerShellGallerySearchInvoker
{
Stream Invoke(Uri uri);
}
}
92 changes: 49 additions & 43 deletions src/DependencyManagement/PowerShellGalleryModuleProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@
namespace Microsoft.Azure.Functions.PowerShellWorker.DependencyManagement
{
using System;
using System.IO;
using System.Management.Automation;
using System.Net.Http;
using System.Xml;

using Microsoft.Azure.Functions.PowerShellWorker.PowerShell;
using Microsoft.Azure.Functions.PowerShellWorker.Utility;

internal class PowerShellGalleryModuleProvider : IModuleProvider
{
private readonly IPowerShellGallerySearchInvoker _searchInvoker;

public PowerShellGalleryModuleProvider(IPowerShellGallerySearchInvoker searchInvoker = null)
{
_searchInvoker = searchInvoker ?? new PowerShellGallerySearchInvoker();
}

/// <summary>
/// Returns the latest module version from the PSGallery for the given module name and major version.
/// </summary>
Expand All @@ -27,38 +32,18 @@ public string GetLatestPublishedModuleVersion(string moduleName, string majorVer

Uri address = new Uri($"{PowerShellGalleryFindPackagesByIdUri}'{moduleName}'");

string latestMajorVersion = null;
Stream stream = null;
var expectedVersionStart = majorVersion + ".";

Version latestVersion = null;

var retryCount = 3;
while (true)
do
{
using (var client = new HttpClient())
var stream = _searchInvoker.Invoke(address);
if (stream == null)
{
try
{
var response = client.GetAsync(address).Result;

// Throw is not a successful request
response.EnsureSuccessStatusCode();

stream = response.Content.ReadAsStreamAsync().Result;
break;
}
catch (Exception)
{
if (retryCount <= 0)
{
throw;
}

retryCount--;
}
break;
}
}

if (stream != null)
{
// Load up the XML response
XmlDocument doc = new XmlDocument();
using (XmlReader reader = XmlReader.Create(stream))
Expand All @@ -68,27 +53,20 @@ public string GetLatestPublishedModuleVersion(string moduleName, string majorVer

// Add the namespaces for the gallery xml content
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("ps", "http://www.w3.org/2005/Atom");
nsmgr.AddNamespace("a", "http://www.w3.org/2005/Atom");
nsmgr.AddNamespace("d", "http://schemas.microsoft.com/ado/2007/08/dataservices");
nsmgr.AddNamespace("m", "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata");

// Find the version information
XmlNode root = doc.DocumentElement;
var props = root.SelectNodes("//m:properties[d:IsPrerelease = \"false\"]/d:Version", nsmgr);
latestVersion = GetLatestVersion(root, nsmgr, expectedVersionStart, latestVersion);

if (props != null && props.Count > 0)
{
foreach (XmlNode prop in props)
{
if (prop.FirstChild.Value.StartsWith(majorVersion))
{
latestMajorVersion = prop.FirstChild.Value;
}
}
}
// The response may be paginated. In this case, the current page
// contains a link to the next page.
address = GetNextLink(root, nsmgr);
}
while (address != null);

return latestMajorVersion;
return latestVersion?.ToString();
}

/// <summary>
Expand Down Expand Up @@ -125,5 +103,33 @@ public void Cleanup(PowerShell pwsh)
.AddParameter("ErrorAction", "SilentlyContinue")
.InvokeAndClearCommands();
}

private static Version GetLatestVersion(
XmlNode root, XmlNamespaceManager namespaceManager, string expectedVersionStart, Version latestVersion)
{
var versions = root.SelectNodes("/a:feed/a:entry/m:properties[d:IsPrerelease = \"false\"]/d:Version", namespaceManager);
if (versions != null)
{
foreach (XmlNode prop in versions)
{
if (prop.FirstChild.Value.StartsWith(expectedVersionStart)
&& Version.TryParse(prop.FirstChild.Value, out var thisVersion))
{
if (latestVersion == null || thisVersion > latestVersion)
{
latestVersion = thisVersion;
}
}
}
}

return latestVersion;
}

private static Uri GetNextLink(XmlNode root, XmlNamespaceManager namespaceManager)
{
var nextLink = root.SelectNodes("/a:feed/a:link[@rel=\"next\"]/@href", namespaceManager);
return nextLink.Count == 1 ? new Uri(nextLink[0].Value) : null;
}
}
}
43 changes: 43 additions & 0 deletions src/DependencyManagement/PowerShellGallerySearchInvoker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

namespace Microsoft.Azure.Functions.PowerShellWorker.DependencyManagement
{
using System;
using System.IO;
using System.Net.Http;

internal class PowerShellGallerySearchInvoker : IPowerShellGallerySearchInvoker
{
public Stream Invoke(Uri uri)
{
var retryCount = 3;
while (true)
{
using (var client = new HttpClient())
{
try
{
var response = client.GetAsync(uri).Result;

// Throw is not a successful request
response.EnsureSuccessStatusCode();

return response.Content.ReadAsStreamAsync().Result;
}
catch (Exception)
{
if (retryCount <= 0)
{
throw;
}

retryCount--;
}
}
}
}
}
}
Loading