Upate extension yaml support

Remove old object query class and add methods to extension which can take yaml objects as aruments.

Add unit test for runawayprocesskiller for yaml support
pull/638/head
Buddhika Chathuranga 2020-08-11 16:35:23 +05:30 committed by NextTurn
parent 42df5eeff9
commit 8013ab3d9d
No known key found for this signature in database
GPG Key ID: 17A0D50ADDE1A0C4
17 changed files with 276 additions and 375 deletions

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Xml;
using WMI;
namespace WinSW.Configuration
@ -132,7 +133,9 @@ namespace WinSW.Configuration
public bool BeepOnShutdown => false;
// Extensions
public object? ExtensionsConfiguration => null;
public XmlNode? ExtensionsConfiguration => null;
public object? YamlExtensionsConfiguration => null;
public string BaseName
{

View File

@ -74,7 +74,9 @@ namespace WinSW.Configuration
bool BeepOnShutdown { get; }
// Extensions
object? ExtensionsConfiguration { get; }
XmlNode? ExtensionsConfiguration { get; }
object? YamlExtensionsConfiguration { get; }
List<string> ExtensionIds { get; }

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Xml;
using WinSW.Native;
using WinSW.Util;
using WMI;
@ -98,9 +99,6 @@ namespace WinSW.Configuration
[YamlMember(Alias = "securityDescriptor")]
public string? SecurityDescriptorYaml { get; set; }
[YamlMember(Alias = "extensions")]
public object? YamlExtensions { get; set; }
public class YamlEnv
{
[YamlMember(Alias = "name")]
@ -640,26 +638,32 @@ namespace WinSW.Configuration
public string LogMode => this.Log.Mode is null ? this.Defaults.LogMode : this.Log.Mode;
public object? ExtensionsConfiguration => this.YamlExtensions;
// TODO - Extensions
public XmlNode? ExtensionsConfiguration => null;
// YAML Extension
[YamlMember(Alias = "extensions")]
public object? YamlExtensionsConfiguration { get; set; }
public List<string> ExtensionIds
{
get
{
var result = new List<string>(0);
var extensions = this.ExtensionsConfiguration;
var result = new List<string>();
if (extensions is null)
if (!(this.YamlExtensionsConfiguration is List<object> extensions))
{
return result;
}
var extensionConfigListObject = new ObjectQuery(extensions).AsList<object>();
foreach (var item in extensionConfigListObject)
foreach (var item in extensions)
{
var configObject = new ObjectQuery(item);
var id = configObject.On("id").AsString();
if (!(item is Dictionary<object, object> dict))
{
continue;
}
var id = (string)dict["id"];
result.Add(id);
}

View File

@ -1,5 +1,5 @@
using WinSW.Configuration;
using WinSW.Util;
using System.Xml;
using WinSW.Configuration;
namespace WinSW.Extensions
{
@ -11,7 +11,12 @@ namespace WinSW.Extensions
public WinSWExtensionDescriptor Descriptor { get; set; }
#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
public virtual void Configure(IWinSWConfiguration descriptor, ObjectQuery settings)
public virtual void Configure(IWinSWConfiguration descriptor, XmlNode node)
{
// Do nothing
}
public virtual void Configure(IWinSWConfiguration descriptor, object yamlObject)
{
// Do nothing
}

View File

@ -1,61 +0,0 @@
using System.Collections.Generic;
using WinSW.Configuration;
using WinSW.Util;
namespace WinSW.Extensions
{
public class ExtensionConfigurationProvider
{
private readonly IWinSWConfiguration serviceDescriptor;
private readonly List<WinSWExtensionConfiguration> extensionConfigList;
public ExtensionConfigurationProvider(IWinSWConfiguration serviceConfigs)
{
this.serviceDescriptor = serviceConfigs;
this.extensionConfigList = this.CreateExtensionConfigList();
}
public WinSWExtensionConfiguration? GetExtenstionConfiguration(string id)
{
foreach (var item in this.extensionConfigList)
{
if (item.Id.Equals(id))
{
return item;
}
}
return null;
}
private List<WinSWExtensionConfiguration> CreateExtensionConfigList()
{
var result = new List<WinSWExtensionConfiguration>(0);
var extensions = this.serviceDescriptor.ExtensionsConfiguration;
if (extensions is null)
{
return result;
}
var extensionNodes = new ObjectQuery(extensions).AsList<object>();
foreach (var extension in extensionNodes)
{
var query = new ObjectQuery(extension);
var id = query.On("id").AsString();
var enabled = query.On("enabled").AsBool();
var className = query.On("classname").AsString();
var settings = query.On("settings").AsParent();
var extensionConfig = new WinSWExtensionConfiguration(id, enabled, className, settings);
result.Add(extensionConfig);
}
return result;
}
}
}

View File

@ -1,5 +1,5 @@
using WinSW.Configuration;
using WinSW.Util;
using System.Xml;
using WinSW.Configuration;
namespace WinSW.Extensions
{
@ -27,7 +27,15 @@ namespace WinSW.Extensions
/// Init handler. Extension should load it's config during that step
/// </summary>
/// <param name="descriptor">Service descriptor</param>
void Configure(IWinSWConfiguration descriptor, ObjectQuery settings);
/// <param name="node">Configuration node</param>
void Configure(IWinSWConfiguration descriptor, XmlNode node);
/// <summary>
/// Configure the extension from Yaml configuration
/// </summary>
/// <param name="descriptor">Yaml Service Descptor</param>
/// <param name="yamlObject">Configuration Node</param>
void Configure(IWinSWConfiguration descriptor, object yamlObject);
/// <summary>
/// Start handler. Called during startup of the service before the child process.

View File

@ -1,23 +0,0 @@
using WinSW.Util;
namespace WinSW.Extensions
{
public class WinSWExtensionConfiguration
{
public string Id { get; set; }
public bool Enabled { get; set; }
public string ClassName { get; set; }
public ObjectQuery Settings { get; set; }
public WinSWExtensionConfiguration(string id, bool enabled, string className, ObjectQuery settings)
{
this.Id = id;
this.Enabled = enabled;
this.ClassName = className;
this.Settings = settings;
}
}
}

View File

@ -1,4 +1,7 @@
using System.Xml;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Xml;
using WinSW.Util;
namespace WinSW.Extensions
@ -26,7 +29,7 @@ namespace WinSW.Extensions
/// </summary>
public string ClassName { get; private set; }
public WinSWExtensionDescriptor(string id, string className, bool enabled)
private WinSWExtensionDescriptor(string id, string className, bool enabled)
{
this.Id = id;
this.Enabled = enabled;
@ -40,5 +43,19 @@ namespace WinSW.Extensions
string id = XmlHelper.SingleAttribute<string>(node, "id");
return new WinSWExtensionDescriptor(id, className, enabled);
}
public static WinSWExtensionDescriptor FromYaml(object node)
{
if (!(node is Dictionary<object, object> config))
{
throw new InvalidDataException("Cannot get the configuration entry");
}
bool enabled = ConfigHelper.YamlBoolParse((string)config["enabled"]);
string className = (string)config["classname"];
string id = (string)config["id"];
return new WinSWExtensionDescriptor(id, className, enabled);
}
}
}

View File

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using log4net;
using WinSW.Configuration;
@ -11,15 +13,12 @@ namespace WinSW.Extensions
public IWinSWConfiguration ServiceDescriptor { get; private set; }
public ExtensionConfigurationProvider ConfigProvider { get; private set; }
private static readonly ILog Log = LogManager.GetLogger(typeof(WinSWExtensionManager));
public WinSWExtensionManager(IWinSWConfiguration serviceDescriptor)
{
this.ServiceDescriptor = serviceDescriptor;
this.Extensions = new Dictionary<string, IWinSWExtension>();
this.ConfigProvider = new ExtensionConfigurationProvider(this.ServiceDescriptor);
}
/// <summary>
@ -129,21 +128,34 @@ namespace WinSW.Extensions
throw new ExtensionException(id, "Extension has been already loaded");
}
var configNode = this.ConfigProvider.GetExtenstionConfiguration(id);
if (this.ServiceDescriptor.GetType() == typeof(ServiceDescriptor))
{
this.LoadExtensionFromXml(id);
}
else
{
this.LoadExtensionFromYaml(id);
}
}
private void LoadExtensionFromXml(string id)
{
XmlNode? extensionsConfig = this.ServiceDescriptor.ExtensionsConfiguration;
XmlElement? configNode = extensionsConfig is null ? null : extensionsConfig.SelectSingleNode("extension[@id='" + id + "'][1]") as XmlElement;
if (configNode is null)
{
throw new ExtensionException(id, "Cannot get the configuration entry");
}
var descriptor = new WinSWExtensionDescriptor(configNode.Id, configNode.ClassName, configNode.Enabled);
var descriptor = WinSWExtensionDescriptor.FromXml(configNode);
if (descriptor.Enabled)
{
IWinSWExtension extension = this.CreateExtensionInstance(descriptor.Id, descriptor.ClassName);
extension.Descriptor = descriptor;
try
{
extension.Configure(this.ServiceDescriptor, configNode.Settings);
extension.Configure(this.ServiceDescriptor, configNode);
}
catch (Exception ex)
{ // Consider any unexpected exception as fatal
@ -160,6 +172,67 @@ namespace WinSW.Extensions
}
}
private void LoadExtensionFromYaml(string id)
{
var extensionConfigList = this.ServiceDescriptor.YamlExtensionsConfiguration as List<object>;
if (extensionConfigList is null)
{
throw new ExtensionException(id, "Cannot get the configuration entry");
}
object? configNode = GetYamlonfigById(extensionConfigList, id);
if (configNode is null)
{
throw new ExtensionException(id, "Cannot get the configuration entry");
}
var descriptor = WinSWExtensionDescriptor.FromYaml(configNode);
if (descriptor.Enabled)
{
IWinSWExtension extension = this.CreateExtensionInstance(descriptor.Id, descriptor.ClassName);
extension.Descriptor = descriptor;
if (!(configNode is Dictionary<object, object> dict))
{
// TODO : Replace with exntension exception
throw new InvalidDataException("Error in config node");
}
try
{
extension.Configure(this.ServiceDescriptor, dict["settings"]);
}
catch (Exception ex)
{ // Consider any unexpected exception as fatal
Log.Fatal("Failed to configure the extension " + id, ex);
throw ex;
}
this.Extensions.Add(id, extension);
Log.Info("Extension loaded: " + id);
}
else
{
Log.Warn("Extension is disabled: " + id);
}
object? GetYamlonfigById(List<object> configs, string id)
{
foreach (var item in configs)
{
if (item is Dictionary<object, object> config && config["id"].Equals(id))
{
return item;
}
}
return null;
}
}
private IWinSWExtension CreateExtensionInstance(string id, string className)
{
object created;

View File

@ -208,7 +208,7 @@ namespace WinSW
}
}
/*public List<string> ExtensionIds
public List<string> ExtensionIds
{
get
{
@ -227,18 +227,9 @@ namespace WinSW
return result;
}
}*/
public List<string> ExtensionIds
{
get
{
return new List<string>(0);
}
}
// public XmlNode? ExtensionsConfiguration => this.dom.SelectSingleNode("//extensions");
public object? ExtensionsConfiguration { get; set; }
public XmlNode? ExtensionsConfiguration => this.dom.SelectSingleNode("//extensions");
/// <summary>
/// Combines the contents of all the elements of the given name,
@ -712,5 +703,7 @@ namespace WinSW
return environment;
}
public object? YamlExtensionsConfiguration => null;
}
}

