YAML Configurations Support (GSoC - 2020) (#543)

* Add ServiceDesciptorYaml.cs

* Add YamlConfigurations.cs
YmlDotNet library added

* Update YamlConfigurations.cs

* Add download configuration to YamlConfiguration

* Revert "Add download configuration to YamlConfiguration"

This reverts commit f150de13b0.

* Add  ServiceDescriptrYaml unit test
Update YamlConfigurations.

* Confid files seperated
Download class refactored
Unit test updated

* Change nullable attributes.

* Refactor IWinSWConfiguration and Logging settings propagation

* Update YamlConfigurations.cs

* Update configuration FailureActions

* Update Yaml Confifurations

* Update YAML Configuration

* Update yaml configurations

* Update Yaml Configurations

* Yaml Configuration refactored

* Refactored YamlConfigurations

* Update serviceaccount and log configs

* YAML_SC_ACTION method name channged

* Refacored Download class. Field names changed to PascalCase readonly

* Add seperate download class to YamlConfigurations and create and return List<Download>

* Created DefaultWinSWSettings singleton

* Refactor variable name

* Update StopExecutable

* Nullable references updated

* Null references updated

* Add sanity checks for yaml deserializing.

* Implement Log Defaults

* Call logdefaults in YAMLConfigurations

* Update defaults value of ServiceAccout

If serviceaccoutn is not specified default ServiceAccount object will be provided from the Defautls.

* Merge build.yml with master

* Update YamlSeriviceDescriptor

Remove invalid Name field from ServiceAccout
Add BaseName logics to defults
merge build.yml with master

* Update IWinSWConfiguration Support

Now can use any IWinSWConfiguration type instead of ServiceDescriptor. We can use both ServiceDescriptor or ServiceDescriptorYaml.

* Update LogMode unit test

* ServiceAccount configurations refactored

Get all ServiceAccount configuration into a single ServiceAccout class.

* Update default BasePath

* Resolve Merge conflicts

* Resolve Merge Conflicts

* Update YamlDownload configs

* Fix null reference issue in arguments

* Update ServiceAccount configs in XML ServiceDescriptor

* remove BOM header

* Update environment variable configurations

Co-authored-by: Oleg Nenashev <o.v.nenashev@gmail.com>
pull/590/head
Buddhika Chathuranga 2020-07-21 22:27:27 +05:30 committed by GitHub
parent ae10abe857
commit 10d3a6113f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 2213 additions and 1230 deletions

View File

@ -18,9 +18,9 @@ using log4net.Appender;
using log4net.Config;
using log4net.Core;
using log4net.Layout;
using WinSW.Configuration;
using WinSW.Logging;
using WinSW.Native;
using WinSW.Util;
using WMI;
using ServiceType = WMI.ServiceType;
@ -59,7 +59,7 @@ namespace WinSW
}
}
public static void Run(string[] argsArray, ServiceDescriptor? descriptor = null)
public static void Run(string[] argsArray, IWinSWConfiguration? descriptor = null)
{
bool inConsoleMode = argsArray.Length > 0;
@ -181,7 +181,9 @@ namespace WinSW
default:
Console.WriteLine("Unknown command: " + args[0]);
PrintAvailableCommands();
#pragma warning disable S112 // General exceptions should never be thrown
throw new Exception("Unknown command: " + args[0]);
#pragma warning restore S112 // General exceptions should never be thrown
}
void Install()
@ -199,7 +201,9 @@ namespace WinSW
{
Console.WriteLine("Service with id '" + descriptor.Id + "' already exists");
Console.WriteLine("To install the service, delete the existing one or change service Id in the configuration file");
#pragma warning disable S112 // General exceptions should never be thrown
throw new Exception("Installation failure: Service with id '" + descriptor.Id + "' already exists");
#pragma warning restore S112 // General exceptions should never be thrown
}
string? username = null;
@ -222,17 +226,17 @@ namespace WinSW
}
else
{
if (descriptor.HasServiceAccount())
if (descriptor.ServiceAccount.HasServiceAccount())
{
username = descriptor.ServiceAccountUser;
password = descriptor.ServiceAccountPassword;
allowServiceLogonRight = descriptor.AllowServiceAcountLogonRight;
username = descriptor.ServiceAccount.ServiceAccountUser;
password = descriptor.ServiceAccount.ServiceAccountPassword;
allowServiceLogonRight = descriptor.ServiceAccount.AllowServiceAcountLogonRight;
}
}
if (allowServiceLogonRight)
{
Security.AddServiceLogonRight(descriptor.ServiceAccountDomain!, descriptor.ServiceAccountName!);
Security.AddServiceLogonRight(descriptor.ServiceAccount.ServiceAccountDomain!, descriptor.ServiceAccount.ServiceAccountName!);
}
svcs.Create(
@ -319,7 +323,7 @@ namespace WinSW
Log.Fatal("Failed to uninstall the service with id '" + descriptor.Id + "'. WMI Error code is '" + e.ErrorCode + "'");
}
throw e;
throw;
}
}
@ -455,14 +459,18 @@ namespace WinSW
bool result = ProcessApis.CreateProcess(null, descriptor.ExecutablePath + " restart", IntPtr.Zero, IntPtr.Zero, false, ProcessApis.CREATE_NEW_PROCESS_GROUP, IntPtr.Zero, null, default, out _);
if (!result)
{
#pragma warning disable S112 // General exceptions should never be thrown
throw new Exception("Failed to invoke restart: " + Marshal.GetLastWin32Error());
#pragma warning restore S112 // General exceptions should never be thrown
}
}
void Status()
{
Log.Debug("User requested the status of the process with id '" + descriptor.Id + "'");
#pragma warning disable S3358 // Ternary operators should not be nested
Console.WriteLine(svc is null ? "NonExistent" : svc.Started ? "Started" : "Stopped");
#pragma warning restore S3358 // Ternary operators should not be nested
}
void Test()
@ -532,7 +540,7 @@ namespace WinSW
[DoesNotReturn]
private static void ThrowNoSuchService() => throw new WmiException(ReturnValue.NoSuchService);
private static void InitLoggers(ServiceDescriptor descriptor, bool enableConsoleLogging)
private static void InitLoggers(IWinSWConfiguration descriptor, bool enableConsoleLogging)
{
// TODO: Make logging levels configurable
Level fileLogLevel = Level.Debug;

View File

@ -10,6 +10,7 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks;
#endif
using log4net;
using WinSW.Configuration;
using WinSW.Extensions;
using WinSW.Logging;
using WinSW.Native;
@ -20,9 +21,11 @@ namespace WinSW
public class WrapperService : ServiceBase, IEventLogger
{
private ServiceApis.SERVICE_STATUS wrapperServiceStatus;
private readonly Process process = new Process();
private readonly ServiceDescriptor descriptor;
private readonly IWinSWConfiguration descriptor;
private Dictionary<string, string>? envs;
internal WinSWExtensionManager ExtensionManager { get; private set; }
@ -55,7 +58,7 @@ namespace WinSW
/// </summary>
public bool IsShuttingDown => this.systemShuttingdown;
public WrapperService(ServiceDescriptor descriptor)
public WrapperService(IWinSWConfiguration descriptor)
{
this.descriptor = descriptor;
this.ServiceName = this.descriptor.Id;
@ -131,14 +134,15 @@ namespace WinSW
/// <returns>Log Handler, which should be used for the spawned process</returns>
private LogHandler CreateExecutableLogHandler()
{
string logDirectory = this.descriptor.LogDirectory;
string? logDirectory = this.descriptor.LogDirectory;
if (!Directory.Exists(logDirectory))
{
Directory.CreateDirectory(logDirectory);
}
LogHandler logAppender = this.descriptor.LogHandler;
LogHandler logAppender = this.descriptor.Log.CreateLogHandler();
logAppender.EventLogger = this;
return logAppender;
}

View File

@ -12,6 +12,8 @@ namespace WinSW.Configuration
/// </summary>
public sealed class DefaultWinSWSettings : IWinSWConfiguration
{
public static LogDefaults DefaultLogSettings { get; } = new LogDefaults();
public string Id => throw new InvalidOperationException(nameof(this.Id) + " must be specified.");
public string Caption => throw new InvalidOperationException(nameof(this.Caption) + " must be specified.");
@ -25,12 +27,6 @@ namespace WinSW.Configuration
public string ExecutablePath => Process.GetCurrentProcess().MainModule.FileName;
// Installation
public bool AllowServiceAcountLogonRight => false;
public string? ServiceAccountPassword => null;
public string? ServiceAccountUser => null;
public Native.SC_ACTION[] FailureActions => new Native.SC_ACTION[0];
public TimeSpan ResetFailureAfter => TimeSpan.FromDays(1);
@ -66,17 +62,67 @@ namespace WinSW.Configuration
public bool Interactive => false;
// Logging
public string LogDirectory => Path.GetDirectoryName(this.ExecutablePath)!;
public Log Log { get => new LogDefaults(); }
public string LogMode => "append";
public string LogDirectory => DefaultLogSettings.Directory;
public bool OutFileDisabled => false;
public string LogMode => DefaultLogSettings.Mode;
public bool ErrFileDisabled => false;
public bool OutFileDisabled => this.Log.OutFileDisabled;
public string OutFilePattern => ".out.log";
public bool ErrFileDisabled => this.Log.ErrFileDisabled;
public string ErrFilePattern => ".err.log";
public string OutFilePattern => this.Log.OutFilePattern;
public string ErrFilePattern => this.Log.ErrFilePattern;
public ServiceAccount ServiceAccount => new ServiceAccount()
{
ServiceAccountName = null,
ServiceAccountDomain = null,
ServiceAccountPassword = null,
AllowServiceAcountLogonRight = false
};
public class LogDefaults : Log
{
private readonly DefaultWinSWSettings defaults;
public LogDefaults()
{
this.defaults = new DefaultWinSWSettings();
}
public override string Mode => "append";
public override string Name => this.defaults.BaseName;
public override string Directory => Path.GetDirectoryName(this.defaults.ExecutablePath)!;
public override int? SizeThreshold => 1024 * 10 * RollingSizeTimeLogAppender.BytesPerKB;
public override int? KeepFiles => SizeBasedRollingLogAppender.DefaultFilesToKeep;
public override string Pattern =>
throw new InvalidDataException("Time Based rolling policy is specified but no pattern can be found in configuration XML.");
public override int? Period => 1;
public override bool OutFileDisabled { get => false; }
public override bool ErrFileDisabled { get => false; }
public override string OutFilePattern { get => ".out.log"; }
public override string ErrFilePattern { get => ".err.log"; }
public override string? AutoRollAtTime => null;
public override int? ZipOlderThanNumDays =>
throw new InvalidDataException("Roll-Size-Time Based rolling policy is specified but zipOlderThanNumDays does not match the int format found in configuration XML.");
public override string? ZipDateFormat => null;
}
// Environment
public List<Download> Downloads => new List<Download>(0);
@ -88,5 +134,32 @@ namespace WinSW.Configuration
// Extensions
public XmlNode? ExtensionsConfiguration => null;
public string BaseName
{
get
{
string baseName = Path.GetFileNameWithoutExtension(this.ExecutablePath);
if (baseName.EndsWith(".vshost"))
{
baseName = baseName.Substring(0, baseName.Length - 7);
}
return baseName;
}
}
public string BasePath
{
get
{
var d = new DirectoryInfo(Path.GetDirectoryName(this.ExecutablePath));
return Path.Combine(d.FullName, this.BaseName);
}
}
public List<string> ExtensionIds => new List<string>(0);
public string? SecurityDescriptor => null;
}
}

View File

@ -22,12 +22,6 @@ namespace WinSW.Configuration
bool HideWindow { get; }
// Installation
bool AllowServiceAcountLogonRight { get; }
string? ServiceAccountPassword { get; }
string? ServiceAccountUser { get; }
Native.SC_ACTION[] FailureActions { get; }
TimeSpan ResetFailureAfter { get; }
@ -60,12 +54,17 @@ namespace WinSW.Configuration
bool Interactive { get; }
// Logging
/// <summary>
/// Destination for logging.
/// If undefined, a default one should be used.
/// </summary>
string LogDirectory { get; }
// TODO: replace by enum
string LogMode { get; }
Log Log { get; }
// Environment
List<Download> Downloads { get; }
@ -76,5 +75,19 @@ namespace WinSW.Configuration
// Extensions
XmlNode? ExtensionsConfiguration { get; }
// IWinSWConfiguration Support
List<string> ExtensionIds { get; }
// Service Account
ServiceAccount ServiceAccount { get; }
string BaseName { get; }
string BasePath { get; }
bool DelayedAutoStart { get; }
string? SecurityDescriptor { get; }
}
}

