Skip to content

Commit

Permalink
#69: Add Management API
Browse files Browse the repository at this point in the history
  • Loading branch information
sevensolutions authored Sep 8, 2024
1 parent 8121e11 commit f8e6031
Show file tree
Hide file tree
Showing 29 changed files with 1,329 additions and 133 deletions.
13 changes: 10 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,17 @@ on:
workflow_dispatch:

jobs:

build:
runs-on: windows-latest

strategy:
matrix:
profile:
- publishProfile: "Release.pubxml"
artifactName: "nomad_iis"
- publishProfile: "ReleaseWithMgmtApi.pubxml"
artifactName: "nomad_iis_mgmt_api"

steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -27,7 +34,7 @@ jobs:

# Publish the application
- name: Publish the application
run: dotnet publish "./src/NomadIIS/NomadIIS.csproj" /p:PublishProfile="./src/NomadIIS/Properties/PublishProfiles/Release.pubxml"
run: dotnet publish "./src/NomadIIS/NomadIIS.csproj" /p:PublishProfile="./src/NomadIIS/Properties/PublishProfiles/${{ matrix.profile.publishProfile }}"

# Copy to output
- name: Copy to output folder
Expand All @@ -39,5 +46,5 @@ jobs:
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: nomad_iis
name: ${{ matrix.profile.artifactName }}
path: ./dist
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</a>
</p>