View File

@ -33,5 +33,17 @@ namespace WinSW.Util
{ "day", 1000L * 60L * 60L * 24L },
{ "days", 1000L * 60L * 60L * 24L }
};
public static bool YamlBoolParse(string value)
{
value = value.ToLower();
if (value.Equals("true") || value.Equals("yes") || value.Equals("on"))
{
return true;
}
return false;
}
}
}

View File

@ -1,165 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace WinSW.Util
{
public class ObjectQuery
{
private readonly object configObject;
private object? current;
private string? key;
public ObjectQuery(object? config)
{
if (config is null)
{
throw new InvalidDataException("Query object is null");
}
this.configObject = config;
this.current = this.configObject;
}
public ObjectQuery On(string key)
{
this.key = key;
this.current = this.Query(this.configObject, key);
return this;
}
public ObjectQuery Get(string key)
{
if (this.current == null)
{
throw new InvalidDataException("The key <" + key + "> is not exist");
}
this.key = key;
this.current = this.Query(this.current, key);
return this;
}
public string AsString()
{
if (this.current == null)
{
throw new InvalidDataException("The key <" + this.key + "> is not exist");
}
var result = this.current as string;
if (result == null)
{
throw new InvalidDataException(this.key + " can't converto to a string");
}
return result;
}
public List<T> AsList<T>()
{
if (this.current == null)
{
throw new InvalidDataException("The key <" + this.key + "> is not exist");
}
var list = this.current as List<object>;
if (list == null)
{
throw new InvalidDataException(this.key + " can't converto to List<" + typeof(T) + ">");
}
var result = new List<T>(0);
foreach (var item in list)
{
result.Add((T)item);
}
return result;
}
public bool AsBool()
{
if (this.current == null)
{
throw new InvalidDataException("The key <" + this.key + "> is not exist");
}
var value = this.current as string;
if (value == null)
{
throw new InvalidDataException(this.key + " can't convert into bool");
}
if (value == "true" || value == "yes" || value == "on")
{
return true;
}
else if (value == "false" || value == "no" || value == "off")
{
return false;
}
else
{
throw new InvalidDataException(value + " cannot convert into bool");
}
}
public ObjectQuery At(int index)
{
if (this.current == null)
{
throw new InvalidDataException("The key <" + this.key + "> is not exist");
}
var list = this.current as List<object>;
if (list == null)
{
throw new InvalidDataException("Can't execute At(index) on " + this.key);
}
try
{
var result = list[index];
this.current = result;
}
catch (IndexOutOfRangeException)
{
throw new InvalidDataException("Index " + index + " not in range");
}
return this;
}
private object? Query(object? dic, string key)
{
if (dic == null)
{
throw new InvalidDataException(key + " is not found");
}
var dict = dic as IDictionary<object, object>;
if (dict != null)
{
foreach (KeyValuePair<object, object> kvp in dict)
{
if (kvp.Key as string == key)
{
return kvp.Value;
}
}
}
return null;
}
public ObjectQuery AsParent()
{
return new ObjectQuery(this.current);
}
}
}

