Update Yaml Configuration support (#596)

* Sync with upstream (#3)

* Add a Dependabot configuration (#558)

* Add a Dependabot configuration

* Update dependabot.yml

* Bump NUnit3TestAdapter from 3.16.0 to 3.16.1 (#561)

Bumps [NUnit3TestAdapter](https://github.com/nunit/nunit3-vs-adapter) from 3.16.0 to 3.16.1.
- [Release notes](https://github.com/nunit/nunit3-vs-adapter/releases)
- [Commits](https://github.com/nunit/nunit3-vs-adapter/compare/V3.16...V3.16.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump Microsoft.NET.Test.Sdk from 16.4.0 to 16.6.1 (#563)

Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.4.0 to 16.6.1.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Commits](https://github.com/microsoft/vstest/compare/v16.4.0...v16.6.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump ilmerge from 3.0.29 to 3.0.40 (#559)

* Bump ilmerge from 3.0.29 to 3.0.40

Bumps [ilmerge](https://github.com/dotnet/ILMerge) from 3.0.29 to 3.0.40.
- [Release notes](https://github.com/dotnet/ILMerge/releases)
- [Commits](https://github.com/dotnet/ILMerge/commits)

Signed-off-by: dependabot[bot] <support@github.com>

* Define $(ILMergeVersion)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Next Turn <45985406+NextTurn@users.noreply.github.com>

* Bump coverlet.collector from 1.2.0 to 1.3.0 (#560)

Bumps [coverlet.collector](https://github.com/coverlet-coverage/coverlet) from 1.2.0 to 1.3.0.
- [Release notes](https://github.com/coverlet-coverage/coverlet/releases)
- [Commits](https://github.com/coverlet-coverage/coverlet/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump ilmerge from 3.0.40 to 3.0.41 (#571)

Bumps [ilmerge](https://github.com/dotnet/ILMerge) from 3.0.40 to 3.0.41.
- [Release notes](https://github.com/dotnet/ILMerge/releases)
- [Commits](https://github.com/dotnet/ILMerge/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Co-authored-by: Oleg Nenashev <o.v.nenashev@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Next Turn <45985406+NextTurn@users.noreply.github.com>

* Update WinSW Yaml Support

Add sample-allOption.yml
Update timespan values to aprse from strings. Now user can specify timespan values like 5sec.
Implement unimplemented properties in YmlConfiguration

* Update configurations namings

* Move ParseTimeSpan to seperate utility class

* Update Environment Variable Syntax

Now env variables support name: value: syntax

* Add YamlConfigFile.md

Add yaml user documentation
Update YamlSupport Unit test

* Update YamlConfigFile.md

Summerize the yaml documentation and reference the XmlConfigFile.md for more details

* Update YamlDoc

* Update doc/YamlConfigFile.md

Co-authored-by: Next Turn <45985406+NextTurn@users.noreply.github.com>

* Update yaml configurations to expand environment variables

* Update doc/YamlConfigFile.md

Co-authored-by: Next Turn <45985406+NextTurn@users.noreply.github.com>

Co-authored-by: Oleg Nenashev <o.v.nenashev@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Next Turn <45985406+NextTurn@users.noreply.github.com>
pull/580/head^2
Buddhika Chathuranga 2020-07-29 17:30:39 +05:30 committed by GitHub
parent 7554d0ecbd
commit 13cfe45490
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 730 additions and 165 deletions

304
doc/YamlConfigFile.md Normal file
View File

@ -0,0 +1,304 @@
# YAML configuration file
This page describes the configuration file, which controls the behavior of the Windows service.
You can find configuration file samples in the [examples](../examples) directory of the source code repository.
Actual samples are also being published as part of releases on GitHub and NuGet.
## File structure
YAML Configuration file shuold be in following format
Example:
```yaml
id: jenkins
name: Jenkins
description: This service runs Jenkins continuous integration system.
env:
- name: JENKINS_HOME
value: '%BASE%'
executable: java
arguments: >
-Xrs
-Xmx256m
-jar "%BASE%\jenkins.war"
--httpPort=8080
log:
mode: roll
```
## Environment variable expansion
Configuration YAML files can include environment variable expansions of the form `%Name%`.
Such occurrences, if found, will be automatically replaced by the actual values of the variables.
[Read more about Environment variable expansion](xmlConfigFile.md#environment-variable-expansion)
## Configuration entries
### id
Specifies the ID that Windows uses internally to identify the service.
This has to be unique among all the services installed in a system, and it should consist entirely out of alpha-numeric characters.
### name
Short display name of the service, which can contain spaces and other characters.
This shouldn't be too long, like `id`, and this also needs to be unique among all the services in a given system.
### description
Long human-readable description of the service.
This gets displayed in Windows service manager when the service is selected.
### executable
This element specifies the executable to be launched.
It can be either absolute path, or you can just specify the executable name and let it be searched from `PATH` (although note that the services often run in a different user account and therefore it might have different `PATH` than your shell does.)
### startmode
This element specifies the start mode of the Windows service.
It can be one of the following values: Boot, System, Automatic, or Manual.
For more information, see the [ChangeStartMode method](https://docs.microsoft.com/windows/win32/cimwin32prov/changestartmode-method-in-class-win32-service).
The default value is `Automatic`.
### delayedAutoStart
This Boolean option enables the delayed start mode if the `Automatic` start mode is defined.
[Read more about delayedAutoStart](xmlConfigFile.md#delayedautostart)
```yaml
delayedAutoStart: false
```
### depend
Specify IDs of other services that this service depends on.
When service `X` depends on service `Y`, `X` can only run if `Y` is running.
YAML list can be used to specify multiple dependencies.
```yaml
depend:
- Eventlog
- W32Time
```
### log
Optionally set a different logging directory with `logpath` and startup `mode`: append (default), reset (clear log), ignore, roll (move to `\*.old`).
User can specify all log configurations as a single YAML dictionary
```yaml
log:
mode: roll-by-size
logpath: '%BASE/log%'
sizeThreshold: 10240
keepFiles: 8
```
See the [Logging and error reporting](loggingAndErrorReporting.md) page for more info.
### Arguments
`arguments` element specifies the arguments to be passed to the executable. User can specify all the commands as a single line.
```yaml
arguments: arg1 arg2 arg3
```
Also user can specify the arguemtns in more structured way with YAML multline strings.
```yaml
arguments: >
arg1
arg2
arg3
```
### stoparguments/stopexecutable
~~When the service is requested to stop, winsw simply calls [TerminateProcess function](https://docs.microsoft.com/windows/win32/api/processthreadsapi/nf-processthreadsapi-terminateprocess) to kill the service instantly.~~
However, if `stoparguments` elements is present, winsw will instead launch another process of `executable` (or `stopexecutable` if that's specified) with the specified arguments, and expects that to initiate the graceful shutdown of the service process.
Winsw will then wait for the two processes to exit on its own, before reporting back to Windows that the service has terminated.
When you use the `stoparguments`, you must use `startarguments` instead of `arguments`. See the complete example below:
```yaml
executable: catalina.sh
startarguments: >
jpda
run
stopexecutable: catalina.sh
stoparguments: stop
```
### stoptimeout
This optional element allows you to change this "15 seconds" value, so that you can control how long winsw gives the service to shut itself down.
[Read more about stoptimeout](xmlConfigFile.md#stoptimeout)
See `onfailure` below for how to specify time duration:
```yaml
stoptimeout: 15 sec
```
### Environment
User can use list of YAML dictionaries, if necessary to specify environment variables to be set for the child process. The syntax is:
```yaml
env:
-
name: MY_TOOL_HOME
value: 'C:\etc\tools\myTool'
-
name: LM_LICENSE_FILE
value: host1;host2
```
### interactive
If this optional element is specified, the service will be allowed to interact with the desktop, such as by showing a new window and dialog boxes.
If your program requires GUI, set this like the following:
```yaml
interactive: true
```
Note that since the introduction UAC (Windows Vista and onward), services are no longer really allowed to interact with the desktop.
In those OSes, all that this does is to allow the user to switch to a separate window station to interact with the service.
### beeponshutdown
This optional element is to emit [simple tones](https://docs.microsoft.com/windows/win32/api/utilapiset/nf-utilapiset-beep) when the service shuts down.
This feature should be used only for debugging, as some operating systems and hardware do not support this functionality.
### download
This optional element can be specified to have the service wrapper retrieve resources from URL and place it locally as a file.
This operation runs when the service is started, before the application specified by `executable` is launched.
[Read more about download](xmlConfigFile.md#download)
Examples:
```yaml
download:
-
from: "http://www.google.com/"
to: '%BASE%\index.html'
-
from: "http://www.nosuchhostexists.com/"
to: '%BASE%\dummy.html'
failOnError: true
-
from: "http://example.com/some.dat"
to: '%BASE%\some.dat'
auth: basic
unsecureAuth: true
username: aUser
password: aPa55w0rd
-
from: "https://example.com/some.dat"
to: '%BASE%\some.dat'
auth: basic
username: aUser
password: aPa55w0rd
-
from: "https://example.com/some.dat"
to: '%BASE%\some.dat'
auth: sspi
```
### onfailure
This optional element controls the behaviour when the process launched by winsw fails (i.e., exits with non-zero exit code).
```yaml
onFailure:
-
action: restart
delay: 10 sec
-
action: restart
delay: 20 sec
-
action: reboot
```
[Read more about onFailure](xmlConfigFile.md#onfailure)
### resetfailure
This optional element controls the timing in which Windows SCM resets the failure count.
For example, if you specify `resetfailure: 1 hour` and your service continues to run longer than one hour, then the failure count is reset to zero.
This affects the behaviour of the failure actions (see `onfailure` above).
In other words, this is the duration in which you consider the service has been running successfully.
Defaults to 1 day.
### Security descriptor
The security descriptor string for the service in SDDL form.
For more information, see [Security Descriptor Definition Language](https://docs.microsoft.com/windows/win32/secauthz/security-descriptor-definition-language).
```yaml
securtityDescriptor: 'D:(A;;DCSWRPDTRC;;;BA)(A;;DCSWRPDTRC;;;SY)S:NO\_ACCESS\_CONTROL'
```
### Service account
The service is installed as the [LocalSystem account](https://docs.microsoft.com/windows/win32/services/localsystem-account) by default. If your service does not need a high privilege level, consider using the [LocalService account](https://docs.microsoft.com/windows/win32/services/localservice-account), the [NetworkService account](https://docs.microsoft.com/windows/win32/services/networkservice-account) or a user account.
To use a user account, specify a `serviceaccount` element like this:
```yaml
serviceaccount:
domain: YOURDOMAIN
user: useraccount
password: Pa55w0rd
allowservicelogon: true
```
[Read more about Service account](xmlConfigFile.md#service-account)
### Working directory
Some services need to run with a working directory specified.
To do this, specify a `workingdirectory` element like this:
```yaml
workingdirectory: 'C:\application'
```
### Priority
Optionally specify the scheduling priority of the service process (equivalent of Unix nice)
Possible values are `idle`, `belownormal`, `normal`, `abovenormal`, `high`, `realtime` (case insensitive.)
```yaml
priority: idle
```
Specifying a priority higher than normal has unintended consequences.
For more information, see [ProcessPriorityClass Enumeration](https://docs.microsoft.com/dotnet/api/system.diagnostics.processpriorityclass) in .NET docs.
This feature is intended primarily to launch a process in a lower priority so as not to interfere with the computer's interactive usage.
### Stop parent process first
Optionally specify the order of service shutdown.
If `true`, the parent process is shutdown first.
This is useful when the main process is a console, which can respond to Ctrl+C command and will gracefully shutdown child processes.
```yaml
stopparentprocessfirst: true
```

View File

@ -0,0 +1,75 @@
id: myapp
name: MyApp Service (powered by WinSW)
description: This service is a service created from a sample configuration
executable: java
#serviceaccount:
# domain: YOURDOMAIN
# user: useraccount
# password: Pa55w0rd
# allowservicelogon: yes
#onFailure:
# -
# action: restart
# delay: 10 sec
# -
# action: restart
# delay: 20 sec
# -
# action: reboot
#resetFailureAfter: 01:00:00
#securityDescriptor: security descriptor string
#arguments: >
# -classpath
# c:\cygwin\home\kohsuke\ws\hello-world\out\production\hello-world
# test.Main
#startArguments: start arguments
#workingdirectory: C:\myApp\work
priority: Normal
stopTimeout: 15 sec
stopParentProcessFirst: true
#stopExecutable: '%BASE%\stop.exe'
#stopArguments: -stop true
startMode: Automatic
#delayedAutoStart: true
#serviceDependencies:
# - Eventlog
# - W32Time
waitHint: 15 sec
sleepTime: 1 sec
#interactive: true
log:
# logpath: '%BASE%\logs'
mode: append
#env:
# -
# name: MY_TOOL_HOME
# value: 'C:\etc\tools\myTool'
# -
# name: LM_LICENSE_FILE
# value: host1;host2
#download:
# -
# from: "http://www.google.com/"
# to: '%BASE%\index.html'
# -
# from: "http://www.nosuchhostexists.com/"
# to: '%BASE%\dummy.html'
# failOnError: true
# -
# from: "http://example.com/some.dat"
# to: '%BASE%\some.dat'
# auth: basic
# unsecureAuth: true
# username: aUser
# password: aPa55w0rd
# -
# from: "https://example.com/some.dat"
# to: '%BASE%\some.dat'
# auth: basic
# username: aUser
# password: aPa55w0rd
# -
# from: "https://example.com/some.dat"
# to: '%BASE%\some.dat'
# auth: sspi
#beepOnShutdown: true

View File

@ -92,6 +92,7 @@
<InputAssemblies>$(InputAssemblies) "$(OutDir)SharedDirectoryMapper.dll"</InputAssemblies> <InputAssemblies>$(InputAssemblies) "$(OutDir)SharedDirectoryMapper.dll"</InputAssemblies>
<InputAssemblies>$(InputAssemblies) "$(OutDir)RunawayProcessKiller.dll"</InputAssemblies> <InputAssemblies>$(InputAssemblies) "$(OutDir)RunawayProcessKiller.dll"</InputAssemblies>
<InputAssemblies>$(InputAssemblies) "$(OutDir)log4net.dll"</InputAssemblies> <InputAssemblies>$(InputAssemblies) "$(OutDir)log4net.dll"</InputAssemblies>
<InputAssemblies>$(InputAssemblies) "$(OutDir)YamlDotNet.dll"</InputAssemblies>
<OutputAssembly>"$(ArtifactsDir)WinSW.$(IdentifierSuffix).exe"</OutputAssembly> <OutputAssembly>"$(ArtifactsDir)WinSW.$(IdentifierSuffix).exe"</OutputAssembly>
</PropertyGroup> </PropertyGroup>

View File

@ -76,7 +76,6 @@ namespace WinSW.Configuration
// Extensions // Extensions
XmlNode? ExtensionsConfiguration { get; } XmlNode? ExtensionsConfiguration { get; }
// IWinSWConfiguration Support
List<string> ExtensionIds { get; } List<string> ExtensionIds { get; }
// Service Account // Service Account

View File

@ -1,4 +1,4 @@
using YamlDotNet.Serialization; using YamlDotNet.Serialization;
namespace WinSW.Configuration namespace WinSW.Configuration
{ {
@ -10,7 +10,7 @@ namespace WinSW.Configuration
[YamlMember(Alias = "domain")] [YamlMember(Alias = "domain")]
public string? ServiceAccountDomain { get; set; } public string? ServiceAccountDomain { get; set; }
[YamlMember(Alias = "Password")] [YamlMember(Alias = "password")]
public string? ServiceAccountPassword { get; set; } public string? ServiceAccountPassword { get; set; }
[YamlMember(Alias = "allowservicelogon")] [YamlMember(Alias = "allowservicelogon")]

View File

@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Xml; using System.Xml;
using WinSW.Native; using WinSW.Native;
using WinSW.Util;
using WMI; using WMI;
using YamlDotNet.Serialization; using YamlDotNet.Serialization;
using static WinSW.Download; using static WinSW.Download;
@ -28,9 +30,6 @@ namespace WinSW.Configuration
[YamlMember(Alias = "executablePath")] [YamlMember(Alias = "executablePath")]
public string? ExecutablePathYaml { get; set; } public string? ExecutablePathYaml { get; set; }
[YamlMember(Alias = "caption")]
public string? CaptionYaml { get; set; }
[YamlMember(Alias = "hideWindow")] [YamlMember(Alias = "hideWindow")]
public bool? HideWindowYaml { get; set; } public bool? HideWindowYaml { get; set; }
@ -62,36 +61,36 @@ namespace WinSW.Configuration
public bool? StopParentProcessFirstYaml { get; set; } public bool? StopParentProcessFirstYaml { get; set; }
[YamlMember(Alias = "resetFailureAfter")] [YamlMember(Alias = "resetFailureAfter")]
public TimeSpan? ResetFailureAfterYaml { get; set; } public string? ResetFailureAfterYaml { get; set; }
[YamlMember(Alias = "stopTimeout")] [YamlMember(Alias = "stopTimeout")]
public TimeSpan? StopTimeoutYaml { get; set; } public string? StopTimeoutYaml { get; set; }
[YamlMember(Alias = "startMode")] [YamlMember(Alias = "startMode")]
public StartMode? StartModeYaml { get; set; } public string? StartModeYaml { get; set; }
[YamlMember(Alias = "serviceDependencies")] [YamlMember(Alias = "serviceDependencies")]
public string[]? ServiceDependenciesYaml { get; set; } public string[]? ServiceDependenciesYaml { get; set; }
[YamlMember(Alias = "waitHint")] [YamlMember(Alias = "waitHint")]
public TimeSpan? WaitHintYaml { get; set; } public string? WaitHintYaml { get; set; }
[YamlMember(Alias = "sleepTime")] [YamlMember(Alias = "sleepTime")]
public TimeSpan? SleepTimeYaml { get; set; } public string? SleepTimeYaml { get; set; }
[YamlMember(Alias = "interactive")] [YamlMember(Alias = "interactive")]
public bool? InteractiveYaml { get; set; } public bool? InteractiveYaml { get; set; }
[YamlMember(Alias = "priority")] [YamlMember(Alias = "priority")]
public ProcessPriorityClass? PriorityYaml { get; set; } public string? PriorityYaml { get; set; }
[YamlMember(Alias = "beepOnShutdown")] [YamlMember(Alias = "beepOnShutdown")]
public bool BeepOnShutdown { get; set; } public bool BeepOnShutdown { get; set; }
[YamlMember(Alias = "env")] [YamlMember(Alias = "env")]
public Dictionary<string, string>? EnvironmentVariablesYaml { get; set; } public List<YamlEnv>? EnvironmentVariablesYaml { get; set; }
[YamlMember(Alias = "failureActions")] [YamlMember(Alias = "onFailure")]
public List<YamlFailureAction>? YamlFailureActions { get; set; } public List<YamlFailureAction>? YamlFailureActions { get; set; }
[YamlMember(Alias = "delayedAutoStart")] [YamlMember(Alias = "delayedAutoStart")]
@ -100,6 +99,18 @@ namespace WinSW.Configuration
[YamlMember(Alias = "securityDescriptor")] [YamlMember(Alias = "securityDescriptor")]
public string? SecurityDescriptorYaml { get; set; } public string? SecurityDescriptorYaml { get; set; }
[YamlMember(Alias = "extensions")]
public List<string>? YamlExtensionIds { get; set; }
public class YamlEnv
{
[YamlMember(Alias = "name")]
public string? Name { get; set; }
[YamlMember(Alias = "value")]
public string? Value { get; set; }
}
public class YamlLog : Log public class YamlLog : Log
{ {
private readonly YamlConfiguration configs; private readonly YamlConfiguration configs;
@ -163,7 +174,7 @@ namespace WinSW.Configuration
{ {
return this.NameYamlLog is null ? return this.NameYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.Name : DefaultWinSWSettings.DefaultLogSettings.Name :
Environment.ExpandEnvironmentVariables(this.NameYamlLog); ExpandEnv(this.NameYamlLog);
} }
} }
@ -173,7 +184,7 @@ namespace WinSW.Configuration
{ {
return this.LogPathYamlLog is null ? return this.LogPathYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.Directory : DefaultWinSWSettings.DefaultLogSettings.Directory :
Environment.ExpandEnvironmentVariables(this.LogPathYamlLog); ExpandEnv(this.LogPathYamlLog);
} }
} }
@ -238,7 +249,7 @@ namespace WinSW.Configuration
{ {
return this.OutFilePatternYamlLog is null ? return this.OutFilePatternYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.OutFilePattern : DefaultWinSWSettings.DefaultLogSettings.OutFilePattern :
Environment.ExpandEnvironmentVariables(this.OutFilePatternYamlLog); ExpandEnv(this.OutFilePatternYamlLog);
} }
} }
@ -248,7 +259,7 @@ namespace WinSW.Configuration
{ {
return this.ErrFilePatternYamlLog is null ? return this.ErrFilePatternYamlLog is null ?
DefaultWinSWSettings.DefaultLogSettings.ErrFilePattern : DefaultWinSWSettings.DefaultLogSettings.ErrFilePattern :
Environment.ExpandEnvironmentVariables(this.ErrFilePatternYamlLog); ExpandEnv(this.ErrFilePatternYamlLog);
} }
} }
@ -295,7 +306,7 @@ namespace WinSW.Configuration
public string ToYamlDownload { get; set; } = string.Empty; public string ToYamlDownload { get; set; } = string.Empty;
[YamlMember(Alias = "auth")] [YamlMember(Alias = "auth")]
public AuthType AuthYamlDownload { get; set; } public string? AuthYamlDownload { get; set; }
[YamlMember(Alias = "username")] [YamlMember(Alias = "username")]
public string? UsernameYamlDownload { get; set; } public string? UsernameYamlDownload { get; set; }
@ -311,19 +322,71 @@ namespace WinSW.Configuration
[YamlMember(Alias = "proxy")] [YamlMember(Alias = "proxy")]
public string? ProxyYamlDownload { get; set; } public string? ProxyYamlDownload { get; set; }
public string FromDownload => ExpandEnv(this.FromYamlDownload);
public string ToDownload => ExpandEnv(this.ToYamlDownload);
public string? UsernameDownload => this.UsernameYamlDownload is null ? null : ExpandEnv(this.UsernameYamlDownload);
public string? PasswordDownload => this.PasswordYamlDownload is null ? null : ExpandEnv(this.PasswordYamlDownload);
public string? ProxyDownload => this.ProxyYamlDownload is null ? null : ExpandEnv(this.ProxyYamlDownload);
public AuthType AuthDownload
{
get
{
if (this.AuthYamlDownload is null)
{
return AuthType.None;
}
var auth = ExpandEnv(this.AuthYamlDownload);
try
{
return (AuthType)Enum.Parse(typeof(AuthType), auth, true);
}
catch
{
Console.WriteLine("Auth type in YAML must be one of the following:");
foreach (string at in Enum.GetNames(typeof(AuthType)))
{
Console.WriteLine(at);
}
throw;
}
}
}
} }
public class YamlFailureAction public class YamlFailureAction
{ {
[YamlMember(Alias = "type")] [YamlMember(Alias = "action")]
private SC_ACTION_TYPE type; public string? FailureAction { get; set; }
[YamlMember(Alias = "delay")] [YamlMember(Alias = "delay")]
private TimeSpan delay; public string? FailureActionDelay { get; set; }
public SC_ACTION_TYPE Type { get => this.type; set => this.type = value; } public SC_ACTION_TYPE Type
{
get
{
SC_ACTION_TYPE actionType = this.FailureAction switch
{
"restart" => SC_ACTION_TYPE.SC_ACTION_RESTART,
"none" => SC_ACTION_TYPE.SC_ACTION_NONE,
"reboot" => SC_ACTION_TYPE.SC_ACTION_REBOOT,
_ => throw new InvalidDataException("Invalid failure action: " + this.FailureAction)
};
public TimeSpan Delay { get => this.delay; set => this.delay = value; } return actionType;
}
}
public TimeSpan Delay => this.FailureActionDelay is null ? TimeSpan.Zero : ConfigHelper.ParseTimeSpan(this.FailureActionDelay);
} }
private string? GetArguments(string? args, ArgType type) private string? GetArguments(string? args, ArgType type)
@ -343,7 +406,7 @@ namespace WinSW.Configuration
} }
} }
return Environment.ExpandEnvironmentVariables(args); return ExpandEnv(args);
} }
private enum ArgType private enum ArgType
@ -365,28 +428,35 @@ namespace WinSW.Configuration
foreach (var item in downloads) foreach (var item in downloads)
{ {
result.Add(new Download( result.Add(new Download(
item.FromYamlDownload, item.FromDownload,
item.ToYamlDownload, item.ToDownload,
item.FailOnErrorYamlDownload, item.FailOnErrorYamlDownload,
item.AuthYamlDownload, item.AuthDownload,
item.UsernameYamlDownload, item.UsernameDownload,
item.PasswordYamlDownload, item.PasswordDownload,
item.UnsecureAuthYamlDownload, item.UnsecureAuthYamlDownload,
item.ProxyYamlDownload)); item.ProxyDownload));
} }
return result; return result;
} }
public string Id => this.IdYaml is null ? this.Defaults.Id : this.IdYaml; internal static string ExpandEnv(string str)
{
return Environment.ExpandEnvironmentVariables(str);
}
public string Description => this.DescriptionYaml is null ? this.Defaults.Description : this.DescriptionYaml; public string Id => this.IdYaml is null ? this.Defaults.Id : ExpandEnv(this.IdYaml);
public string Executable => this.ExecutableYaml is null ? this.Defaults.Executable : this.ExecutableYaml; public string Description => this.DescriptionYaml is null ? this.Defaults.Description : ExpandEnv(this.DescriptionYaml);
public string ExecutablePath => this.ExecutablePathYaml is null ? this.Defaults.ExecutablePath : this.ExecutablePathYaml; public string Executable => this.ExecutableYaml is null ? this.Defaults.Executable : ExpandEnv(this.ExecutableYaml);
public string Caption => this.CaptionYaml is null ? this.Defaults.Caption : this.CaptionYaml; public string ExecutablePath => this.ExecutablePathYaml is null ?
this.Defaults.ExecutablePath :
ExpandEnv(this.ExecutablePathYaml);
public string Caption => this.NameYaml is null ? this.Defaults.Caption : ExpandEnv(this.NameYaml);
public bool HideWindow => this.HideWindowYaml is null ? this.Defaults.HideWindow : (bool)this.HideWindowYaml; public bool HideWindow => this.HideWindowYaml is null ? this.Defaults.HideWindow : (bool)this.HideWindowYaml;
@ -400,7 +470,33 @@ namespace WinSW.Configuration
} }
} }
public StartMode StartMode => this.StartModeYaml is null ? this.Defaults.StartMode : (StartMode)this.StartModeYaml; public StartMode StartMode
{
get
{
if (this.StartModeYaml is null)
{
return this.Defaults.StartMode;
}
var p = ExpandEnv(this.StartModeYaml);
try
{
return (StartMode)Enum.Parse(typeof(StartMode), p, true);
}
catch
{
Console.WriteLine("Start mode in YAML must be one of the following:");
foreach (string sm in Enum.GetNames(typeof(StartMode)))
{
Console.WriteLine(sm);
}
throw;
}
}
}
public string Arguments public string Arguments
{ {
@ -421,7 +517,7 @@ namespace WinSW.Configuration
{ {
return this.StopExecutableYaml is null ? return this.StopExecutableYaml is null ?
this.Defaults.StopExecutable : this.Defaults.StopExecutable :
null; ExpandEnv(this.StopExecutableYaml);
} }
} }
@ -447,23 +543,65 @@ namespace WinSW.Configuration
public TimeSpan ResetFailureAfter => this.ResetFailureAfterYaml is null ? public TimeSpan ResetFailureAfter => this.ResetFailureAfterYaml is null ?
this.Defaults.ResetFailureAfter : this.Defaults.ResetFailureAfter :
(TimeSpan)this.ResetFailureAfterYaml; ConfigHelper.ParseTimeSpan(this.ResetFailureAfterYaml);
public string WorkingDirectory => this.WorkingDirectoryYaml is null ? public string WorkingDirectory => this.WorkingDirectoryYaml is null ?
this.Defaults.WorkingDirectory : this.Defaults.WorkingDirectory :
this.WorkingDirectoryYaml; ExpandEnv(this.WorkingDirectoryYaml);
public ProcessPriorityClass Priority => this.PriorityYaml is null ? this.Defaults.Priority : (ProcessPriorityClass)this.PriorityYaml; public ProcessPriorityClass Priority
{
get
{
if (this.PriorityYaml is null)
{
return this.Defaults.Priority;
}
public TimeSpan StopTimeout => this.StopTimeoutYaml is null ? this.Defaults.StopTimeout : (TimeSpan)this.StopTimeoutYaml; var p = ExpandEnv(this.PriorityYaml);
public string[] ServiceDependencies => this.ServiceDependenciesYaml is null ? try
this.Defaults.ServiceDependencies : {
this.ServiceDependenciesYaml; return (ProcessPriorityClass)Enum.Parse(typeof(ProcessPriorityClass), p, true);
}
catch
{
Console.WriteLine("Priority in YAML must be one of the following:");
foreach (string pr in Enum.GetNames(typeof(ProcessPriorityClass)))
{
Console.WriteLine(pr);
}
public TimeSpan WaitHint => this.WaitHintYaml is null ? this.Defaults.WaitHint : (TimeSpan)this.WaitHintYaml; throw;
}
}
}
public TimeSpan SleepTime => this.SleepTimeYaml is null ? this.Defaults.SleepTime : (TimeSpan)this.SleepTimeYaml; public TimeSpan StopTimeout => this.StopTimeoutYaml is null ? this.Defaults.StopTimeout : ConfigHelper.ParseTimeSpan(this.StopTimeoutYaml);
public string[] ServiceDependencies
{
get
{
if (this.ServiceDependenciesYaml is null)
{
return this.Defaults.ServiceDependencies;
}
var result = new List<string>(0);
foreach (var item in this.ServiceDependenciesYaml)
{
result.Add(ExpandEnv(item));
}
return result.ToArray();
}
}
public TimeSpan WaitHint => this.WaitHintYaml is null ? this.Defaults.WaitHint : ConfigHelper.ParseTimeSpan(this.WaitHintYaml);
public TimeSpan SleepTime => this.SleepTimeYaml is null ? this.Defaults.SleepTime : ConfigHelper.ParseTimeSpan(this.SleepTimeYaml);
public bool Interactive => this.InteractiveYaml is null ? this.Defaults.Interactive : (bool)this.InteractiveYaml; public bool Interactive => this.InteractiveYaml is null ? this.Defaults.Interactive : (bool)this.InteractiveYaml;
@ -481,9 +619,16 @@ namespace WinSW.Configuration
{ {
foreach (var item in this.EnvironmentVariablesYaml) foreach (var item in this.EnvironmentVariablesYaml)
{ {
var value = Environment.ExpandEnvironmentVariables(item.Value); if (item.Name is null || item.Value is null)
this.EnvironmentVariables[item.Key] = value; {
Environment.SetEnvironmentVariable(item.Key, value); continue;
}
var key = item.Name;
var value = ExpandEnv(item.Value);
this.EnvironmentVariables[key] = value;
Environment.SetEnvironmentVariable(key, value);
} }
} }
} }
@ -496,19 +641,26 @@ namespace WinSW.Configuration
public string LogMode => this.Log.Mode is null ? this.Defaults.LogMode : this.Log.Mode; public string LogMode => this.Log.Mode is null ? this.Defaults.LogMode : this.Log.Mode;
// TODO // TODO - Extensions
XmlNode? IWinSWConfiguration.ExtensionsConfiguration => throw new NotImplementedException(); XmlNode? IWinSWConfiguration.ExtensionsConfiguration => throw new NotImplementedException();
public List<string> ExtensionIds => throw new NotImplementedException(); public List<string> ExtensionIds => this.YamlExtensionIds ?? this.Defaults.ExtensionIds;
public string BaseName => throw new NotImplementedException(); public string BaseName => this.Defaults.BaseName;
public string BasePath => throw new NotImplementedException(); public string BasePath => this.Defaults.BasePath;
public string? ServiceAccountDomain => throw new NotImplementedException(); public string? SecurityDescriptor
{
get
{
if (this.SecurityDescriptorYaml is null)
{
return this.Defaults.SecurityDescriptor;
}
public string? ServiceAccountName => throw new NotImplementedException(); return ExpandEnv(this.SecurityDescriptorYaml);
}
public string? SecurityDescriptor => throw new NotImplementedException(); }
} }
} }

View File

@ -150,38 +150,9 @@ namespace WinSW
private TimeSpan SingleTimeSpanElement(string tagName, TimeSpan defaultValue) private TimeSpan SingleTimeSpanElement(string tagName, TimeSpan defaultValue)
{ {
string? value = this.SingleElement(tagName, true); string? value = this.SingleElement(tagName, true);
return value is null ? defaultValue : this.ParseTimeSpan(value); return value is null ? defaultValue : ConfigHelper.ParseTimeSpan(value);
} }
private TimeSpan ParseTimeSpan(string v)
{
v = v.Trim();
foreach (var s in Suffix)
{
if (v.EndsWith(s.Key))
{
return TimeSpan.FromMilliseconds(int.Parse(v.Substring(0, v.Length - s.Key.Length).Trim()) * s.Value);
}
}
return TimeSpan.FromMilliseconds(int.Parse(v));
}
private static readonly Dictionary<string, long> Suffix = new Dictionary<string, long>
{
{ "ms", 1 },
{ "sec", 1000L },
{ "secs", 1000L },
{ "min", 1000L * 60L },
{ "mins", 1000L * 60L },
{ "hr", 1000L * 60L * 60L },
{ "hrs", 1000L * 60L * 60L },
{ "hour", 1000L * 60L * 60L },
{ "hours", 1000L * 60L * 60L },
{ "day", 1000L * 60L * 60L * 24L },
{ "days", 1000L * 60L * 60L * 24L }
};
/// <summary> /// <summary>
/// Path to the executable. /// Path to the executable.
/// </summary> /// </summary>
@ -647,7 +618,7 @@ namespace WinSW
_ => throw new Exception("Invalid failure action: " + action) _ => throw new Exception("Invalid failure action: " + action)
}; };
XmlAttribute? delay = node.Attributes["delay"]; XmlAttribute? delay = node.Attributes["delay"];
result[i] = new SC_ACTION(type, delay != null ? this.ParseTimeSpan(delay.Value) : TimeSpan.Zero); result[i] = new SC_ACTION(type, delay != null ? ConfigHelper.ParseTimeSpan(delay.Value) : TimeSpan.Zero);
} }
return result; return result;

View File

@ -1,4 +1,4 @@
using System; using System;
using System.IO; using System.IO;
using WinSW.Configuration; using WinSW.Configuration;
using YamlDotNet.Serialization; using YamlDotNet.Serialization;
@ -11,13 +11,9 @@ namespace WinSW
public static DefaultWinSWSettings Defaults { get; } = new DefaultWinSWSettings(); public static DefaultWinSWSettings Defaults { get; } = new DefaultWinSWSettings();
public string BasePath { get; set; }
public virtual string ExecutablePath => Defaults.ExecutablePath;
public ServiceDescriptorYaml() public ServiceDescriptorYaml()
{ {
string p = this.ExecutablePath; string p = Defaults.ExecutablePath;
string baseName = Path.GetFileNameWithoutExtension(p); string baseName = Path.GetFileNameWithoutExtension(p);
if (baseName.EndsWith(".vshost")) if (baseName.EndsWith(".vshost"))
{ {
@ -40,9 +36,9 @@ namespace WinSW
d = d.Parent; d = d.Parent;
} }
this.BasePath = Path.Combine(d.FullName, baseName); var basepath = Path.Combine(d.FullName, baseName);
using (var reader = new StreamReader(this.BasePath + ".yml")) using (var reader = new StreamReader(basepath + ".yml"))
{ {
var file = reader.ReadToEnd(); var file = reader.ReadToEnd();
var deserializer = new DeserializerBuilder().Build(); var deserializer = new DeserializerBuilder().Build();
@ -56,7 +52,7 @@ namespace WinSW
Environment.SetEnvironmentVariable("SERVICE_ID", this.Configurations.Id); Environment.SetEnvironmentVariable("SERVICE_ID", this.Configurations.Id);
// New name // New name
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameExecutablePath, this.ExecutablePath); Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameExecutablePath, Defaults.ExecutablePath);
// Also inject system environment variables // Also inject system environment variables
Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Configurations.Id); Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Configurations.Id);

View File

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
namespace WinSW.Util
{
public static class ConfigHelper
{
public static TimeSpan ParseTimeSpan(string v)
{
v = v.Trim();
foreach (var s in Suffix)
{
if (v.EndsWith(s.Key))
{
return TimeSpan.FromMilliseconds(int.Parse(v.Substring(0, v.Length - s.Key.Length).Trim()) * s.Value);
}
}
return TimeSpan.FromMilliseconds(int.Parse(v));
}
private static readonly Dictionary<string, long> Suffix = new Dictionary<string, long>
{
{ "ms", 1 },
{ "sec", 1000L },
{ "secs", 1000L },
{ "min", 1000L * 60L },
{ "mins", 1000L * 60L },
{ "hr", 1000L * 60L * 60L },
{ "hrs", 1000L * 60L * 60L },
{ "hour", 1000L * 60L * 60L },
{ "hours", 1000L * 60L * 60L },
{ "day", 1000L * 60L * 60L * 24L },
{ "days", 1000L * 60L * 60L * 24L }
};
}
}

View File

@ -1,54 +1,46 @@
using System; using System;
using NUnit.Framework; using NUnit.Framework;
using WinSW; using WinSW;
using WinSW.Configuration;
using WinSW.Native;
namespace winswTests namespace winswTests
{ {
class ServiceDescriptorYamlTest class ServiceDescriptorYamlTest
{ {
private string MinimalYaml = @"id: myapp private readonly string MinimalYaml = @"id: myapp
caption: This is a test name: This is a test
executable: 'C:\Program Files\Java\jdk1.8.0_241\bin\java.exe' executable: 'C:\Program Files\Java\jdk1.8.0_241\bin\java.exe'
description: This is test winsw"; description: This is test winsw";
private readonly DefaultWinSWSettings Defaults = new DefaultWinSWSettings();
[Test] [Test]
public void Simple_yaml_parsing_test() public void Parse_must_implemented_value_test()
{ {
var configs = ServiceDescriptorYaml.FromYaml(MinimalYaml).Configurations; var yml = @"name: This is a test
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' executable: 'C:\Program Files\Java\jdk1.8.0_241\bin\java.exe'
description: This is test winsw"; description: This is test winsw";
void getId() Assert.That(() =>
{ {
var id = ServiceDescriptorYaml.FromYaml(yml).Configurations.Id; _ = ServiceDescriptorYaml.FromYaml(yml).Configurations.Id;
} }, Throws.TypeOf<InvalidOperationException>());
Assert.That(() => getId(), Throws.TypeOf<InvalidOperationException>());
} }
[Test] [Test]
public void Default_value_map_test() public void Default_value_map_test()
{ {
var executablePath = ServiceDescriptorYaml.FromYaml(MinimalYaml).Configurations.ExecutablePath; var configs = ServiceDescriptorYaml.FromYaml(MinimalYaml).Configurations;
Assert.IsNotNull(executablePath); Assert.IsNotNull(configs.ExecutablePath);
Assert.IsNotNull(configs.BaseName);
Assert.IsNotNull(configs.BasePath);
} }
[Test] [Test]
public void Simple_download_parsing_test() public void Parse_downloads()
{ {
var yml = @"download: var yml = @"download:
- -
@ -64,56 +56,94 @@ description: This is test winsw";
var configs = ServiceDescriptorYaml.FromYaml(yml).Configurations; var configs = ServiceDescriptorYaml.FromYaml(yml).Configurations;
Assert.AreEqual(3, configs.Downloads.Count); Assert.AreEqual(3, configs.Downloads.Count);
Assert.AreEqual("www.sample.com", configs.Downloads[0].From);
Assert.AreEqual("c://tmp", configs.Downloads[0].To);
} }
[Test] [Test]
public void Download_not_specified_test() public void Parse_serviceaccount()
{ {
var yml = @"id: jenkins var yml = @"id: myapp
name: No Service Account name: winsw
"; description: yaml test
executable: java
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: serviceaccount:
user: testuser user: testuser
"; domain: mydomain
password: pa55w0rd
allowservicelogon: yes";
var configs = ServiceDescriptorYaml.FromYaml(yml).Configurations; var serviceAccount = ServiceDescriptorYaml.FromYaml(yml).Configurations.ServiceAccount;
Assert.AreEqual("mydomain\\testuser", serviceAccount.ServiceAccountUser);
Assert.AreEqual(true, serviceAccount.AllowServiceAcountLogonRight);
Assert.AreEqual("pa55w0rd", serviceAccount.ServiceAccountPassword);
Assert.AreEqual(true, serviceAccount.HasServiceAccount());
}
[Test]
public void Parse_environment_variables()
{
var yml = @"id: myapp
name: WinSW
executable: java
description: env test
env:
-
name: MY_TOOL_HOME
value: 'C:\etc\tools\myTool'
-
name: LM_LICENSE_FILE
value: host1;host2";
var envs = ServiceDescriptorYaml.FromYaml(yml).Configurations.EnvironmentVariables;
Assert.That(@"C:\etc\tools\myTool", Is.EqualTo(envs["MY_TOOL_HOME"]));
Assert.That("host1;host2", Is.EqualTo(envs["LM_LICENSE_FILE"]));
}
[Test]
public void Parse_log()
{
var yml = @"id: myapp
name: winsw
description: yaml test
executable: java
log:
mode: roll
logpath: 'D://winsw/logs'";
var config = ServiceDescriptorYaml.FromYaml(yml).Configurations;
Assert.AreEqual("roll", config.LogMode);
Assert.AreEqual("D://winsw/logs", config.LogDirectory);
}
[Test]
public void Parse_onfailure_actions()
{
var yml = @"id: myapp
name: winsw
description: yaml test
executable: java
onFailure:
-
action: restart
delay: 5 sec
-
action: reboot
delay: 10 min";
var onFailure = ServiceDescriptorYaml.FromYaml(yml).Configurations.FailureActions;
Assert.That(onFailure[0].Type, Is.EqualTo(SC_ACTION_TYPE.SC_ACTION_RESTART));
Assert.That(onFailure[1].Type, Is.EqualTo(SC_ACTION_TYPE.SC_ACTION_REBOOT));
Assert.That(TimeSpan.FromMilliseconds(onFailure[0].Delay), Is.EqualTo(TimeSpan.FromSeconds(5)));
Assert.That(TimeSpan.FromMilliseconds(onFailure[1].Delay), Is.EqualTo(TimeSpan.FromMinutes(10)));
Assert.DoesNotThrow(() =>
{
var user = configs.ServiceAccount.ServiceAccountUser;
var password = configs.ServiceAccount.ServiceAccountPassword;
var allowLogon = configs.ServiceAccount.AllowServiceAcountLogonRight;
var hasAccount = configs.ServiceAccount.HasServiceAccount();
});
} }
} }
} }