Skip to content
This repository has been archived by the owner on Sep 4, 2024. It is now read-only.

Commit

Permalink
Add an API to return available versions of Node and NPM
Browse files Browse the repository at this point in the history
Fixes #784
  • Loading branch information
pranavkm committed Oct 26, 2013
1 parent 4b8c36c commit 5af0828
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 0 deletions.
12 changes: 12 additions & 0 deletions Kudu.Contracts/Diagnostics/RuntimeInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Collections.Generic;
using System.Runtime.Serialization;

namespace Kudu.Services.Diagnostics
{
[DataContract(Name = "runtime")]
public class RuntimeInfo
{
[DataMember(Name = "nodejs")]
public IEnumerable<Dictionary<string, string>> NodeVerions { get; set; }
}
}
1 change: 1 addition & 0 deletions Kudu.Contracts/Kudu.Contracts.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<Compile Include="Deployment\LogEntryType.cs" />
<Compile Include="Diagnostics\ProcessInfo.cs" />
<Compile Include="Diagnostics\ProcessThreadInfo.cs" />
<Compile Include="Diagnostics\RuntimeInfo.cs" />
<Compile Include="Editor\VfsStatEntry.cs" />
<Compile Include="Hooks\ConflictException.cs" />
<Compile Include="Hooks\WebHook.cs" />
Expand Down
1 change: 1 addition & 0 deletions Kudu.Services.Test/Kudu.Services.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
<Compile Include="KilnHgHandlerFacts.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ReceivePackHandlerFacts.cs" />
<Compile Include="RuntimeControllerFacts.cs" />
<Compile Include="SettingsControllerFacts.cs" />
<Compile Include="SSHKeyControllerTests.cs" />
<Compile Include="TestMessageHandler.cs" />
Expand Down
83 changes: 83 additions & 0 deletions Kudu.Services.Test/RuntimeControllerFacts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Text;
using Kudu.Contracts.Tracing;
using Kudu.Services.Diagnostics;
using Moq;
using Xunit;

namespace Kudu.Services.Test
{
public class RuntimeControllerFacts
{
private static readonly string _programFilesDir = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
private static readonly string _nodeDir = Path.Combine(_programFilesDir, "nodejs");

[Fact]
public void RuntimeControllerReturnsEmptyListIfDirectoryDoesNotExist()
{
// Arrange
var nodeDir = new Mock<DirectoryInfoBase>();
nodeDir.Setup(d => d.Exists).Returns(false);
var directory = new Mock<IDirectoryInfoFactory>();
directory.Setup(d => d.FromDirectoryName(_nodeDir)).Returns(nodeDir.Object);
var fileSystem = new Mock<IFileSystem>();
fileSystem.Setup(f => f.DirectoryInfo).Returns(directory.Object);

var controller = new RuntimeController(Mock.Of<ITracer>(), fileSystem.Object);

// Act
var runtimeInfo = controller.GetRuntimeVersions();

// Assert
Assert.Empty(runtimeInfo.NodeVerions);
}

[Fact]
public void RuntimeControllerReturnsNodeVersions()
{
// Arrange
var nodeDir = new Mock<DirectoryInfoBase>();
nodeDir.Setup(d => d.Exists).Returns(true);
nodeDir.Setup(d => d.GetDirectories()).Returns(new[] {
CreateDirectory("0.8.19", CreateFile("npm.txt", "1.2.8")),
CreateDirectory("0.10.5", CreateFile("npm.txt", "1.3.11")),
CreateDirectory("0.10.18"),
CreateDirectory("node_modules"),
CreateDirectory("docs")
});
var directoryInfo = new Mock<IDirectoryInfoFactory>();
directoryInfo.Setup(d => d.FromDirectoryName(_nodeDir)).Returns(nodeDir.Object);
var fileSystem = new Mock<IFileSystem>();
fileSystem.Setup(f => f.DirectoryInfo).Returns(directoryInfo.Object);

var controller = new RuntimeController(Mock.Of<ITracer>(), fileSystem.Object);

// Act
var nodeVersions = controller.GetRuntimeVersions().NodeVerions.ToList();

// Assert
Assert.Equal(new[] { "0.8.19", "0.10.5", "0.10.18" }, nodeVersions.Select(v => v["version"]));
Assert.Equal(new[] { "1.2.8", "1.3.11", null }, nodeVersions.Select(v => v["npm"]));
}

private DirectoryInfoBase CreateDirectory(string name, params FileInfoBase[] files)
{
var dir = new Mock<DirectoryInfoBase>();
dir.SetupGet(d => d.Name).Returns(name);
dir.Setup(d => d.GetFiles(It.IsAny<string>())).Returns(files);
return dir.Object;
}

private FileInfoBase CreateFile(string fileName, string content)
{
var file = new Mock<FileInfoBase>();
file.SetupGet(f => f.Name).Returns(fileName);
var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(content));
file.Setup(f => f.OpenRead()).Returns(memoryStream);
return file.Object;
}
}
}
3 changes: 3 additions & 0 deletions Kudu.Services.Web/App_Start/NinjectServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,9 @@ public static void RegisterRoutes(IKernel kernel, RouteCollection routes)
routes.MapHttpRoute("all-threads", "diagnostics/processes/{id}/threads", new { controller = "Process", action = "GetAllThreads" }, new { verb = new HttpMethodConstraint("GET") });
routes.MapHttpRoute("one-process-thread", "diagnostics/processes/{processId}/threads/{threadId}", new { controller = "Process", action = "GetThread" }, new { verb = new HttpMethodConstraint("GET") });