View File

@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Xml;
using log4net;
using WinSW.Configuration;
using WinSW.Extensions;
@ -179,20 +181,39 @@ namespace WinSW.Plugins.RunawayProcessKiller
return parameters.Environment;
}
public override void Configure(IWinSWConfiguration descriptor, ObjectQuery settings)
public override void Configure(IWinSWConfiguration descriptor, XmlNode node)
{
this.Pidfile = settings.On("pidfile").AsString();
this.StopTimeout = TimeSpan.FromMilliseconds(int.Parse(settings.On("stopTimeOut").AsString()));
this.StopParentProcessFirst = settings.On("StopParentFirst").AsBool();
// We expect the upper logic to process any errors
// TODO: a better parser API for types would be useful
this.Pidfile = XmlHelper.SingleElement(node, "pidfile", false)!;
this.StopTimeout = TimeSpan.FromMilliseconds(int.Parse(XmlHelper.SingleElement(node, "stopTimeout", false)!));
this.StopParentProcessFirst = bool.Parse(XmlHelper.SingleElement(node, "stopParentFirst", false)!);
this.ServiceId = descriptor.Id;
// TODO: Consider making it documented
var checkWinSWEnvironmentVariable = XmlHelper.SingleElement(node, "checkWinSWEnvironmentVariable", true);
this.CheckWinSWEnvironmentVariable = checkWinSWEnvironmentVariable is null ? true : bool.Parse(checkWinSWEnvironmentVariable);
}
public override void Configure(IWinSWConfiguration descriptor, object yamlObject)
{
if (!(yamlObject is Dictionary<object, object> dict))
{
// TODO : throw ExtensionException
throw new InvalidDataException("Cann't configure");
}
this.Pidfile = (string)dict["pidfile"];
this.StopTimeout = TimeSpan.FromMilliseconds(int.Parse((string)dict["stopTimeOut"]));
this.StopParentProcessFirst = bool.Parse((string)dict["StopParentFirst"]);
try
{
this.CheckWinSWEnvironmentVariable = settings.Get("checkWinSWEnvironmentVariable").AsBool();
this.CheckWinSWEnvironmentVariable = bool.Parse((string)dict["checkWinSWEnvironmentVariable"]);
}
catch
{
this.CheckWinSWEnvironmentVariable = true;
}
this.ServiceId = descriptor.Id;
}
/// <summary>