View File

@ -0,0 +1,107 @@
using System;
using System.IO;
namespace WinSW.Configuration
{
public abstract class Log
{
public abstract string? Mode { get; }
public abstract string Name { get; }
public abstract string Directory { get; }
public abstract int? SizeThreshold { get; }
public abstract int? KeepFiles { get; }
public abstract string Pattern { get; }
public abstract int? Period { get; }
// Filters
public abstract bool OutFileDisabled { get; }
public abstract bool ErrFileDisabled { get; }
public abstract string OutFilePattern { get; }
public abstract string ErrFilePattern { get; }
// Zip options
public abstract string? AutoRollAtTime { get; }
public abstract int? ZipOlderThanNumDays { get; }
public abstract string? ZipDateFormat { get; }
public LogHandler CreateLogHandler()
{
switch (this.Mode)
{
case "rotate":
return new SizeBasedRollingLogAppender(this.Directory, this.Name, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern);
case "none":
return new IgnoreLogAppender();
case "reset":
return new ResetLogAppender(this.Directory, this.Name, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern);
case "roll":
return new RollingLogAppender(this.Directory, this.Name, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern);
case "roll-by-time":
return new TimeBasedRollingLogAppender(this.Directory, this.Name, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern, this.Pattern, this.Period.GetValueOrDefault(1));
case "roll-by-size":
return new SizeBasedRollingLogAppender(
this.Directory,
this.Name,
this.OutFileDisabled,
this.ErrFileDisabled,
this.OutFilePattern,
this.ErrFilePattern,
this.SizeThreshold.GetValueOrDefault(10 * 1024) * SizeBasedRollingLogAppender.BytesPerKB,
this.KeepFiles.GetValueOrDefault(SizeBasedRollingLogAppender.DefaultFilesToKeep));
case "append":
return new DefaultLogAppender(this.Directory, this.Name, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern);
case "roll-by-size-time":
if (this.Pattern is null)
{
throw new InvalidDataException("Roll-Size-Time Based rolling policy is specified but no pattern can be found in configuration");
}
TimeSpan? autoRollAtTime = null;
if (this.AutoRollAtTime != null)
{
// validate it
if (!TimeSpan.TryParse(this.AutoRollAtTime, out TimeSpan autoRollAtTimeValue))
{
throw new InvalidDataException("Roll-Size-Time Based rolling policy is specified but autoRollAtTime does not match the TimeSpan format HH:mm:ss found in configuration XML.");
}
autoRollAtTime = autoRollAtTimeValue;
}
return new RollingSizeTimeLogAppender(
this.Directory,
this.Name,
this.OutFileDisabled,
this.ErrFileDisabled,
this.OutFilePattern,
this.ErrFilePattern,
this.SizeThreshold.GetValueOrDefault(10 * 1024) * SizeBasedRollingLogAppender.BytesPerKB,
this.Pattern,
autoRollAtTime,
this.ZipOlderThanNumDays,
this.ZipDateFormat != null ? this.ZipDateFormat : "yyyyMM");
default:
throw new InvalidDataException("Undefined logging mode: " + this.Mode);
}
}
}
}

View File

@ -0,0 +1,29 @@
using YamlDotNet.Serialization;
namespace WinSW.Configuration
{
public class ServiceAccount
{
[YamlMember(Alias = "user")]
public string? ServiceAccountName { get; set; }
[YamlMember(Alias = "domain")]
public string? ServiceAccountDomain { get; set; }
[YamlMember(Alias = "Password")]
public string? ServiceAccountPassword { get; set; }
[YamlMember(Alias = "allowservicelogon")]
public bool AllowServiceAcountLogonRight { get; set; }
public string? ServiceAccountUser
{
get => this.ServiceAccountName is null ? null : (this.ServiceAccountDomain ?? ".") + "\\" + this.ServiceAccountName;
}
public bool HasServiceAccount()
{
return !string.IsNullOrEmpty(this.ServiceAccountName);
}
}
}

