added docker support, various little changes

This commit is contained in:
taywon 2024-04-29 17:30:47 +02:00
parent 1ca4517984
commit f903be2342
16 changed files with 431 additions and 72 deletions

View File

@ -0,0 +1,64 @@
using Salmon.Core;
using Salmon.Core.Cliff;
namespace Salmon.Model.Monitor;
public class Container
: Element
{
[PersistentField]
public bool? Joinable { get; set; }
[PersistentField]
public bool? Running { get; set; }
[PersistentField]
public string? ContainerId { get; set; }
[PersistentField]
public string? Image { get; set; }
[PersistentField]
public string? ImageId { get; set; }
[PersistentField]
public string? ContainerStatus { get; set; }
public bool Ok {
get
{
return Joinable == true && Running == true;
}
}
protected Container()
{
}
public Container(string id)
: base(id)
{
}
public override IEnumerable<KeyValuePair<string, object>> ImportantProperties()
{
foreach (var i in base.ImportantProperties())
yield return new KeyValuePair<string, object>(i.Key, i.Value);
yield return new KeyValuePair<string, object>(nameof(Ok), Ok);
if (ContainerId is not null)
yield return new KeyValuePair<string, object>(nameof(ContainerId), ContainerId);
if (Image is not null)
yield return new KeyValuePair<string, object>(nameof(Image), Image);
if (ContainerStatus is not null)
yield return new KeyValuePair<string, object>(nameof(ContainerStatus), ContainerStatus);
}
}

View File

@ -1,4 +1,4 @@
 using Salmon.Core.Cliff; using Salmon.Core.Cliff;
namespace Salmon.Model.Monitor; namespace Salmon.Model.Monitor;

View File

@ -0,0 +1,92 @@
using Salmon.Core;
using Salmon.Core.Cliff;
using System.Diagnostics;
namespace Salmon.Model.Monitor;
public class WebEndpoint
: Element
{
[PersistentField]
public bool Joinable { get; set; } = true;
[PersistentField]
public string? Uri { get; set; }
[PersistentField]
public string? Method { get; set; }
[PersistentField]
public int? Code { get; set; }
[PersistentField]
public string? Content { get; set; }
public bool Ok
{
get
{
return Joinable && (Code != null && Code >= 200 && Code < 300);
}
}
protected WebEndpoint()
{
}
public WebEndpoint(string identifier)
: base(identifier)
{
}
public override IEnumerable<KeyValuePair<string, object>> ImportantProperties()
{
foreach (var kv in base.ImportantProperties())
yield return kv;
if (Uri is not null)
yield return new KeyValuePair<string, object>(nameof(Uri), Uri);
if (Method is not null)
yield return new KeyValuePair<string, object>(nameof(Method), Method);
yield return new KeyValuePair<string, object>(nameof(Ok), Ok);
if (Code is not null)
yield return new KeyValuePair<string, object>(nameof(Code), Code);
}
public static async Task<WebEndpoint> From(string uri, string method = "GET", string? id = null)
{
if(id is null)
id = Helper.CreateIdFrom("webendpoint", uri, method);
using HttpClient client = new();
HttpMethod httpmethod = new(method);
HttpRequestMessage request = new(httpmethod, uri);
HttpResponseMessage response;
var ret = new WebEndpoint(id)
{
Uri = uri,
Method = method
};
try
{
response = await client.SendAsync(request);
}
catch(Exception e)
{
ret.Joinable = false;
ret.Content = e.ToString();
return ret;
}
ret.Code = (int)response.StatusCode;
ret.Content = response.Content.ToString();
return ret;
}
}

View File

@ -1,8 +1,4 @@
using Salmon.Core; namespace Salmon.Service;
using Salmon.Model.Monitor;
using System.Security.Cryptography.X509Certificates;
namespace Salmon.Service;
public class Configuration public class Configuration
{ {
@ -11,37 +7,5 @@ public class Configuration
public bool? MonitorHardware { get; set; } = true; public bool? MonitorHardware { get; set; } = true;
public bool? MonitorLocalSoftware { get; set; } = true; public bool? MonitorLocalSoftware { get; set; } = true;
public List<WatcherConfiguration> Watch { get; set; } = new (); public List<Watchers.Base> Watch { get; set; } = new ();
}
public abstract class WatcherConfiguration
{
public abstract Task<IEnumerable<Element>> ForgeElements();
}
public class ExecutableInstanceWatcherConfiguration
: WatcherConfiguration
{
public string Path { get; set; }
public override async Task<IEnumerable<Element>> ForgeElements()
{
List<Element> ret = new();
foreach (var el in Software.FromPath(Path))
ret.Add(el);
return ret;
}
}
public class HttpPageWatcherConfiguration
: WatcherConfiguration
{
public string Method { get; set; } = "GET";
public string Uri { get; set; }
public override async Task<IEnumerable<Element>> ForgeElements()
{
throw new NotImplementedException();
}
} }

View File