View File

@ -1,4 +1,6 @@
using System.Collections.Generic;
using System.IO;
using System.Xml;
using log4net;
using WinSW.Configuration;
using WinSW.Extensions;
@ -26,18 +28,41 @@ namespace WinSW.Plugins.SharedDirectoryMapper
this._entries.Add(config);
}
public override void Configure(IWinSWConfiguration descriptor, ObjectQuery settings)
public override void Configure(IWinSWConfiguration descriptor, XmlNode node)
{
var maps = settings.On("mapping").AsList<object>();
foreach (var map in maps)
XmlNodeList? mapNodes = XmlHelper.SingleNode(node, "mapping", false)!.SelectNodes("map");
if (mapNodes != null)
{
var mapObject = new ObjectQuery(map);
var enable = mapObject.On("enabled").AsBool();
var label = mapObject.On("label").AsString();
var uncpath = mapObject.On("uncpath").AsString();
for (int i = 0; i < mapNodes.Count; i++)
{
if (mapNodes[i] is XmlElement mapElement)
{
var config = SharedDirectoryMapperConfig.FromXml(mapElement);
this._entries.Add(config);
}
}
}
}
var config = new SharedDirectoryMapperConfig(enable, label, uncpath);
public override void Configure(IWinSWConfiguration descriptor, object yamlObject)
{
if (!(yamlObject is Dictionary<object, object> dict))
{
// TODO : throw ExtensionException
throw new InvalidDataException("Conn't configure");
}
var mappingNode = dict["mapping"];
if (!(mappingNode is List<object> mappings))
{
// TODO : throw ExtensionException
throw new InvalidDataException("Conn't configure");
}
foreach (var map in mappings)
{
var config = SharedDirectoryMapperConfig.FromYaml(map);
this._entries.Add(config);
}
}