View File

@ -0,0 +1,514 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Xml;
using WinSW.Native;
using WMI;
using YamlDotNet.Serialization;
using static WinSW.Download;
namespace WinSW.Configuration
{
public class YamlConfiguration : IWinSWConfiguration
{
public DefaultWinSWSettings Defaults { get; } = new DefaultWinSWSettings();
[YamlMember(Alias = "id")]
public string? IdYaml { get; set; }
[YamlMember(Alias = "name")]
public string? NameYaml { get; set; }
[YamlMember(Alias = "description")]
public string? DescriptionYaml { get; set; }
[YamlMember(Alias = "executable")]
public string? ExecutableYaml { get; set; }
[YamlMember(Alias = "executablePath")]
public string? ExecutablePathYaml { get; set; }
[YamlMember(Alias = "caption")]
public string? CaptionYaml { get; set; }
[YamlMember(Alias = "hideWindow")]
public bool? HideWindowYaml { get; set; }
[YamlMember(Alias = "workingdirectory")]
public string? WorkingDirectoryYaml { get; set; }
[YamlMember(Alias = "serviceaccount")]
public ServiceAccount? ServiceAccountYaml { get; set; }
[YamlMember(Alias = "log")]
public YamlLog? YAMLLog { get; set; }
[YamlMember(Alias = "download")]
public List<YamlDownload>? DownloadsYaml { get; set; }
[YamlMember(Alias = "arguments")]
public string? ArgumentsYaml { get; set; }
[YamlMember(Alias = "startArguments")]
public string? StartArgumentsYaml { get; set; }
[YamlMember(Alias = "stopArguments")]
public string? StopArgumentsYaml { get; set; }
[YamlMember(Alias = "stopExecutable")]
public string? StopExecutableYaml { get; set; }
[YamlMember(Alias = "stopParentProcessFirst")]
public bool? StopParentProcessFirstYaml { get; set; }
[YamlMember(Alias = "resetFailureAfter")]
public TimeSpan? ResetFailureAfterYaml { get; set; }
[YamlMember(Alias = "stopTimeout")]
public TimeSpan? StopTimeoutYaml { get; set; }
[YamlMember(Alias = "startMode")]
public StartMode? StartModeYaml { get; set; }
[YamlMember(Alias = "serviceDependencies")]
public string[]? ServiceDependenciesYaml { get; set; }
[YamlMember(Alias = "waitHint")]
public TimeSpan? WaitHintYaml { get; set; }
[YamlMember(Alias = "sleepTime")]
public TimeSpan? SleepTimeYaml { get; set; }
[YamlMember(Alias = "interactive")]
public bool? InteractiveYaml { get; set; }
[YamlMember(Alias = "priority")]
public ProcessPriorityClass? PriorityYaml { get; set; }
[YamlMember(Alias = "beepOnShutdown")]
public bool BeepOnShutdown { get; set; }
[YamlMember(Alias = "env")]
public Dictionary<string, string>? EnvironmentVariablesYaml { get; set; }
[YamlMember(Alias = "failureActions")]
public List<YamlFailureAction>? YamlFailureActions { get; set; }
[YamlMember(Alias = "delayedAutoStart")]
public bool DelayedAutoStart { get; set; }
[YamlMember(Alias = "securityDescriptor")]
public string? SecurityDescriptorYaml { get; set; }
public class YamlLog : Log
{
private readonly YamlConfiguration configs;
public YamlLog()
{
this.configs = new YamlConfiguration();
}
[YamlMember(Alias = "mode")]
public string? ModeYamlLog { get; set; }
[YamlMember(Alias = "name")]
public string? NameYamlLog { get; set; }
[YamlMember(Alias = "sizeThreshold")]
public int? SizeThresholdYamlLog { get; set; }
[YamlMember(Alias = "keepFiles")]
public int? KeepFilesYamlLog { get; set; }
[YamlMember(Alias = "pattern")]
public string? PatternYamlLog { get; set; }
[YamlMember(Alias = "period")]
public int? PeriodYamlLog { get; set; }
[YamlMember(Alias = "logpath")]
public string? LogPathYamlLog { get; set; }
// Filters
[YamlMember(Alias = "outFileDisabled")]
public bool? OutFileDisabledYamlLog { get; set; }
[YamlMember(Alias = "errFileDisabled")]
public bool? ErrFileDisabledYamlLog { get; set; }
[YamlMember(Alias = "outFilePattern")]
public string? OutFilePatternYamlLog { get; set; }
[YamlMember(Alias = "errFilePattern")]
public string? ErrFilePatternYamlLog { get; set; }
// Zip options
[YamlMember(Alias = "autoRollAtTime")]
public string? AutoRollAtTimeYamlLog { get; set; }
[YamlMember(Alias = "zipOlderThanNumDays")]
public int? ZipOlderThanNumDaysYamlLog { get; set; }
[YamlMember(Alias = "zipDateFormat")]
public string? ZipDateFormatYamlLog { get; set; }
public override string Mode => this.ModeYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.Mode :
this.ModeYamlLog;
public override string Name
{
get
{
return this.NameYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.Name :
Environment.ExpandEnvironmentVariables(this.NameYamlLog);
}
}
public override string Directory
{
get
{
return this.LogPathYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.Directory :
Environment.ExpandEnvironmentVariables(this.LogPathYamlLog);
}
}
public override int? SizeThreshold
{
get
{
return this.SizeThresholdYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.SizeThreshold :
this.SizeThresholdYamlLog * RollingSizeTimeLogAppender.BytesPerKB;
}
}
public override int? KeepFiles
{
get
{
return this.KeepFilesYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.KeepFiles :
this.KeepFilesYamlLog;
}
}
public override string Pattern
{
get
{
if (this.PatternYamlLog != null)
{
return this.PatternYamlLog;
}
return DefaultWinSWSettings.DefaultLogSettings.Pattern;
}
}
public override int? Period => this.PeriodYamlLog is null ? 1 : this.PeriodYamlLog;
public override bool OutFileDisabled
{
get
{
return this.OutFileDisabledYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.OutFileDisabled :
(bool)this.OutFileDisabledYamlLog;
}
}
public override bool ErrFileDisabled
{
get
{
return this.ErrFileDisabledYamlLog is null ?
this.configs.Defaults.ErrFileDisabled :
(bool)this.ErrFileDisabledYamlLog;
}
}
public override string OutFilePattern
{
get
{
return this.OutFilePatternYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.OutFilePattern :
Environment.ExpandEnvironmentVariables(this.OutFilePatternYamlLog);
}
}
public override string ErrFilePattern
{
get
{
return this.ErrFilePatternYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.ErrFilePattern :
Environment.ExpandEnvironmentVariables(this.ErrFilePatternYamlLog);
}
}
public override string? AutoRollAtTime
{
get
{
return this.AutoRollAtTimeYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.AutoRollAtTime :
this.AutoRollAtTimeYamlLog;
}
}
public override int? ZipOlderThanNumDays
{
get
{
if (this.ZipOlderThanNumDaysYamlLog != null)
{
return this.ZipOlderThanNumDaysYamlLog;
}
return DefaultWinSWSettings.DefaultLogSettings.ZipOlderThanNumDays;
}
}
public override string? ZipDateFormat
{
get
{
return this.ZipDateFormatYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.ZipDateFormat :
this.ZipDateFormatYamlLog;
}
}
}
public class YamlDownload
{
[YamlMember(Alias = "from")]
public string FromYamlDownload { get; set; } = string.Empty;
[YamlMember(Alias = "to")]
public string ToYamlDownload { get; set; } = string.Empty;
[YamlMember(Alias = "auth")]
public AuthType AuthYamlDownload { get; set; }
[YamlMember(Alias = "username")]
public string? UsernameYamlDownload { get; set; }
[YamlMember(Alias = "password")]
public string? PasswordYamlDownload { get; set; }
[YamlMember(Alias = "unsecureAuth")]
public bool UnsecureAuthYamlDownload { get; set; }
[YamlMember(Alias = "failOnError")]
public bool FailOnErrorYamlDownload { get; set; }
[YamlMember(Alias = "proxy")]
public string? ProxyYamlDownload { get; set; }
}
public class YamlFailureAction
{
[YamlMember(Alias = "type")]
private SC_ACTION_TYPE type;
[YamlMember(Alias = "delay")]
private TimeSpan delay;
public SC_ACTION_TYPE Type { get => this.type; set => this.type = value; }
public TimeSpan Delay { get => this.delay; set => this.delay = value; }
}
private string? GetArguments(string? args, ArgType type)
{
if (args is null)
{
switch (type)
{
case ArgType.Arg:
return this.Defaults.Arguments;
case ArgType.Startarg:
return this.Defaults.StartArguments;
case ArgType.Stoparg:
return this.Defaults.StopArguments;
default:
return string.Empty;
}
}
return Environment.ExpandEnvironmentVariables(args);
}
private enum ArgType
{
Arg = 0,
Startarg = 1,
Stoparg = 2
}
private List<Download> GetDownloads(List<YamlDownload>? downloads)
{
if (downloads is null)
{
return this.Defaults.Downloads;
}
var result = new List<Download>(downloads.Count);
foreach (var item in downloads)
{
result.Add(new Download(
item.FromYamlDownload,
item.ToYamlDownload,
item.FailOnErrorYamlDownload,
item.AuthYamlDownload,
item.UsernameYamlDownload,
item.PasswordYamlDownload,
item.UnsecureAuthYamlDownload,
item.ProxyYamlDownload));
}
return result;
}
public string Id => this.IdYaml is null ? this.Defaults.Id : this.IdYaml;
public string Description => this.DescriptionYaml is null ? this.Defaults.Description : this.DescriptionYaml;
public string Executable => this.ExecutableYaml is null ? this.Defaults.Executable : this.ExecutableYaml;
public string ExecutablePath => this.ExecutablePathYaml is null ? this.Defaults.ExecutablePath : this.ExecutablePathYaml;
public string Caption => this.CaptionYaml is null ? this.Defaults.Caption : this.CaptionYaml;
public bool HideWindow => this.HideWindowYaml is null ? this.Defaults.HideWindow : (bool)this.HideWindowYaml;
public bool StopParentProcessFirst
{
get
{
return this.StopParentProcessFirstYaml is null ?
this.Defaults.StopParentProcessFirst :
(bool)this.StopParentProcessFirstYaml;
}
}
public StartMode StartMode => this.StartModeYaml is null ? this.Defaults.StartMode : (StartMode)this.StartModeYaml;
public string Arguments
{
get
{
var args = this.GetArguments(this.ArgumentsYaml, ArgType.Arg);
return args is null ? this.Defaults.Arguments : args;
}
}
public string? StartArguments => this.GetArguments(this.StartArgumentsYaml, ArgType.Startarg);
public string? StopArguments => this.GetArguments(this.StopArgumentsYaml, ArgType.Stoparg);
public string? StopExecutable
{
get
{
return this.StopExecutableYaml is null ?
this.Defaults.StopExecutable :
null;
}
}
public SC_ACTION[] FailureActions
{
get
{
if (this.YamlFailureActions is null)
{
return new SC_ACTION[0];
}
var arr = new List<SC_ACTION>();
foreach (var item in this.YamlFailureActions)
{
arr.Add(new SC_ACTION(item.Type, item.Delay));
}
return arr.ToArray();
}
}
public TimeSpan ResetFailureAfter => this.ResetFailureAfterYaml is null ?
this.Defaults.ResetFailureAfter :
(TimeSpan)this.ResetFailureAfterYaml;
public string WorkingDirectory => this.WorkingDirectoryYaml is null ?
this.Defaults.WorkingDirectory :
this.WorkingDirectoryYaml;
public ProcessPriorityClass Priority => this.PriorityYaml is null ? this.Defaults.Priority : (ProcessPriorityClass)this.PriorityYaml;
public TimeSpan StopTimeout => this.StopTimeoutYaml is null ? this.Defaults.StopTimeout : (TimeSpan)this.StopTimeoutYaml;
public string[] ServiceDependencies => this.ServiceDependenciesYaml is null ?
this.Defaults.ServiceDependencies :
this.ServiceDependenciesYaml;
public TimeSpan WaitHint => this.WaitHintYaml is null ? this.Defaults.WaitHint : (TimeSpan)this.WaitHintYaml;
public TimeSpan SleepTime => this.SleepTimeYaml is null ? this.Defaults.SleepTime : (TimeSpan)this.SleepTimeYaml;
public bool Interactive => this.InteractiveYaml is null ? this.Defaults.Interactive : (bool)this.InteractiveYaml;
public List<Download> Downloads => this.GetDownloads(this.DownloadsYaml);
public Dictionary<string, string> EnvironmentVariables { get; set; } = new Dictionary<string, string>();
public void LoadEnvironmentVariables()
{
if (this.EnvironmentVariablesYaml is null)
{
this.EnvironmentVariables = this.Defaults.EnvironmentVariables;
}
else
{
foreach (var item in this.EnvironmentVariablesYaml)
{
var value = Environment.ExpandEnvironmentVariables(item.Value);
this.EnvironmentVariables[item.Key] = value;
Environment.SetEnvironmentVariable(item.Key, value);
}
}
}
public ServiceAccount ServiceAccount => this.ServiceAccountYaml is null ? this.Defaults.ServiceAccount : this.ServiceAccountYaml;
public Log Log => this.YAMLLog is null ? this.Defaults.Log : this.YAMLLog;
public string LogDirectory => this.Log.Directory;
public string LogMode => this.Log.Mode is null ? this.Defaults.LogMode : this.Log.Mode;
// TODO
XmlNode? IWinSWConfiguration.ExtensionsConfiguration => throw new NotImplementedException();
public List<string> ExtensionIds => throw new NotImplementedException();
public string BaseName => throw new NotImplementedException();
public string BasePath => throw new NotImplementedException();
public string? ServiceAccountDomain => throw new NotImplementedException();
public string? ServiceAccountName => throw new NotImplementedException();
public string? SecurityDescriptor => throw new NotImplementedException();
}
}

