diff --git a/.github/actions/expand-tokens/Dockerfile b/.github/actions/expand-tokens/Dockerfile
new file mode 100644
index 0000000..72a476f
--- /dev/null
+++ b/.github/actions/expand-tokens/Dockerfile
@@ -0,0 +1,8 @@
+FROM mcr.microsoft.com/powershell
+WORKDIR /action
+
+RUN pwsh -c '$ProgressPreference = "SilentlyContinue"; Install-Module Axinom.DevOpsTooling -Scope AllUsers -AllowPrerelease -Force'
+
+COPY *.ps1 .
+
+ENTRYPOINT ["pwsh", "-c", "& /action/entrypoint.ps1"]
\ No newline at end of file
diff --git a/.github/actions/expand-tokens/action.yml b/.github/actions/expand-tokens/action.yml
new file mode 100644
index 0000000..fba91f3
--- /dev/null
+++ b/.github/actions/expand-tokens/action.yml
@@ -0,0 +1,14 @@
+name: 'Expand tokens'
+description: 'Replaces `__TOKENS__` with the values of equivalent environment variables in files.'
+inputs:
+ path:
+ description: 'Path to the file or directory to process.'
+ required: true
+ filenames:
+ description: 'Filename filter, used if path is a directory.'
+ recursive:
+ description: 'Whether to recursively search for files, used if path is a directory.'
+ default: false
+runs:
+ using: 'docker'
+ image: 'Dockerfile'
\ No newline at end of file
diff --git a/.github/actions/expand-tokens/entrypoint.ps1 b/.github/actions/expand-tokens/entrypoint.ps1
new file mode 100644
index 0000000..5a7800c
--- /dev/null
+++ b/.github/actions/expand-tokens/entrypoint.ps1
@@ -0,0 +1,7 @@
+$ErrorActionPreference = "Stop"
+
+Import-Module Axinom.DevOpsTooling
+
+$path = Join-Path $env:GITHUB_WORKSPACE $env:INPUT_PATH
+
+Expand-Tokens -path $path -filenames $env:INPUT_FILENAMES -recursive:([bool]$env:INPUT_RECURSIVE)
\ No newline at end of file
diff --git a/.github/actions/make-version-string/Dockerfile b/.github/actions/make-version-string/Dockerfile
new file mode 100644
index 0000000..72a476f
--- /dev/null
+++ b/.github/actions/make-version-string/Dockerfile
@@ -0,0 +1,8 @@
+FROM mcr.microsoft.com/powershell
+WORKDIR /action
+
+RUN pwsh -c '$ProgressPreference = "SilentlyContinue"; Install-Module Axinom.DevOpsTooling -Scope AllUsers -AllowPrerelease -Force'
+
+COPY *.ps1 .
+
+ENTRYPOINT ["pwsh", "-c", "& /action/entrypoint.ps1"]
\ No newline at end of file
diff --git a/.github/actions/make-version-string/action.yml b/.github/actions/make-version-string/action.yml
new file mode 100644
index 0000000..6acbafd
--- /dev/null
+++ b/.github/actions/make-version-string/action.yml
@@ -0,0 +1,12 @@
+name: 'Create the version string'
+description: 'Generates a version string of the form [branch-]1.2.3-NNNNNNNNNNN-CCCCCCC, where N is an incrementing value and C identifies the commit, with an optional branch prefix. Fields lengths are unspecified and may increase in future versions.'
+inputs:
+ assemblyInfoPath:
+ description: 'Path to a .NET style AssemblyInfo file containing the numeric version component.'
+ required: true
+outputs:
+ versionstring:
+ description: 'The generated version string'
+runs:
+ using: 'docker'
+ image: 'Dockerfile'
\ No newline at end of file
diff --git a/.github/actions/make-version-string/entrypoint.ps1 b/.github/actions/make-version-string/entrypoint.ps1
new file mode 100644
index 0000000..518394d
--- /dev/null
+++ b/.github/actions/make-version-string/entrypoint.ps1
@@ -0,0 +1,8 @@
+$ErrorActionPreference = "Stop"
+
+Import-Module Axinom.DevOpsTooling
+
+$path = Join-Path $env:GITHUB_WORKSPACE $env:INPUT_ASSEMBLYINFOPATH
+
+$version = Set-DotNetBuildAndVersionStrings -assemblyInfoPath $path -commitId $ENV:GITHUB_SHA
+Write-Host "::set-output name=versionstring::$version"
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..da4f773
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,21 @@
+on: [push, pull_request]
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@master
+ - id: make_version_string
+ uses: ./.github/actions/make-version-string
+ with:
+ assemblyInfoPath: AssemblyInfo.cs
+ - uses: ./.github/actions/expand-tokens
+ env:
+ VERSIONSTRING: ${{ steps.make_version_string.outputs.versionstring }}
+ with:
+ path: Constants.cs
+# - name: Publish to Registry
+# uses: elgohr/Publish-Docker-Github-Action@master
+# with:
+# name: sandersaares/test123
+# username: sandersaares
+# password: ${{ secrets.docker_hub_token }}
diff --git a/AssemblyInfo.cs b/AssemblyInfo.cs
new file mode 100644
index 0000000..dff11dd
--- /dev/null
+++ b/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System.Reflection;
+
+[assembly: AssemblyCompany("prometheus-net")]
+[assembly: AssemblyProduct("Docker Stats Exporter")]
+[assembly: AssemblyVersion("1.0.0")]
\ No newline at end of file
diff --git a/Constants.cs b/Constants.cs
new file mode 100644
index 0000000..9a93cb1
--- /dev/null
+++ b/Constants.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace DockerExporter
+{
+ static class Constants
+ {
+ // Will be replaced with real version string (AssemblyInfo number + build parameters) on automated build.
+ public const string VersionString = "__VERSIONSTRING__";
+
+ ///
+ /// Docker can sometimes be slow to respond. If that is the case, we just give up and try again later.
+ ///
+ public static readonly TimeSpan DockerCommandTimeout = TimeSpan.FromSeconds(30);
+ }
+}
diff --git a/DockerExporter.csproj b/DockerExporter.csproj
new file mode 100644
index 0000000..90cf006
--- /dev/null
+++ b/DockerExporter.csproj
@@ -0,0 +1,30 @@
+
+
+
+ Exe
+ netcoreapp3.1
+ DockerExporter
+ false
+ preview
+ enable
+
+
+
+ true
+
+ true
+
+
+
+ true
+
+ true
+
+
+
+
+
+
+
+
+
diff --git a/DockerExporter.sln b/DockerExporter.sln
new file mode 100644
index 0000000..64175a1
--- /dev/null
+++ b/DockerExporter.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29609.76
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DockerExporter", "DockerExporter.csproj", "{A961E4EB-A444-4DCD-AE0F-48F5FDBFA88F}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A961E4EB-A444-4DCD-AE0F-48F5FDBFA88F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A961E4EB-A444-4DCD-AE0F-48F5FDBFA88F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A961E4EB-A444-4DCD-AE0F-48F5FDBFA88F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A961E4EB-A444-4DCD-AE0F-48F5FDBFA88F}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {645E4C22-DEE1-4A82-AD14-9C7036CA28C0}
+ EndGlobalSection
+EndGlobal
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..e097720
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,18 @@
+FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
+WORKDIR /app
+
+# Separate layers here to avoid redoing dependencies on code change.
+COPY *.sln .
+COPY DockerExporter/*.csproj ./DockerExporter/
+RUN dotnet restore
+
+# Now the code.
+COPY DockerExporter/. ./DockerExporter/
+WORKDIR /app/DockerExporter
+RUN dotnet publish -c Release -o out
+
+FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS runtime
+WORKDIR /app
+COPY --from=build /app/DockerExporter/out ./
+
+ENTRYPOINT ["dotnet", "DockerExporter.dll"]
\ No newline at end of file
diff --git a/ExporterLogic.cs b/ExporterLogic.cs
new file mode 100644
index 0000000..a942e01
--- /dev/null
+++ b/ExporterLogic.cs
@@ -0,0 +1,47 @@
+using Axinom.Toolkit;
+using Docker.DotNet;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace DockerExporter
+{
+ public sealed class ExporterLogic
+ {
+ public string DockerUrl { get; set; }
+
+ public ExporterLogic()
+ {
+ // Default value only valid if not running as container.
+ if (Helpers.Environment.IsMicrosoftOperatingSystem())
+ {
+ DockerUrl = "npipe://./pipe/docker_engine";
+ }
+ else
+ {
+ DockerUrl = "unix:///var/run/docker.sock";
+ }
+ }
+
+ public async Task RunAsync(CancellationToken cancel)
+ {
+ _log.Info($"Connecting to Docker via {DockerUrl}");
+
+ var clientConfig = new DockerClientConfiguration(new Uri(DockerUrl), null, Constants.DockerCommandTimeout);
+
+ using (var client = clientConfig.CreateClient())
+ {
+ var allContainers = await client.Containers.ListContainersAsync(new Docker.DotNet.Models.ContainersListParameters
+ {
+ All = true
+ }, cancel);
+
+ _log.Info(Helpers.Debug.ToDebugString(allContainers));
+ }
+ }
+
+ private static readonly LogSource _log = Log.Default;
+ }
+}
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000..fb4f241
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,155 @@
+using Axinom.Toolkit;
+using Mono.Options;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Threading;
+
+namespace DockerExporter
+{
+ internal sealed class Program
+ {
+ private readonly LogSource _log = Log.Default;
+ private readonly FilteringLogListener _filteringLogListener;
+
+ private readonly ExporterLogic _logic = new ExporterLogic();
+
+ private void Run(string[] args)
+ {
+ // We signal this to shut down the service.
+ var cancel = new CancellationTokenSource();
+
+ try
+ {
+ if (!ParseArguments(args))
+ {
+ Environment.ExitCode = -1;
+ return;
+ }
+
+ _log.Info(GetVersionString());
+
+ // Control+C will gracefully shut us down.
+ Console.CancelKeyPress += (s, e) =>
+ {
+ _log.Info("Canceling execution due to received signal.");
+ e.Cancel = true;
+ cancel.Cancel();
+ };
+
+ _logic.RunAsync(cancel.Token).WaitAndUnwrapExceptions();
+
+ _log.Info("Application logic execution has completed.");
+ }
+ catch (OperationCanceledException)
+ {
+ if (cancel.IsCancellationRequested)
+ {
+ // We really were cancelled. That's fine.
+ }
+ else
+ {
+ _log.Error("Unexpected cancellation/timeout halted execution.");
+ }
+
+ Environment.ExitCode = -1;
+ }
+ catch (AggregateException ex)
+ {
+ foreach (var innerException in ex.InnerExceptions)
+ {
+ _log.Error(innerException.Message);
+ _log.Error(innerException.GetType().Name);
+ }
+
+ Environment.ExitCode = -1;
+ }
+ catch (Exception ex)
+ {
+ if (!string.IsNullOrWhiteSpace(ex.Message))
+ {
+ _log.Error(ex.Message);
+ _log.Error(ex.GetType().Name);
+ }
+
+ Environment.ExitCode = -1;
+ }
+ }
+
+ private bool ParseArguments(string[] args)
+ {
+ var showHelp = false;
+ var verbose = false;
+ var debugger = false;
+
+ var options = new OptionSet
+ {
+ GetVersionString(),
+ "",
+ "General",
+ { "h|?|help", "Displays usage instructions.", val => showHelp = val != null },
+ { "docker-url=", "URL to use for accessing Docker.", val => _logic.DockerUrl = val },
+
+ "",
+ "Diagnostics",
+ { "verbose", "Displays extensive diagnostic information.", val => verbose = val != null },
+ { "debugger", "Requests a debugger to be attached before execution starts.", val => debugger = val != null, true },
+ };
+
+ List remainingOptions;
+
+ try
+ {
+ remainingOptions = options.Parse(args);
+
+ if (showHelp)
+ {
+ options.WriteOptionDescriptions(Console.Out);
+ return false;
+ }
+
+ if (verbose)
+ _filteringLogListener.MinimumSeverity = LogEntrySeverity.Debug;
+ }
+ catch (OptionException ex)
+ {
+ Console.WriteLine(ex.Message);
+ Console.WriteLine("For usage instructions, use the --help command line parameter.");
+ return false;
+ }
+
+ if (remainingOptions.Count != 0)
+ {
+ Console.WriteLine("Unknown command line parameters: {0}", string.Join(" ", remainingOptions.ToArray()));
+ Console.WriteLine("For usage instructions, use the --help command line parameter.");
+ return false;
+ }
+
+ if (debugger)
+ Debugger.Launch();
+
+ return true;
+ }
+
+ private string GetVersionString()
+ {
+ return $"{typeof(Program).Namespace} v{Constants.VersionString}";
+ }
+
+ private Program()
+ {
+ // We default to displaying Info or higher but allow this to be reconfiured later, if the user wishes.
+ _filteringLogListener = new FilteringLogListener(new ConsoleLogListener())
+ {
+ MinimumSeverity = LogEntrySeverity.Info
+ };
+
+ Log.Default.RegisterListener(_filteringLogListener);
+ }
+
+ private static void Main(string[] args)
+ {
+ new Program().Run(args);
+ }
+ }
+}