View File

@ -1,4 +1,6 @@
using System.Xml;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using WinSW.Util;
namespace WinSW.Plugins.SharedDirectoryMapper
@ -26,5 +28,20 @@ namespace WinSW.Plugins.SharedDirectoryMapper
string uncPath = XmlHelper.SingleAttribute<string>(node, "uncpath");
return new SharedDirectoryMapperConfig(enableMapping, label, uncPath);
}
public static SharedDirectoryMapperConfig FromYaml(object yamlObject)
{
if (!(yamlObject is Dictionary<object, object> dict))
{
// TODO : throw ExtensionExeption
throw new InvalidDataException("SharedDirectoryMapperConfig config error");
}
bool enableMapping = ConfigHelper.YamlBoolParse((string)dict["enabled"]);
string label = (string)dict["label"];
string uncPath = (string)dict["uncpath"];
return new SharedDirectoryMapperConfig(enableMapping, label, uncPath);
}
}
}

View File

@ -4,6 +4,7 @@ using System.Diagnostics;
using System.IO;
using NUnit.Framework;
using WinSW;
using WinSW.Configuration;
using WinSW.Extensions;
using WinSW.Plugins.RunawayProcessKiller;
using WinSW.Util;
@ -14,7 +15,8 @@ namespace winswTests.Extensions
[TestFixture]
class RunawayProcessKillerExtensionTest : ExtensionTestBase
{
ServiceDescriptor _testServiceDescriptor;
IWinSWConfiguration _testServiceDescriptor;
IWinSWConfiguration _testServiceDescriptorYaml;
readonly string testExtension = GetExtensionClassNameWithAssembly(typeof(RunawayProcessKillerExtension));
@ -38,6 +40,29 @@ $@"<service>
</extensions>
</service>";
this._testServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
string seedYaml = $@"---
id: jenkins
name: Jenkins
description: This service runs Jenkins automation server.
env:
-
name: JENKINS_HOME
value: '%LocalAppData%\Jenkins.jenkins'
executable: java
arguments: >-
-Xrs -Xmx256m -Dhudson.lifecycle=hudson.lifecycle.WindowsServiceLifecycle
-jar E:\Winsw Test\yml6\jenkins.war --httpPort=8081
extensions:
- id: killRunawayProcess
enabled: yes
classname: ""{this.testExtension}""
settings:
pidfile: 'foo/bar/pid.txt'
stopTimeOut: 5000
StopParentFirst: true";
this._testServiceDescriptorYaml = ServiceDescriptorYaml.FromYaml(seedYaml).Configurations;
}
[Test]
@ -55,6 +80,21 @@ $@"<service>
Assert.AreEqual(true, extension.StopParentProcessFirst, "Loaded StopParentFirst is not equal to the expected one");
}
[Test]
public void LoadExtensionsYaml()
{
WinSWExtensionManager manager = new WinSWExtensionManager(this._testServiceDescriptorYaml);
manager.LoadExtensions();
Assert.AreEqual(1, manager.Extensions.Count, "One extension should be loaded");
// Check the file is correct
var extension = manager.Extensions["killRunawayProcess"] as RunawayProcessKillerExtension;
Assert.IsNotNull(extension, "RunawayProcessKillerExtension should be loaded");
Assert.AreEqual("foo/bar/pid.txt", extension.Pidfile, "Loaded PID file path is not equal to the expected one");
Assert.AreEqual(5000, extension.StopTimeout.TotalMilliseconds, "Loaded Stop Timeout is not equal to the expected one");
Assert.AreEqual(true, extension.StopParentProcessFirst, "Loaded StopParentFirst is not equal to the expected one");
}
[Test]
public void StartStopExtension()
{

View File

@ -1,70 +0,0 @@
using NUnit.Framework;
using WinSW;
using WinSW.Configuration;
using WinSW.Extensions;
using WinSW.Plugins.RunawayProcessKiller;
using WinSW.Util;
using winswTests.Extensions;
namespace winswTests
{
public class YamlExtensionTest : ExtensionTestBase
{
private IWinSWConfiguration _testServiceDescriptor { get; set; }
readonly string testExtension = GetExtensionClassNameWithAssembly(typeof(RunawayProcessKillerExtension));
[SetUp]
public void SetUp()
{
string yamlDoc = $@"id: SERVICE_NAME
name: Jenkins Slave
description: This service runs Jenkins automation server.
executable: java
arguments: -Xrs -jar \""%BASE%\slave.jar\"" -jnlpUrl ...
extensions:
- id: killOnStartup
enabled: yes
classname: ""{this.testExtension}""
settings:
pidfile: pid.txt
stopTimeOut: 5000
StopParentFirst: true";
this._testServiceDescriptor = ServiceDescriptorYaml.FromYaml(yamlDoc).Configurations;
}
[Test]
public void LoadExtensions()
{
WinSWExtensionManager manager = new WinSWExtensionManager(this._testServiceDescriptor);
manager.LoadExtensions();
Assert.AreEqual(1, manager.Extensions.Count, "One extension should be loaded");
// Check the file is correct
var extension = manager.Extensions["killRunawayProcess"] as RunawayProcessKillerExtension;
Assert.IsNotNull(extension, "RunawayProcessKillerExtension should be loaded");
Assert.AreEqual("foo/bar/pid.txt", extension.Pidfile, "Loaded PID file path is not equal to the expected one");
Assert.AreEqual(5000, extension.StopTimeout.TotalMilliseconds, "Loaded Stop Timeout is not equal to the expected one");
Assert.AreEqual(true, extension.StopParentProcessFirst, "Loaded StopParentFirst is not equal to the expected one");
}
[Test]
public void Sample_test()
{
ExtensionConfigurationProvider provider = new ExtensionConfigurationProvider(this._testServiceDescriptor);
var config = provider.GetExtenstionConfiguration("killOnStartup");
var pid = config.Settings.On("pidfile").AsString();
var stopTimeOut = config.Settings.On("stopTimeOut").AsString();
var StopParentFirst = config.Settings.On("StopParentFirst").AsString();
System.Console.WriteLine(pid);
System.Console.WriteLine(stopTimeOut);
System.Console.WriteLine(StopParentFirst);
}
}
}