// Runtime
routes.MapHttpRoute("runtime", "diagnostics/runtime", new { controller = "Runtime", action = "GetRuntimeVersions" }, new { verb = new HttpMethodConstraint("GET") });

// Hooks
routes.MapHttpRoute("unsubscribe-hook", "hooks/{id}", new { controller = "WebHooks", action = "Unsubscribe" }, new { verb = new HttpMethodConstraint("DELETE") });
routes.MapHttpRoute("get-hook", "hooks/{id}", new { controller = "WebHooks", action = "GetWebHook" }, new { verb = new HttpMethodConstraint("GET") });
Expand Down
81 changes: 81 additions & 0 deletions Kudu.Services/Diagnostics/RuntimeController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Net.Http.Formatting;
using System.Text.RegularExpressions;
using System.Web.Http;
using System.Web.Http.Controllers;
using Kudu.Contracts.Tracing;
using Newtonsoft.Json;

namespace Kudu.Services.Diagnostics
{
public class RuntimeController : ApiController
{
private const string VersionKey = "version";
private static readonly Regex _versionRegex = new Regex(@"^\d+\.\d+", RegexOptions.ExplicitCapture);
private readonly ITracer _tracer;
private readonly IFileSystem _fileSystem;

public RuntimeController(ITracer tracer, IFileSystem fileSystem)
{
_tracer = tracer;
_fileSystem = fileSystem;
}

protected override void Initialize(HttpControllerContext controllerContext)
{
controllerContext.Configuration.Formatters.Clear();

var settings = new JsonSerializerSettings
{
ContractResolver = new LowerCasePropertyNamesContractResolver()
};
controllerContext.Configuration.Formatters.Add(new JsonMediaTypeFormatter { SerializerSettings = settings });
}

[HttpGet]
public RuntimeInfo GetRuntimeVersions()
{
using (_tracer.Step("RuntimeController.GetRuntimeVersions"))
{
return new RuntimeInfo
{
NodeVerions = GetNodeVersions()
};
}
}

private IEnumerable<Dictionary<string, string>> GetNodeVersions()
{
string nodeRoot = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "nodejs");
var directoryInfo = _fileSystem.DirectoryInfo.FromDirectoryName(nodeRoot);
if (directoryInfo.Exists)
{
return directoryInfo.GetDirectories()
.Where(dir => _versionRegex.IsMatch(dir.Name))
.Select(dir => new Dictionary<string, string>
{
{ VersionKey, dir.Name },
{ "npm", TryReadNpmVersion(dir) }
});
}
return Enumerable.Empty<Dictionary<string, string>>();
}

private string TryReadNpmVersion(DirectoryInfoBase nodeDir)
{
var npmRedirectionFile = nodeDir.GetFiles("npm.txt").FirstOrDefault();
if (npmRedirectionFile == null)
{
return null;
}
using (StreamReader reader = new StreamReader(npmRedirectionFile.OpenRead()))
{
return reader.ReadLine();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Newtonsoft.Json.Serialization;

namespace Kudu.Services
{
public class LowerCasePropertyNamesContractResolver : DefaultContractResolver
{
protected override string ResolvePropertyName(string propertyName)
{
return propertyName.ToLowerInvariant();
}
}
}
2 changes: 2 additions & 0 deletions Kudu.Services/Kudu.Services.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
<Compile Include="Diagnostics\LogStreamHandler.cs" />
<Compile Include="Diagnostics\LogStreamManager.cs" />
<Compile Include="Diagnostics\ProcessController.cs" />
<Compile Include="Diagnostics\RuntimeController.cs" />
<Compile Include="Editor\VfsController.cs" />
<Compile Include="FetchHelpers\DropboxEntryInfo.cs" />
<Compile Include="FetchHelpers\DropboxDeployInfo.cs" />
Expand All @@ -108,6 +109,7 @@
<Compile Include="Hooks\WebHooksController.cs" />
<Compile Include="HttpRequestExtensions.cs" />
<Compile Include="Infrastructure\InstanceIdUtility.cs" />
<Compile Include="Infrastructure\LowerCasePropertyNamesContractResolver.cs" />
<Compile Include="ServiceHookHandlers\CodebaseHqHandler.cs" />
<Compile Include="ServiceHookHandlers\CodePlexHandler.cs" />
<Compile Include="ServiceHookHandlers\DropboxHandler.cs" />
Expand Down

0 comments on commit 5af0828

Please sign in to comment.