View File

@ -1,4 +1,5 @@
using System.Xml;
using WinSW.Configuration;
namespace WinSW.Extensions
{
@ -10,7 +11,7 @@ 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(ServiceDescriptor descriptor, XmlNode node)
public virtual void Configure(IWinSWConfiguration descriptor, XmlNode node)
{
// Do nothing
}

View File

@ -1,4 +1,5 @@
using System.Xml;
using WinSW.Configuration;
namespace WinSW.Extensions
{
@ -27,7 +28,7 @@ namespace WinSW.Extensions
/// </summary>
/// <param name="descriptor">Service descriptor</param>
/// <param name="node">Configuration node</param>
void Configure(ServiceDescriptor descriptor, XmlNode node);
void Configure(IWinSWConfiguration descriptor, XmlNode node);
/// <summary>
/// Start handler. Called during startup of the service before the child process.

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Xml;
using log4net;
using WinSW.Configuration;
namespace WinSW.Extensions
{
@ -9,11 +10,11 @@ namespace WinSW.Extensions
{
public Dictionary<string, IWinSWExtension> Extensions { get; private set; }
public ServiceDescriptor ServiceDescriptor { get; private set; }
public IWinSWConfiguration ServiceDescriptor { get; private set; }
private static readonly ILog Log = LogManager.GetLogger(typeof(WinSWExtensionManager));
public WinSWExtensionManager(ServiceDescriptor serviceDescriptor)
public WinSWExtensionManager(IWinSWConfiguration serviceDescriptor)
{
this.ServiceDescriptor = serviceDescriptor;
this.Extensions = new Dictionary<string, IWinSWExtension>();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,82 @@
using System;
using System.IO;
using WinSW.Configuration;
using YamlDotNet.Serialization;
namespace WinSW
{
public class ServiceDescriptorYaml
{
public readonly YamlConfiguration Configurations = new YamlConfiguration();
public static DefaultWinSWSettings Defaults { get; } = new DefaultWinSWSettings();
public string BasePath { get; set; }
public virtual string ExecutablePath => Defaults.ExecutablePath;
public ServiceDescriptorYaml()
{
string p = this.ExecutablePath;
string baseName = Path.GetFileNameWithoutExtension(p);
if (baseName.EndsWith(".vshost"))
{
baseName = baseName.Substring(0, baseName.Length - 7);
}
DirectoryInfo d = new DirectoryInfo(Path.GetDirectoryName(p));
while (true)
{
if (File.Exists(Path.Combine(d.FullName, baseName + ".yml")))
{
break;
}
if (d.Parent is null)
{
throw new FileNotFoundException("Unable to locate " + baseName + ".yml file within executable directory or any parents");
}
d = d.Parent;
}
this.BasePath = Path.Combine(d.FullName, baseName);
using (var reader = new StreamReader(this.BasePath + ".yml"))
{
var file = reader.ReadToEnd();
var deserializer = new DeserializerBuilder().Build();
this.Configurations = deserializer.Deserialize<YamlConfiguration>(file);
}
Environment.SetEnvironmentVariable("BASE", d.FullName);
// ditto for ID
Environment.SetEnvironmentVariable("SERVICE_ID", this.Configurations.Id);
// New name
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameExecutablePath, this.ExecutablePath);
// Also inject system environment variables
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Configurations.Id);
this.Configurations.LoadEnvironmentVariables();
}
#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
public ServiceDescriptorYaml(YamlConfiguration configs)
#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
{
this.Configurations = configs;
this.Configurations.LoadEnvironmentVariables();
}
public static ServiceDescriptorYaml FromYaml(string yaml)
{
var deserializer = new DeserializerBuilder().Build();
var configs = deserializer.Deserialize<YamlConfiguration>(yaml);
return new ServiceDescriptorYaml(configs);
}
}
}

View File

@ -11,6 +11,7 @@
<ItemGroup>
<PackageReference Include="log4net" Version="2.0.8" />
<PackageReference Include="YamlDotNet" Version="8.1.2" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.*">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>

7
src/NuGet.Config Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

View File

@ -5,6 +5,7 @@ using System.IO;
using System.Text;
using System.Xml;
using log4net;
using WinSW.Configuration;
using WinSW.Extensions;
using WinSW.Util;
using static WinSW.Plugins.RunawayProcessKiller.RunawayProcessKillerExtension.NativeMethods;
@ -179,7 +180,7 @@ namespace WinSW.Plugins.RunawayProcessKiller
return parameters.Environment;
}
public override void Configure(ServiceDescriptor descriptor, XmlNode node)
public override void Configure(IWinSWConfiguration descriptor, XmlNode node)
{
// We expect the upper logic to process any errors
// TODO: a better parser API for types would be useful

View File

@ -1,9 +1,11 @@
using System.Collections.Generic;
using System.Xml;
using log4net;
using WinSW.Configuration;
using WinSW.Extensions;
using WinSW.Util;
namespace WinSW.Plugins.SharedDirectoryMapper
{
public class SharedDirectoryMapper : AbstractWinSWExtension
@ -25,7 +27,7 @@ namespace WinSW.Plugins.SharedDirectoryMapper
this._entries.Add(config);
}
public override void Configure(ServiceDescriptor descriptor, XmlNode node)
public override void Configure(IWinSWConfiguration descriptor, XmlNode node)
{
XmlNodeList? mapNodes = XmlHelper.SingleNode(node, "mapping", false)!.SelectNodes("map");
if (mapNodes != null)

View File

@ -1,445 +1,445 @@
using System;
using System.Diagnostics;
using NUnit.Framework;
using WinSW;
using winswTests.Util;
using WMI;
namespace winswTests
{
[TestFixture]
public class ServiceDescriptorTests
{
private ServiceDescriptor _extendedServiceDescriptor;
private const string ExpectedWorkingDirectory = @"Z:\Path\SubPath";
private const string Username = "User";
private const string Password = "Password";
private const string Domain = "Domain";
private const string AllowServiceAccountLogonRight = "true";
[SetUp]
public void SetUp()
{
string seedXml =
$@"<service>
<id>service.exe</id>
<name>Service</name>
<description>The service.</description>
<executable>node.exe</executable>
<arguments>My Arguments</arguments>
<log mode=""roll""></log>
<serviceaccount>
<domain>{Domain}</domain>
<user>{Username}</user>
<password>{Password}</password>
<allowservicelogon>{AllowServiceAccountLogonRight}</allowservicelogon>
</serviceaccount>
<workingdirectory>{ExpectedWorkingDirectory}</workingdirectory>
<logpath>C:\logs</logpath>
</service>";
this._extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
}
[Test]
public void DefaultStartMode()
{
Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(StartMode.Automatic));
}
[Test]
public void IncorrectStartMode()
{
string seedXml =
$@"<service>
<id>service.exe</id>
<name>Service</name>
<description>The service.</description>
<executable>node.exe</executable>
<arguments>My Arguments</arguments>
<startmode>roll</startmode>
<log mode=""roll""></log>
<serviceaccount>
<domain>{Domain}</domain>
<user>{Username}</user>
<password>{Password}</password>
<allowservicelogon>{AllowServiceAccountLogonRight}</allowservicelogon>
</serviceaccount>
<workingdirectory>{ExpectedWorkingDirectory}</workingdirectory>
<logpath>C:\logs</logpath>
</service>";
this._extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(() => this._extendedServiceDescriptor.StartMode, Throws.ArgumentException);
}
[Test]
public void ChangedStartMode()
{
string seedXml =
$@"<service>
<id>service.exe</id>
<name>Service</name>
<description>The service.</description>
<executable>node.exe</executable>
<arguments>My Arguments</arguments>
<startmode>manual</startmode>
<log mode=""roll""></log>
<serviceaccount>
<domain>{Domain}</domain>
<user>{Username}</user>
<password>{Password}</password>
<allowservicelogon>{AllowServiceAccountLogonRight}</allowservicelogon>
</serviceaccount>
<workingdirectory>{ExpectedWorkingDirectory}</workingdirectory>
<logpath>C:\logs</logpath>
</service>";
this._extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(StartMode.Manual));
}
[Test]
public void VerifyWorkingDirectory()
{
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + this._extendedServiceDescriptor.WorkingDirectory);
Assert.That(this._extendedServiceDescriptor.WorkingDirectory, Is.EqualTo(ExpectedWorkingDirectory));
}
[Test]
public void VerifyServiceLogonRight()
{
Assert.That(this._extendedServiceDescriptor.AllowServiceAcountLogonRight, Is.True);
}
[Test]
public void VerifyUsername()
{
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + this._extendedServiceDescriptor.WorkingDirectory);
Assert.That(this._extendedServiceDescriptor.ServiceAccountUser, Is.EqualTo(Domain + "\\" + Username));
}
[Test]
public void VerifyPassword()
{
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + this._extendedServiceDescriptor.WorkingDirectory);
Assert.That(this._extendedServiceDescriptor.ServiceAccountPassword, Is.EqualTo(Password));
}
[Test]
public void Priority()
{
var sd = ServiceDescriptor.FromXML("<service><id>test</id><priority>normal</priority></service>");
Assert.That(sd.Priority, Is.EqualTo(ProcessPriorityClass.Normal));
sd = ServiceDescriptor.FromXML("<service><id>test</id><priority>idle</priority></service>");
Assert.That(sd.Priority, Is.EqualTo(ProcessPriorityClass.Idle));
sd = ServiceDescriptor.FromXML("<service><id>test</id></service>");
Assert.That(sd.Priority, Is.EqualTo(ProcessPriorityClass.Normal));
}
[Test]
public void StopParentProcessFirstIsFalseByDefault()
{
Assert.That(this._extendedServiceDescriptor.StopParentProcessFirst, Is.False);
}
[Test]
public void CanParseStopParentProcessFirst()
{
const string seedXml = "<service>"
+ "<stopparentprocessfirst>true</stopparentprocessfirst>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.StopParentProcessFirst, Is.True);
}
[Test]
public void CanParseStopTimeout()
{
const string seedXml = "<service>"
+ "<stoptimeout>60sec</stoptimeout>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.StopTimeout, Is.EqualTo(TimeSpan.FromSeconds(60)));
}
[Test]
public void CanParseStopTimeoutFromMinutes()
{
const string seedXml = "<service>"
+ "<stoptimeout>10min</stoptimeout>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.StopTimeout, Is.EqualTo(TimeSpan.FromMinutes(10)));
}
[Test]
public void CanParseLogname()
{
const string seedXml = "<service>"
+ "<logname>MyTestApp</logname>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.LogName, Is.EqualTo("MyTestApp"));
}
[Test]
public void CanParseOutfileDisabled()
{
const string seedXml = "<service>"
+ "<outfiledisabled>true</outfiledisabled>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.OutFileDisabled, Is.True);
}
[Test]
public void CanParseErrfileDisabled()
{
const string seedXml = "<service>"
+ "<errfiledisabled>true</errfiledisabled>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.ErrFileDisabled, Is.True);
}
[Test]
public void CanParseOutfilePattern()
{
const string seedXml = "<service>"
+ "<outfilepattern>.out.test.log</outfilepattern>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.OutFilePattern, Is.EqualTo(".out.test.log"));
}
[Test]
public void CanParseErrfilePattern()
{
const string seedXml = "<service>"
+ "<errfilepattern>.err.test.log</errfilepattern>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.ErrFilePattern, Is.EqualTo(".err.test.log"));
}
[Test]
public void LogModeRollBySize()
{
const string seedXml = "<service>"
+ "<logpath>c:\\</logpath>"
+ "<log mode=\"roll-by-size\">"
+ "<sizeThreshold>112</sizeThreshold>"
+ "<keepFiles>113</keepFiles>"
+ "</log>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
serviceDescriptor.BaseName = "service";
var logHandler = serviceDescriptor.LogHandler as SizeBasedRollingLogAppender;
Assert.That(logHandler, Is.Not.Null);
Assert.That(logHandler.SizeTheshold, Is.EqualTo(112 * 1024));
Assert.That(logHandler.FilesToKeep, Is.EqualTo(113));
}
[Test]
public void LogModeRollByTime()
{
const string seedXml = "<service>"
+ "<logpath>c:\\</logpath>"
+ "<log mode=\"roll-by-time\">"
+ "<period>7</period>"
+ "<pattern>log pattern</pattern>"
+ "</log>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
serviceDescriptor.BaseName = "service";
var logHandler = serviceDescriptor.LogHandler as TimeBasedRollingLogAppender;
Assert.That(logHandler, Is.Not.Null);
Assert.That(logHandler.Period, Is.EqualTo(7));
Assert.That(logHandler.Pattern, Is.EqualTo("log pattern"));
}
[Test]
public void LogModeRollBySizeTime()
{
const string seedXml = "<service>"
+ "<logpath>c:\\</logpath>"
+ "<log mode=\"roll-by-size-time\">"
+ "<sizeThreshold>10240</sizeThreshold>"
+ "<pattern>yyyy-MM-dd</pattern>"
+ "<autoRollAtTime>00:00:00</autoRollAtTime>"
+ "</log>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
serviceDescriptor.BaseName = "service";
var logHandler = serviceDescriptor.LogHandler as RollingSizeTimeLogAppender;
Assert.That(logHandler, Is.Not.Null);
Assert.That(logHandler.SizeTheshold, Is.EqualTo(10240 * 1024));
Assert.That(logHandler.FilePattern, Is.EqualTo("yyyy-MM-dd"));
Assert.That(logHandler.AutoRollAtTime, Is.EqualTo((TimeSpan?)new TimeSpan(0, 0, 0)));
}
[Test]
public void VerifyServiceLogonRightGraceful()
{
const string seedXml = "<service>"
+ "<serviceaccount>"
+ "<domain>" + Domain + "</domain>"
+ "<user>" + Username + "</user>"
+ "<password>" + Password + "</password>"
+ "<allowservicelogon>true1</allowservicelogon>"
+ "</serviceaccount>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.AllowServiceAcountLogonRight, Is.False);
}
[Test]
public void VerifyServiceLogonRightOmitted()
{
const string seedXml = "<service>"
+ "<serviceaccount>"
+ "<domain>" + Domain + "</domain>"
+ "<user>" + Username + "</user>"
+ "<password>" + Password + "</password>"
+ "</serviceaccount>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.AllowServiceAcountLogonRight, Is.False);
}
[Test]
public void VerifyWaitHint_FullXML()
{
var sd = ConfigXmlBuilder.create()
.WithTag("waithint", "20 min")
.ToServiceDescriptor(true);
Assert.That(sd.WaitHint, Is.EqualTo(TimeSpan.FromMinutes(20)));
}
/// <summary>
/// Test for https://github.com/kohsuke/winsw/issues/159
/// </summary>
[Test]
public void VerifyWaitHint_XMLWithoutVersion()
{
var sd = ConfigXmlBuilder.create(printXMLVersion: false)
.WithTag("waithint", "21 min")
.ToServiceDescriptor(true);
Assert.That(sd.WaitHint, Is.EqualTo(TimeSpan.FromMinutes(21)));
}
[Test]
public void VerifyWaitHint_XMLWithoutComment()
{
var sd = ConfigXmlBuilder.create(xmlComment: null)
.WithTag("waithint", "22 min")
.ToServiceDescriptor(true);
Assert.That(sd.WaitHint, Is.EqualTo(TimeSpan.FromMinutes(22)));
}
[Test]
public void VerifyWaitHint_XMLWithoutVersionAndComment()
{
var sd = ConfigXmlBuilder.create(xmlComment: null, printXMLVersion: false)
.WithTag("waithint", "23 min")
.ToServiceDescriptor(true);
Assert.That(sd.WaitHint, Is.EqualTo(TimeSpan.FromMinutes(23)));
}
[Test]
public void VerifySleepTime()
{
var sd = ConfigXmlBuilder.create().WithTag("sleeptime", "3 hrs").ToServiceDescriptor(true);
Assert.That(sd.SleepTime, Is.EqualTo(TimeSpan.FromHours(3)));
}
[Test]
public void VerifyResetFailureAfter()
{
var sd = ConfigXmlBuilder.create().WithTag("resetfailure", "75 sec").ToServiceDescriptor(true);
Assert.That(sd.ResetFailureAfter, Is.EqualTo(TimeSpan.FromSeconds(75)));
}
[Test]
public void VerifyStopTimeout()
{
var sd = ConfigXmlBuilder.create().WithTag("stoptimeout", "35 secs").ToServiceDescriptor(true);
Assert.That(sd.StopTimeout, Is.EqualTo(TimeSpan.FromSeconds(35)));
}
/// <summary>
/// https://github.com/kohsuke/winsw/issues/178
/// </summary>
[Test]
public void Arguments_LegacyParam()
{
var sd = ConfigXmlBuilder.create().WithTag("arguments", "arg").ToServiceDescriptor(true);
Assert.That(sd.Arguments, Is.EqualTo("arg"));
}
[Test]
public void Arguments_NewParam_Single()
{
var sd = ConfigXmlBuilder.create()
.WithTag("argument", "--arg1=2")
.ToServiceDescriptor(true);
Assert.That(sd.Arguments, Is.EqualTo(" --arg1=2"));
}
[Test]
public void Arguments_NewParam_MultipleArgs()
{
var sd = ConfigXmlBuilder.create()
.WithTag("argument", "--arg1=2")
.WithTag("argument", "--arg2=123")
.WithTag("argument", "--arg3=null")
.ToServiceDescriptor(true);
Assert.That(sd.Arguments, Is.EqualTo(" --arg1=2 --arg2=123 --arg3=null"));
}
/// <summary>
/// Ensures that the new single-argument field has a higher priority.
/// </summary>
[Test]
public void Arguments_Bothparam_Priorities()
{
var sd = ConfigXmlBuilder.create()
.WithTag("arguments", "--arg1=2 --arg2=3")
.WithTag("argument", "--arg2=123")
.WithTag("argument", "--arg3=null")
.ToServiceDescriptor(true);
Assert.That(sd.Arguments, Is.EqualTo(" --arg2=123 --arg3=null"));
}
[TestCase(true)]
[TestCase(false)]
public void DelayedStart_RoundTrip(bool enabled)
{
var bldr = ConfigXmlBuilder.create();
if (enabled)
{
bldr = bldr.WithDelayedAutoStart();
}
var sd = bldr.ToServiceDescriptor();
Assert.That(sd.DelayedAutoStart, Is.EqualTo(enabled));
}
}
}
using System;
using System.Diagnostics;
using NUnit.Framework;
using WinSW;
using winswTests.Util;
using WMI;
namespace winswTests
{
[TestFixture]
public class ServiceDescriptorTests
{
private ServiceDescriptor _extendedServiceDescriptor;
private const string ExpectedWorkingDirectory = @"Z:\Path\SubPath";
private const string Username = "User";
private const string Password = "Password";
private const string Domain = "Domain";
private const string AllowServiceAccountLogonRight = "true";
[SetUp]
public void SetUp()
{
string seedXml =
$@"<service>
<id>service.exe</id>
<name>Service</name>
<description>The service.</description>
<executable>node.exe</executable>
<arguments>My Arguments</arguments>
<log mode=""roll""></log>
<serviceaccount>
<domain>{Domain}</domain>
<user>{Username}</user>
<password>{Password}</password>
<allowservicelogon>{AllowServiceAccountLogonRight}</allowservicelogon>
</serviceaccount>
<workingdirectory>{ExpectedWorkingDirectory}</workingdirectory>
<logpath>C:\logs</logpath>
</service>";
this._extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
}
[Test]
public void DefaultStartMode()
{
Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(StartMode.Automatic));
}
[Test]
public void IncorrectStartMode()
{
string seedXml =
$@"<service>
<id>service.exe</id>
<name>Service</name>
<description>The service.</description>
<executable>node.exe</executable>
<arguments>My Arguments</arguments>
<startmode>roll</startmode>
<log mode=""roll""></log>
<serviceaccount>
<domain>{Domain}</domain>
<user>{Username}</user>
<password>{Password}</password>
<allowservicelogon>{AllowServiceAccountLogonRight}</allowservicelogon>
</serviceaccount>
<workingdirectory>{ExpectedWorkingDirectory}</workingdirectory>
<logpath>C:\logs</logpath>
</service>";
this._extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(() => this._extendedServiceDescriptor.StartMode, Throws.ArgumentException);
}
[Test]
public void ChangedStartMode()
{
string seedXml =
$@"<service>
<id>service.exe</id>
<name>Service</name>
<description>The service.</description>
<executable>node.exe</executable>
<arguments>My Arguments</arguments>
<startmode>manual</startmode>
<log mode=""roll""></log>
<serviceaccount>
<domain>{Domain}</domain>
<user>{Username}</user>
<password>{Password}</password>
<allowservicelogon>{AllowServiceAccountLogonRight}</allowservicelogon>
</serviceaccount>
<workingdirectory>{ExpectedWorkingDirectory}</workingdirectory>
<logpath>C:\logs</logpath>
</service>";
this._extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(this._extendedServiceDescriptor.StartMode, Is.EqualTo(StartMode.Manual));
}
[Test]
public void VerifyWorkingDirectory()
{
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + this._extendedServiceDescriptor.WorkingDirectory);
Assert.That(this._extendedServiceDescriptor.WorkingDirectory, Is.EqualTo(ExpectedWorkingDirectory));
}
[Test]
public void VerifyServiceLogonRight()
{
Assert.That(_extendedServiceDescriptor.ServiceAccount.AllowServiceAcountLogonRight, Is.True);
}
[Test]
public void VerifyUsername()
{
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + _extendedServiceDescriptor.WorkingDirectory);
Assert.That(_extendedServiceDescriptor.ServiceAccount.ServiceAccountUser, Is.EqualTo(Domain + "\\" + Username));
}
[Test]
public void VerifyPassword()
{
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + _extendedServiceDescriptor.WorkingDirectory);
Assert.That(_extendedServiceDescriptor.ServiceAccount.ServiceAccountPassword, Is.EqualTo(Password));
}
[Test]
public void Priority()
{
var sd = ServiceDescriptor.FromXML("<service><id>test</id><priority>normal</priority></service>");
Assert.That(sd.Priority, Is.EqualTo(ProcessPriorityClass.Normal));
sd = ServiceDescriptor.FromXML("<service><id>test</id><priority>idle</priority></service>");
Assert.That(sd.Priority, Is.EqualTo(ProcessPriorityClass.Idle));
sd = ServiceDescriptor.FromXML("<service><id>test</id></service>");
Assert.That(sd.Priority, Is.EqualTo(ProcessPriorityClass.Normal));
}
[Test]
public void StopParentProcessFirstIsFalseByDefault()
{
Assert.That(this._extendedServiceDescriptor.StopParentProcessFirst, Is.False);
}
[Test]
public void CanParseStopParentProcessFirst()
{
const string seedXml = "<service>"
+ "<stopparentprocessfirst>true</stopparentprocessfirst>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.StopParentProcessFirst, Is.True);
}
[Test]
public void CanParseStopTimeout()
{
const string seedXml = "<service>"
+ "<stoptimeout>60sec</stoptimeout>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.StopTimeout, Is.EqualTo(TimeSpan.FromSeconds(60)));
}
[Test]
public void CanParseStopTimeoutFromMinutes()
{
const string seedXml = "<service>"
+ "<stoptimeout>10min</stoptimeout>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.StopTimeout, Is.EqualTo(TimeSpan.FromMinutes(10)));
}
[Test]
public void CanParseLogname()
{
const string seedXml = "<service>"
+ "<logname>MyTestApp</logname>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.LogName, Is.EqualTo("MyTestApp"));
}
[Test]
public void CanParseOutfileDisabled()
{
const string seedXml = "<service>"
+ "<outfiledisabled>true</outfiledisabled>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.Log.OutFileDisabled, Is.True);
}
[Test]
public void CanParseErrfileDisabled()
{
const string seedXml = "<service>"
+ "<errfiledisabled>true</errfiledisabled>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.Log.ErrFileDisabled, Is.True);
}
[Test]
public void CanParseOutfilePattern()
{
const string seedXml = "<service>"
+ "<outfilepattern>.out.test.log</outfilepattern>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.Log.OutFilePattern, Is.EqualTo(".out.test.log"));
}
[Test]
public void CanParseErrfilePattern()
{
const string seedXml = "<service>"
+ "<errfilepattern>.err.test.log</errfilepattern>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.Log.ErrFilePattern, Is.EqualTo(".err.test.log"));
}
[Test]
public void LogModeRollBySize()
{
const string seedXml = "<service>"
+ "<logpath>c:\\</logpath>"
+ "<log mode=\"roll-by-size\">"
+ "<sizeThreshold>112</sizeThreshold>"
+ "<keepFiles>113</keepFiles>"
+ "</log>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
serviceDescriptor.BaseName = "service";
var logHandler = serviceDescriptor.Log.CreateLogHandler() as SizeBasedRollingLogAppender;
Assert.That(logHandler, Is.Not.Null);
Assert.That(logHandler.SizeTheshold, Is.EqualTo(112 * 1024));
Assert.That(logHandler.FilesToKeep, Is.EqualTo(113));
}
[Test]
public void LogModeRollByTime()
{
const string seedXml = "<service>"
+ "<logpath>c:\\</logpath>"
+ "<log mode=\"roll-by-time\">"
+ "<period>7</period>"
+ "<pattern>log pattern</pattern>"
+ "</log>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
serviceDescriptor.BaseName = "service";
var logHandler = serviceDescriptor.Log.CreateLogHandler() as TimeBasedRollingLogAppender;
Assert.That(logHandler, Is.Not.Null);
Assert.That(logHandler.Period, Is.EqualTo(7));
Assert.That(logHandler.Pattern, Is.EqualTo("log pattern"));
}
[Test]
public void LogModeRollBySizeTime()
{
const string seedXml = "<service>"
+ "<logpath>c:\\</logpath>"
+ "<log mode=\"roll-by-size-time\">"
+ "<sizeThreshold>10240</sizeThreshold>"
+ "<pattern>yyyy-MM-dd</pattern>"
+ "<autoRollAtTime>00:00:00</autoRollAtTime>"
+ "</log>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
serviceDescriptor.BaseName = "service";
var logHandler = serviceDescriptor.Log.CreateLogHandler() as RollingSizeTimeLogAppender;
Assert.That(logHandler, Is.Not.Null);
Assert.That(logHandler.SizeTheshold, Is.EqualTo(10240 * 1024));
Assert.That(logHandler.FilePattern, Is.EqualTo("yyyy-MM-dd"));
Assert.That(logHandler.AutoRollAtTime, Is.EqualTo((TimeSpan?)new TimeSpan(0, 0, 0)));
}
[Test]
public void VerifyServiceLogonRightGraceful()
{
const string seedXml = "<service>"
+ "<serviceaccount>"
+ "<domain>" + Domain + "</domain>"
+ "<user>" + Username + "</user>"
+ "<password>" + Password + "</password>"
+ "<allowservicelogon>true1</allowservicelogon>"
+ "</serviceaccount>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.ServiceAccount.AllowServiceAcountLogonRight, Is.False);
}
[Test]
public void VerifyServiceLogonRightOmitted()
{
const string seedXml = "<service>"
+ "<serviceaccount>"
+ "<domain>" + Domain + "</domain>"
+ "<user>" + Username + "</user>"
+ "<password>" + Password + "</password>"
+ "</serviceaccount>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
Assert.That(serviceDescriptor.ServiceAccount.AllowServiceAcountLogonRight, Is.False);
}
[Test]
public void VerifyWaitHint_FullXML()
{
var sd = ConfigXmlBuilder.create()
.WithTag("waithint", "20 min")
.ToServiceDescriptor(true);
Assert.That(sd.WaitHint, Is.EqualTo(TimeSpan.FromMinutes(20)));
}
/// <summary>
/// Test for https://github.com/kohsuke/winsw/issues/159
/// </summary>
[Test]
public void VerifyWaitHint_XMLWithoutVersion()
{
var sd = ConfigXmlBuilder.create(printXMLVersion: false)
.WithTag("waithint", "21 min")
.ToServiceDescriptor(true);
Assert.That(sd.WaitHint, Is.EqualTo(TimeSpan.FromMinutes(21)));
}
[Test]
public void VerifyWaitHint_XMLWithoutComment()
{
var sd = ConfigXmlBuilder.create(xmlComment: null)
.WithTag("waithint", "22 min")
.ToServiceDescriptor(true);
Assert.That(sd.WaitHint, Is.EqualTo(TimeSpan.FromMinutes(22)));
}
[Test]
public void VerifyWaitHint_XMLWithoutVersionAndComment()
{
var sd = ConfigXmlBuilder.create(xmlComment: null, printXMLVersion: false)
.WithTag("waithint", "23 min")
.ToServiceDescriptor(true);
Assert.That(sd.WaitHint, Is.EqualTo(TimeSpan.FromMinutes(23)));
}
[Test]
public void VerifySleepTime()
{
var sd = ConfigXmlBuilder.create().WithTag("sleeptime", "3 hrs").ToServiceDescriptor(true);
Assert.That(sd.SleepTime, Is.EqualTo(TimeSpan.FromHours(3)));
}
[Test]
public void VerifyResetFailureAfter()
{
var sd = ConfigXmlBuilder.create().WithTag("resetfailure", "75 sec").ToServiceDescriptor(true);
Assert.That(sd.ResetFailureAfter, Is.EqualTo(TimeSpan.FromSeconds(75)));
}
[Test]
public void VerifyStopTimeout()
{
var sd = ConfigXmlBuilder.create().WithTag("stoptimeout", "35 secs").ToServiceDescriptor(true);
Assert.That(sd.StopTimeout, Is.EqualTo(TimeSpan.FromSeconds(35)));
}
/// <summary>
/// https://github.com/kohsuke/winsw/issues/178
/// </summary>
[Test]
public void Arguments_LegacyParam()
{
var sd = ConfigXmlBuilder.create().WithTag("arguments", "arg").ToServiceDescriptor(true);
Assert.That(sd.Arguments, Is.EqualTo("arg"));
}
[Test]
public void Arguments_NewParam_Single()
{
var sd = ConfigXmlBuilder.create()
.WithTag("argument", "--arg1=2")
.ToServiceDescriptor(true);
Assert.That(sd.Arguments, Is.EqualTo(" --arg1=2"));
}
[Test]
public void Arguments_NewParam_MultipleArgs()
{
var sd = ConfigXmlBuilder.create()
.WithTag("argument", "--arg1=2")
.WithTag("argument", "--arg2=123")
.WithTag("argument", "--arg3=null")
.ToServiceDescriptor(true);
Assert.That(sd.Arguments, Is.EqualTo(" --arg1=2 --arg2=123 --arg3=null"));
}
/// <summary>
/// Ensures that the new single-argument field has a higher priority.
/// </summary>
[Test]
public void Arguments_Bothparam_Priorities()
{
var sd = ConfigXmlBuilder.create()
.WithTag("arguments", "--arg1=2 --arg2=3")
.WithTag("argument", "--arg2=123")
.WithTag("argument", "--arg3=null")
.ToServiceDescriptor(true);
Assert.That(sd.Arguments, Is.EqualTo(" --arg2=123 --arg3=null"));
}
[TestCase(true)]
[TestCase(false)]
public void DelayedStart_RoundTrip(bool enabled)
{
var bldr = ConfigXmlBuilder.create();
if (enabled)
{
bldr = bldr.WithDelayedAutoStart();
}
var sd = bldr.ToServiceDescriptor();
Assert.That(sd.DelayedAutoStart, Is.EqualTo(enabled));
}
}
}