This repository contains a task driver for [HashiCorp Nomad](https://www.nomadproject.io/) to run web-applications in IIS on Windows machines. Unlike most other Nomad task drivers, this one is written in the C# language using ASP.NET 8.
A task driver for [HashiCorp Nomad](https://www.nomadproject.io/) to run web-applications in IIS on Windows machines. Unlike most other Nomad task drivers, this one is written in the C# language using ASP.NET 8.
It uses the *Microsoft.Web.Administration*-API to communicate with IIS.
Feel free to use it as-is or as a reference implementation for your own C#-based Nomad-plugins.

Expand All @@ -28,13 +28,14 @@ Feel free to use it as-is or as a reference implementation for your own C#-based
| Virtual Directories || Support for multiple *virtual directories* below an application. |
| HTTP Bindings || |
| HTTPS Bindings || [GH-3](https://github.com/sevensolutions/nomad-iis/issues/3) |
| Environment Variables || [Details](#-environment-variables) |
| Environment Variables || [Details](https://nomad-iis.sevensolutions.cc/docs/features/environment-variables) |
| Resource Statistics || |
| Logging || Experimental UDP logging. See [GH-6](https://github.com/sevensolutions/nomad-iis/issues/6) for details. |
| Signals with `nomad alloc signal` || [Details](#-supported-signals) |
| Signals || [Details](https://nomad-iis.sevensolutions.cc/docs/features/signals) |
| Exec (Shell Access) || I'am playing around a little bit but don't want to give you hope :/. See [GH-15](https://github.com/sevensolutions/nomad-iis/issues/15) for status. |
| Filesystem Isolation | 🔶 | [Details](#-filesystem-isolation) |
| Filesystem Isolation | 🔶 | [Details](https://nomad-iis.sevensolutions.cc/docs/features/filesystem-isolation) |
| Nomad Networking || |
| Management API || *Nomad IIS* provides a very powerfull [Management API](https://nomad-iis.sevensolutions.cc/docs/features/management-api) with functionalities like taking a local screenshot or creating a process dump. |

## 📚 Documentation

Expand Down
10 changes: 8 additions & 2 deletions examples/agent.dev.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@
log_level = "TRACE"

plugin "nomad_iis" {
args = ["--management-api-port=5004", "--management-api-key=12345"]
config {
enabled = true,
fingerprint_interval = "10s"
allowed_target_websites = [ "Default Web Site" ]
fingerprint_interval = "10s"
allowed_target_websites = [ "Default Web Site" ]

procdump {
binary_path = "C:\\procdump.exe"
accept_eula = true
}
}
}
2 changes: 1 addition & 1 deletion examples/agent.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ client {
plugin "nomad_iis" {
config {
enabled = true,
fingerprint_interval = "10s"
fingerprint_interval = "10s"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ client {
plugin "nomad_iis" {
config {
enabled = true,
fingerprint_interval = "10s"
fingerprint_interval = "10s"
allowed_target_websites = [ "Default Web Site" ]
}
}
30 changes: 30 additions & 0 deletions src/NomadIIS.Tests/Data/configs/with_api.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0

log_level = "TRACE"

bind_addr = "0.0.0.0"

server {
enabled = true
bootstrap_expect = 1
}

client {
enabled = true
network_interface = "Ethernet"
}

plugin "nomad_iis" {
args = ["--management-api-port=5004", "--management-api-key=12345"]
config {
enabled = true,
fingerprint_interval = "10s"
allowed_target_websites = [ "Default Web Site" ]

procdump {
binary_path = "C:\\procdump.exe"
accept_eula = true
}
}
}
11 changes: 0 additions & 11 deletions src/NomadIIS.Tests/Data/server.hcl

This file was deleted.

70 changes: 70 additions & 0 deletions src/NomadIIS.Tests/IntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,74 @@ public async Task JobWithSettings_PoolShouldHaveSettings ()

_output.WriteLine( "Job stopped." );
}

#if MANAGEMENT_API
[Fact]
public async Task ManagementApi_TakeScreenshot ()
{
var jobHcl = """
job "screenshot-job" {
datacenters = ["dc1"]
type = "service"

group "app" {
count = 1

network {
port "httplabel" {}
}

task "app" {
driver = "iis"

config {
application {
path = "C:\\inetpub\\wwwroot"
}

binding {
type = "http"
port = "httplabel"
}
}
}
}
}
""";

_output.WriteLine( "Submitting job..." );

var jobId = await _fixture.ScheduleJobAsync( jobHcl );

_output.WriteLine( $"Job Id: {jobId}" );

var allocations = await _fixture.ListJobAllocationsAsync( jobId );

if ( allocations is null || allocations.Length == 0 )
Assert.Fail( "No job allocations" );

var allocId = allocations[0].Id;
var poolAndWebsiteName = $"nomad-{allocId}-app";

_output.WriteLine( $"AppPool and Website Name: {poolAndWebsiteName}" );

_fixture.AccessIIS( iis =>
{
iis.AppPool( poolAndWebsiteName ).ShouldExist();
iis.Website( poolAndWebsiteName ).ShouldExist();
} );

var screenshotData = await _fixture.TakeScreenshotAsync( allocId, "app" );

_output.WriteLine( $"Returned screenshot with a size of {screenshotData.Length / 1024}kB." );

Assert.True( screenshotData.Length > 10_000, "Invalid screenshot received." );

_output.WriteLine( "Stopping job..." );

await _fixture.StopJobAsync( jobId );

_output.WriteLine( "Job stopped." );
}
#endif
}
8 changes: 8 additions & 0 deletions src/NomadIIS.Tests/NomadIIS.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<DefineConstants>MANAGEMENT_API;$(DefineConstants)</DefineConstants>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CliWrap" Version="3.6.6" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
Expand All @@ -29,6 +33,10 @@
</Content>
</ItemGroup>

<ItemGroup>
<None Remove="Data\nomad_api_config.hcl" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\NomadIIS\NomadIIS.csproj" />
</ItemGroup>
Expand Down
64 changes: 57 additions & 7 deletions src/NomadIIS.Tests/NomadIISFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@
using System.IO;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Threading;

namespace NomadIIS.Tests;

public sealed class NomadIISFixture : IAsyncLifetime
{
private readonly HttpClient _httpClient;
#if MANAGEMENT_API
private readonly HttpClient _apiHttpClient;
#endif
private CancellationTokenSource _ctsNomad = new CancellationTokenSource();
private Thread? _nomadThread;

Expand All @@ -23,6 +27,18 @@ public NomadIISFixture ()
BaseAddress = new Uri( "http://localhost:4646/v1/" ),
Timeout = TimeSpan.FromSeconds( 10 )
};

#if MANAGEMENT_API
_apiHttpClient = new HttpClient()
{
BaseAddress = new Uri( "http://localhost:5004/api/v1/" ),
Timeout = TimeSpan.FromSeconds( 30 ),
DefaultRequestHeaders =
{
{ "X-Api-Key", "12345" }
}
};
#endif
}

public HttpClient HttpClient => _httpClient;
Expand All @@ -37,19 +53,40 @@ public async Task InitializeAsync ()
pluginDir = @"..\..\..\..\NomadIIS\bin\Debug\net8.0";

var pluginDirectory = Path.GetFullPath( pluginDir );
var configFile = Path.GetFullPath( @"Data\serverAndClient.hcl" );

#if MANAGEMENT_API
var configFile = Path.GetFullPath( @"Data\configs\with_api.hcl" );
#else
var configFile = Path.GetFullPath( @"Data\configs\default.hcl" );
#endif

_nomadThread = new Thread( async () =>
{
var stdout = new StringBuilder();
var stderr = new StringBuilder();
var nomadCommand = Cli.Wrap( Path.Combine( nomadDirectory, "nomad.exe" ) )
.WithArguments( $"agent -dev -plugin-dir=\"{pluginDirectory}\"" )
.WithArguments( $"agent -dev -config=\"{configFile}\" -plugin-dir=\"{pluginDirectory}\"" )
.WithWorkingDirectory( nomadDirectory )
.WithValidation( CommandResultValidation.None );
var result = await nomadCommand.ExecuteBufferedAsync( _ctsNomad.Token );
.WithStandardOutputPipe( PipeTarget.ToDelegate( line =>
{
stdout.AppendLine( line );
Debug.WriteLine( line );
} ) )
.WithStandardOutputPipe( PipeTarget.ToDelegate( line =>
{
stderr.AppendLine( line );
Debug.WriteLine( line );
} ) );
Debug.WriteLine( result.StandardOutput );
Debug.WriteLine( result.StandardError );
try
{
var result = await nomadCommand.ExecuteBufferedAsync( _ctsNomad.Token );
}
catch ( Exception ex )
{
Debug.WriteLine( ex.Message );
}
} );

_nomadThread.Start();
Expand Down Expand Up @@ -172,4 +209,17 @@ public void AccessIIS ( Action<IisHandle> action )

action( handle );
}

#if MANAGEMENT_API
public async Task<byte[]> TakeScreenshotAsync ( string allocId, string taskName )
{
var response = await _apiHttpClient.GetAsync( $"allocs/{allocId}/{taskName}/screenshot" );

response.EnsureSuccessStatusCode();

using var ms = new MemoryStream();
await response.Content.CopyToAsync( ms );
return ms.ToArray();
}
#endif
}
30 changes: 30 additions & 0 deletions src/NomadIIS/ManagementApi/ApiModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Text.Json.Serialization;

namespace NomadIIS.ManagementApi.ApiModel;

public class TaskStatusResponse
{
[JsonPropertyName( "allocId" )]
public string AllocId { get; set; } = default!;
[JsonPropertyName( "taskName" )]
public string TaskName { get; set; } = default!;
[JsonPropertyName( "applicationPool" )]
public ApplicationPool ApplicationPool { get; set; } = default!;
}
public class ApplicationPool
{
[JsonPropertyName( "status" )]
public ApplicationPoolStatus Status { get; set; }
[JsonPropertyName( "isWorkerProcessRunning" )]
public bool IsWorkerProcessRunning { get; set; }
}

[JsonConverter( typeof( JsonStringEnumConverter<ApplicationPoolStatus> ) )]
public enum ApplicationPoolStatus
{
Starting,
Started,
Stopping,
Stopped,
Unknown
}
Loading

0 comments on commit f8e6031

Please sign in to comment.