@ -1,9 +0,0 @@
namespace Salmon.Service;
public class Monitor
{
private void FlushStatus()
{
}
}

View File

@ -12,6 +12,8 @@ int? period = null;
Uri? uri = null; Uri? uri = null;
bool? monitor_hardware = null; bool? monitor_hardware = null;
bool? monitor_localsoftware = null; bool? monitor_localsoftware = null;
List<Salmon.Service.Watchers.Base> watchers = new();
bool? watch_config = true;
// 1: use environment variable // 1: use environment variable
var period_env = Environment.GetEnvironmentVariable(ENV_PERIOD); var period_env = Environment.GetEnvironmentVariable(ENV_PERIOD);
@ -34,39 +36,70 @@ if(uri_env != null)
// 2: override with configuration file // 2: override with configuration file
string configpath = "config.json"; string configpath = "config.json";
if (File.Exists(configpath)) Func<bool, Task<int>> reloadConfigFile = new(async (bool configFileMustExists ) =>
{ {
await Task.Delay(500);
if (!File.Exists(configpath))
{
if (!configFileMustExists)
return 0;
Console.WriteLine($"Cannot find config file\"{configpath}\".");
return 1;
}
string content;
try
{
content = File.ReadAllText(configpath);
}
catch(Exception ex)
{
Console.WriteLine($"Cannot open config file\"{configpath}\": {ex}.");
return 1;
}
Console.WriteLine("Found a configuration file, parsing..."); Console.WriteLine("Found a configuration file, parsing...");
string content = File.ReadAllText(configpath);
Configuration? conf = null; Configuration? conf = null;
try try
{ {
conf = JsonSerializer.Deserialize<Configuration>(content); conf = JsonSerializer.Deserialize<Configuration>(content);
} }
catch(Exception e) catch (Exception e)
{ {
Console.WriteLine($"Cannot parse config file \"{configpath}\", {e}."); Console.WriteLine($"Cannot parse config file \"{configpath}\", {e}.");
return 1; return 1;
} }
if(conf == null) if (conf == null)
{ {
Console.WriteLine($"Cannot parse config file \"{configpath}\"."); Console.WriteLine($"Cannot parse config file \"{configpath}\".");
return 1; return 1;
} }
if(conf.Url != null) if (conf.Url != null)
uri = conf.Url; uri = conf.Url;
if(conf.Period != null) if (conf.Period != null)
period = conf.Period; period = conf.Period;
if(conf.MonitorHardware != null) if (conf.MonitorHardware != null)
monitor_hardware = conf.MonitorHardware; monitor_hardware = conf.MonitorHardware;
if(conf.MonitorLocalSoftware != null) if (conf.MonitorLocalSoftware != null)
monitor_localsoftware = conf.MonitorLocalSoftware; monitor_localsoftware = conf.MonitorLocalSoftware;
}
if (conf.Watch != null)
watchers = conf.Watch;
return 0;
});
if (await reloadConfigFile(false) != 0)
return 0;
//todo: override with parameter //todo: override with parameter
@ -84,17 +117,32 @@ if(uri == null)
return 1; return 1;
} }
// Initialisation // Initialisation
Transmitter transmitter = new(); Transmitter transmitter = new();
FileSystemWatcher configWatcher;
Console.WriteLine($"Salmon.Service started at {DateTime.Now}."); Console.WriteLine($"Salmon.Service started at {DateTime.Now}.");
if(configpath != null && watch_config == true)
{
FileInfo fi = new (configpath);
//watch configuration if needed
configWatcher = new FileSystemWatcher(fi.DirectoryName);
configWatcher.Filter = fi.Name;
configWatcher.NotifyFilter = NotifyFilters.CreationTime
| NotifyFilters.LastWrite;
configWatcher.Changed += async (object sender, FileSystemEventArgs e) =>
{
if (await reloadConfigFile(false) != 0)
Console.WriteLine("Cannot use modified config file, using last valid file.");
else
Console.WriteLine("Config file reloaded.");
};
configWatcher.EnableRaisingEvents = true;
}
while ( true ) while ( true )
{ {
var software = Salmon.Model.Monitor.Software.FromLocal(); var software = Salmon.Model.Monitor.Software.FromLocal();
List<Element> tosend = new(); List<Element> tosend = new();
@ -106,6 +154,18 @@ while ( true )
tosend.AddRange(hardwares); tosend.AddRange(hardwares);
} }
foreach (var w in watchers)
try
{
foreach (var el in await w.ForgeElements())
tosend.Add(el);
}
catch(Exception e)
{
Console.WriteLine($"Error while forging element: {e}.");
}
try try
{ {
await transmitter.SendAsync(uri, tosend); await transmitter.SendAsync(uri, tosend);

View File

@ -1,3 +1,41 @@
{ {
"Url":"http://localhost:5009/api/Push" "Url":"http://localhost:5009/api/Push",
"Period":15000,
"Watch":[
{
"Type":"web",
"ShortName": "Voie93quarts",
"Uri":"https://voie93quarts.fr"
},
{
"Type":"web",
"ShortName": "Voie93quarts fallback",
"Uri":"https://sdgsdgsdg.voie93quarts.fr"
},
{
"Type":"web",
"ShortName": "Home assistant",
"Uri":"https://ha.voie93quarts.fr"
},
{
"Type":"web",
"ShortName": "Gitea",
"Uri":"https://git.voie93quarts.fr"
},
{
"Type":"docker",
"Uri":"http://192.168.1.156:4243",
"Containers":[
{
"ShortName": "Jellyfin container",
"ContainerName": "/jellyfin"
},
{
"ShortName": "Gitea container",
"ContainerName": "/gitea"
}
]
}
]
} }

View File

@ -0,0 +1 @@
dotnet run --project ../Salmon.Service.csproj

View File

@ -0,0 +1,2 @@
cd ../../Salmon.Web
dotnet run http

View File

@ -1,2 +0,0 @@
cd ..
dotnet run

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
@ -8,6 +8,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Docker.DotNet" Version="3.125.15" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.18.1" /> <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.18.1" />
</ItemGroup> </ItemGroup>

View File

@ -0,0 +1,37 @@
using Salmon.Core;
using System.Text.Json.Serialization;
namespace Salmon.Service.Watchers;
[JsonPolymorphic(
TypeDiscriminatorPropertyName = "Type"
, UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization)
]
[JsonDerivedType(typeof(Executable), typeDiscriminator: "executable")]
[JsonDerivedType(typeof(WebStatus), typeDiscriminator: "web")]
[JsonDerivedType(typeof(Docker), typeDiscriminator: "docker")]
public abstract class Base
{
public string? UniqueId { get; set; }
public string? ShortName { get; set; }
public string? LongName { get; set; }
public string? Description { get; set; }
public string? ParentId { get; set; } = null;
public abstract Task<IEnumerable<Element>> ForgeElements();
public void ApplyField(Element e)
{
if(ShortName is not null)
e.ShortName = ShortName;
if(LongName is not null)
e.LongName = LongName;
if(Description is not null)
e.Description = Description;
if(ParentId is not null)
e.ParentId = ParentId;
}
}

View File

@ -0,0 +1,68 @@
using Docker.DotNet;
using Docker.DotNet.Models;
using Salmon.Core;
using Salmon.Model.Monitor;
namespace Salmon.Service.Watchers;
public class Docker
: Base
{
public string Uri { get; set; }
public string Name { get; set; }
public List<DockerContainer> Containers { get; set; } = new List<DockerContainer>();
public override async Task<IEnumerable<Element>> ForgeElements()
{
List<Element> elements = new();
DockerClient client = new DockerClientConfiguration(
new Uri(Uri))
.CreateClient();
var listedContainers = await client.Containers.ListContainersAsync(new global::Docker.DotNet.Models.ContainersListParameters
{
All = true
});
foreach (var i in Containers)
elements.Add(i.FromResponses(Uri, listedContainers));
return elements;
}
}
public class DockerContainer
: Base
{
public string ContainerName { get; set; }
public override Task<IEnumerable<Element>> ForgeElements()
{
// Implement later with Single... Not very efficient.
throw new NotImplementedException();
}
public Element FromResponses(string uri, IList<ContainerListResponse> responses)
{
Container ret = new(UniqueId ?? Helper.CreateIdFrom(uri, ContainerName))
{
Joinable = false
};
foreach (var r in responses)
if(r.Names.Contains(ContainerName))
{
ret.Joinable = true;
ret.Running = r.State == "running";
ret.ContainerId = r.ID;
ret.ContainerStatus = r.Status;
ret.Image = r.Image;
ret.ImageId = r.ImageID;
break;
}
ApplyField(ret);
return ret;
}
}

View File

@ -0,0 +1,21 @@
using Salmon.Core;
using Salmon.Model.Monitor;
namespace Salmon.Service.Watchers;
public class Executable
: Base
{
public string Path { get; set; }
public override async Task<IEnumerable<Element>> ForgeElements()
{
List<Element> ret = new();
foreach (var el in Software.FromPath(Path))
ret.Add(el);
return ret;
}
}

View File

@ -0,0 +1,19 @@
using Salmon.Core;
namespace Salmon.Service.Watchers;
public class WebStatus
: Base
{
public string Method { get; set; } = "GET";
public string Uri { get; set; }
public override async Task<IEnumerable<Element>> ForgeElements()
{
List<Element> elements = new();
var ws = await Salmon.Model.Monitor.WebEndpoint.From(Uri, Method);
ApplyField(ws);
elements.Add(ws);
return elements;
}
}

View File

@ -2,9 +2,12 @@
@inject Salmon.Core.Instance Salmon; @inject Salmon.Core.Instance Salmon;
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="basic-addon3">UniqueId</span>
</div>
<input type="text" class="form-control" aria-describedby="basic-addon3" readonly value="@Id">
</div>
@if (Error is not null) @if (Error is not null)
{ {