View File

@ -0,0 +1,119 @@
using System;
using NUnit.Framework;
using WinSW;
namespace winswTests
{
class ServiceDescriptorYamlTest
{
private string MinimalYaml = @"id: myapp
caption: This is a test
executable: 'C:\Program Files\Java\jdk1.8.0_241\bin\java.exe'
description: This is test winsw";
[Test]
public void Simple_yaml_parsing_test()
{
var configs = ServiceDescriptorYaml.FromYaml(MinimalYaml).Configurations;
Assert.AreEqual("myapp", configs.Id);
Assert.AreEqual("This is a test", configs.Caption);
Assert.AreEqual("C:\\Program Files\\Java\\jdk1.8.0_241\\bin\\java.exe", configs.Executable);
Assert.AreEqual("This is test winsw", configs.Description);
}
[Test]
public void Must_implemented_value_test()
{
string yml = @"caption: This is a test
executable: 'C:\Program Files\Java\jdk1.8.0_241\bin\java.exe'
description: This is test winsw";
void getId()
{
var id = ServiceDescriptorYaml.FromYaml(yml).Configurations.Id;
}
Assert.That(() => getId(), Throws.TypeOf<InvalidOperationException>());
}
[Test]
public void Default_value_map_test()
{
var executablePath = ServiceDescriptorYaml.FromYaml(MinimalYaml).Configurations.ExecutablePath;
Assert.IsNotNull(executablePath);
}
[Test]
public void Simple_download_parsing_test()
{
var yml = @"download:
-
from: www.sample.com
to: c://tmp
-
from: www.sample2.com
to: d://tmp
-
from: www.sample3.com
to: d://temp";
var configs = ServiceDescriptorYaml.FromYaml(yml).Configurations;
Assert.AreEqual(3, configs.Downloads.Count);
}
[Test]
public void Download_not_specified_test()
{
var yml = @"id: jenkins
name: No Service Account
";
var configs = ServiceDescriptorYaml.FromYaml(yml).Configurations;
Assert.DoesNotThrow(() =>
{
var dowloads = configs.Downloads;
});
}
[Test]
public void Service_account_not_specified_test()
{
var yml = @"id: jenkins
name: No Service Account
";
var configs = ServiceDescriptorYaml.FromYaml(yml).Configurations;
Assert.DoesNotThrow(() =>
{
var serviceAccount = configs.ServiceAccount.AllowServiceAcountLogonRight;
});
}
[Test]
public void Service_account_specified_but_fields_not_specified()
{
var yml = @"id: jenkins
name: No Service Account
serviceaccount:
user: testuser
";
var configs = ServiceDescriptorYaml.FromYaml(yml).Configurations;
Assert.DoesNotThrow(() =>
{
var user = configs.ServiceAccount.ServiceAccountUser;
var password = configs.ServiceAccount.ServiceAccountPassword;
var allowLogon = configs.ServiceAccount.AllowServiceAcountLogonRight;
var hasAccount = configs.ServiceAccount.HasServiceAccount();
});
}
}
}

View File

@ -59,6 +59,10 @@ namespace winswTests.Util
properties.Remove("Caption");
properties.Remove("Description");
properties.Remove("Executable");
properties.Remove("BaseName");
properties.Remove("BasePath");
properties.Remove("Log");
properties.Remove("ServiceAccount");
return properties;
}
}