mirror of https://github.com/dotnet/tye.git
committed by
GitHub
20 changed files with 1680 additions and 118 deletions
@ -0,0 +1,165 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<root> |
||||
|
<!-- |
||||
|
Microsoft ResX Schema |
||||
|
|
||||
|
Version 2.0 |
||||
|
|
||||
|
The primary goals of this format is to allow a simple XML format |
||||
|
that is mostly human readable. The generation and parsing of the |
||||
|
various data types are done through the TypeConverter classes |
||||
|
associated with the data types. |
||||
|
|
||||
|
Example: |
||||
|
|
||||
|
... ado.net/XML headers & schema ... |
||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader> |
||||
|
<resheader name="version">2.0</resheader> |
||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> |
||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> |
||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> |
||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> |
||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> |
||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value> |
||||
|
</data> |
||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> |
||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> |
||||
|
<comment>This is a comment</comment> |
||||
|
</data> |
||||
|
|
||||
|
There are any number of "resheader" rows that contain simple |
||||
|
name/value pairs. |
||||
|
|
||||
|
Each data row contains a name, and value. The row also contains a |
||||
|
type or mimetype. Type corresponds to a .NET class that support |
||||
|
text/value conversion through the TypeConverter architecture. |
||||
|
Classes that don't support this are serialized and stored with the |
||||
|
mimetype set. |
||||
|
|
||||
|
The mimetype is used for serialized objects, and tells the |
||||
|
ResXResourceReader how to depersist the object. This is currently not |
||||
|
extensible. For a given mimetype the value must be set accordingly: |
||||
|
|
||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format |
||||
|
that the ResXResourceWriter will generate, however the reader can |
||||
|
read any of the formats listed below. |
||||
|
|
||||
|
mimetype: application/x-microsoft.net.object.binary.base64 |
||||
|
value : The object must be serialized with |
||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter |
||||
|
: and then encoded with base64 encoding. |
||||
|
|
||||
|
mimetype: application/x-microsoft.net.object.soap.base64 |
||||
|
value : The object must be serialized with |
||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter |
||||
|
: and then encoded with base64 encoding. |
||||
|
|
||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64 |
||||
|
value : The object must be serialized into a byte array |
||||
|
: using a System.ComponentModel.TypeConverter |
||||
|
: and then encoded with base64 encoding. |
||||
|
--> |
||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> |
||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> |
||||
|
<xsd:element name="root" msdata:IsDataSet="true"> |
||||
|
<xsd:complexType> |
||||
|
<xsd:choice maxOccurs="unbounded"> |
||||
|
<xsd:element name="metadata"> |
||||
|
<xsd:complexType> |
||||
|
<xsd:sequence> |
||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" /> |
||||
|
</xsd:sequence> |
||||
|
<xsd:attribute name="name" use="required" type="xsd:string" /> |
||||
|
<xsd:attribute name="type" type="xsd:string" /> |
||||
|
<xsd:attribute name="mimetype" type="xsd:string" /> |
||||
|
<xsd:attribute ref="xml:space" /> |
||||
|
</xsd:complexType> |
||||
|
</xsd:element> |
||||
|
<xsd:element name="assembly"> |
||||
|
<xsd:complexType> |
||||
|
<xsd:attribute name="alias" type="xsd:string" /> |
||||
|
<xsd:attribute name="name" type="xsd:string" /> |
||||
|
</xsd:complexType> |
||||
|
</xsd:element> |
||||
|
<xsd:element name="data"> |
||||
|
<xsd:complexType> |
||||
|
<xsd:sequence> |
||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> |
||||
|
</xsd:sequence> |
||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> |
||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> |
||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> |
||||
|
<xsd:attribute ref="xml:space" /> |
||||
|
</xsd:complexType> |
||||
|
</xsd:element> |
||||
|
<xsd:element name="resheader"> |
||||
|
<xsd:complexType> |
||||
|
<xsd:sequence> |
||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
||||
|
</xsd:sequence> |
||||
|
<xsd:attribute name="name" type="xsd:string" use="required" /> |
||||
|
</xsd:complexType> |
||||
|
</xsd:element> |
||||
|
</xsd:choice> |
||||
|
</xsd:complexType> |
||||
|
</xsd:element> |
||||
|
</xsd:schema> |
||||
|
<resheader name="resmimetype"> |
||||
|
<value>text/microsoft-resx</value> |
||||
|
</resheader> |
||||
|
<resheader name="version"> |
||||
|
<value>2.0</value> |
||||
|
</resheader> |
||||
|
<resheader name="reader"> |
||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
||||
|
</resheader> |
||||
|
<resheader name="writer"> |
||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
||||
|
</resheader> |
||||
|
<data name="ExpectedYamlScalar" xml:space="preserve"> |
||||
|
<value>Excpeted scalar value for key: "{key}".</value> |
||||
|
</data> |
||||
|
<data name="ExpectedYamlSequence" xml:space="preserve"> |
||||
|
<value>Excpeted yaml sequence for key: "{key}".</value> |
||||
|
</data> |
||||
|
<data name="ExtensionMustProvideAName" xml:space="preserve"> |
||||
|
<value>Configuration validation failed. Extensions must provide a name.</value> |
||||
|
</data> |
||||
|
<data name="IngressBindingMustBeHttpOrHttps" xml:space="preserve"> |
||||
|
<value>Ingress bindings must be http or https.</value> |
||||
|
</data> |
||||
|
<data name="IngressRuleMustReferenceService" xml:space="preserve"> |
||||
|
<value>Ingress rules references a service that does not exist.</value> |
||||
|
</data> |
||||
|
<data name="MultipleIngressBindingWithoutName" xml:space="preserve"> |
||||
|
<value>Cannot have multiple ingress bindings without names. Please specify names for each ingress binding.</value> |
||||
|
</data> |
||||
|
<data name="MultipleIngressBindingWithSameName" xml:space="preserve"> |
||||
|
<value>Cannot have multiple ingress bindings with the same name.</value> |
||||
|
</data> |
||||
|
<data name="MultipleServiceBindingsWithoutName" xml:space="preserve"> |
||||
|
<value>Cannot have multiple service bindings without names. Please specify names for each service binding.</value> |
||||
|
</data> |
||||
|
<data name="MultipleServiceBindingsWithSameName" xml:space="preserve"> |
||||
|
<value>Cannot have multiple service bindings with the same name.</value> |
||||
|
</data> |
||||
|
<data name="MustBeABoolean" xml:space="preserve"> |
||||
|
<value>"{value}" must be a boolean value (true/false).</value> |
||||
|
</data> |
||||
|
<data name="MustBeAnInteger" xml:space="preserve"> |
||||
|
<value>"{value}" value must be an integer.</value> |
||||
|
</data> |
||||
|
<data name="MustBePositive" xml:space="preserve"> |
||||
|
<value>"{value}" value cannot be negative.</value> |
||||
|
</data> |
||||
|
<data name="ServiceMustHaveUniqueNames" xml:space="preserve"> |
||||
|
<value>Services must have unique names.</value> |
||||
|
</data> |
||||
|
<data name="UnexpectedType" xml:space="preserve"> |
||||
|
<value>Unexpected node type in tye.yaml. Expected "{expected}" but got "{actual}".</value> |
||||
|
</data> |
||||
|
<data name="UnrecognizedKey" xml:space="preserve"> |
||||
|
<value>Unexpected key "{key}" in tye.yaml.</value> |
||||
|
</data> |
||||
|
</root> |
||||
@ -0,0 +1,7 @@ |
|||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
// See the LICENSE file in the project root for more information.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
[assembly: InternalsVisibleTo("Microsoft.Tye.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] |
||||
@ -0,0 +1,44 @@ |
|||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
// See the LICENSE file in the project root for more information.
|
||||
|
|
||||
|
using Microsoft.Tye.ConfigModel; |
||||
|
using YamlDotNet.RepresentationModel; |
||||
|
|
||||
|
namespace Tye.Serialization |
||||
|
{ |
||||
|
public static class ConfigApplicationParser |
||||
|
{ |
||||
|
public static void HandleConfigApplication(YamlMappingNode yamlMappingNode, ConfigApplication app) |
||||
|
{ |
||||
|
foreach (var child in yamlMappingNode.Children) |
||||
|
{ |
||||
|
var key = YamlParser.GetScalarValue(child.Key); |
||||
|
|
||||
|
switch (key) |
||||
|
{ |
||||
|
case "name": |
||||
|
app.Name = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
case "registry": |
||||
|
app.Registry = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
case "ingress": |
||||
|
YamlParser.ThrowIfNotYamlSequence(key, child.Value); |
||||
|
ConfigIngressParser.HandleIngress((child.Value as YamlSequenceNode)!, app.Ingress); |
||||
|
break; |
||||
|
case "services": |
||||
|
YamlParser.ThrowIfNotYamlSequence(key, child.Value); |
||||
|
ConfigServiceParser.HandleServiceMapping((child.Value as YamlSequenceNode)!, app.Services); |
||||
|
break; |
||||
|
case "extensions": |
||||
|
YamlParser.ThrowIfNotYamlSequence(key, child.Value); |
||||
|
ConfigExtensionsParser.HandleExtensionsMapping((child.Value as YamlSequenceNode)!, app.Extensions); |
||||
|
break; |
||||
|
default: |
||||
|
throw new TyeYamlException(child.Key.Start, CoreStrings.FormatUnrecognizedKey(key)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
// See the LICENSE file in the project root for more information.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using YamlDotNet.RepresentationModel; |
||||
|
|
||||
|
namespace Tye.Serialization |
||||
|
{ |
||||
|
public static class ConfigExtensionsParser |
||||
|
{ |
||||
|
public static void HandleExtensionsMapping(YamlSequenceNode yamlSequenceNode, List<Dictionary<string, object>> extensions) |
||||
|
{ |
||||
|
foreach (var child in yamlSequenceNode.Children) |
||||
|
{ |
||||
|
switch (child.NodeType) |
||||
|
{ |
||||
|
case YamlNodeType.Mapping: |
||||
|
var extensionDictionary = new Dictionary<string, object>(); |
||||
|
foreach (var mapping in (YamlMappingNode)child) |
||||
|
{ |
||||
|
var key = YamlParser.GetScalarValue(mapping.Key); |
||||
|
extensionDictionary[key] = YamlParser.GetScalarValue(key, mapping.Value)!; |
||||
|
} |
||||
|
|
||||
|
extensions.Add(extensionDictionary); |
||||
|
break; |
||||
|
default: |
||||
|
throw new TyeYamlException(child.Start, |
||||
|
CoreStrings.FormatUnexpectedType(YamlNodeType.Mapping.ToString(), child.NodeType.ToString())); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,141 @@ |
|||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
// See the LICENSE file in the project root for more information.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using Microsoft.Tye.ConfigModel; |
||||
|
using YamlDotNet.RepresentationModel; |
||||
|
|
||||
|
namespace Tye.Serialization |
||||
|
{ |
||||
|
public static class ConfigIngressParser |
||||
|
{ |
||||
|
public static void HandleIngress(YamlSequenceNode yamlSequenceNode, List<ConfigIngress> ingress) |
||||
|
{ |
||||
|
foreach (var child in yamlSequenceNode.Children) |
||||
|
{ |
||||
|
YamlParser.ThrowIfNotYamlMapping(child); |
||||
|
var configIngress = new ConfigIngress(); |
||||
|
HandleIngressMapping((YamlMappingNode)child, configIngress); |
||||
|
ingress.Add(configIngress); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void HandleIngressMapping(YamlMappingNode yamlMappingNode, ConfigIngress configIngress) |
||||
|
{ |
||||
|
foreach (var child in yamlMappingNode!.Children) |
||||
|
{ |
||||
|
var key = YamlParser.GetScalarValue(child.Key); |
||||
|
|
||||
|
switch (key) |
||||
|
{ |
||||
|
case "name": |
||||
|
configIngress.Name = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
case "replicas": |
||||
|
if (!int.TryParse(YamlParser.GetScalarValue(key, child.Value), out var replicas)) |
||||
|
{ |
||||
|
throw new TyeYamlException(child.Value.Start, CoreStrings.FormatMustBeAnInteger(key)); |
||||
|
} |
||||
|
|
||||
|
if (replicas < 0) |
||||
|
{ |
||||
|
throw new TyeYamlException(child.Value.Start, CoreStrings.FormatMustBePositive(key)); |
||||
|
} |
||||
|
|
||||
|
configIngress.Replicas = replicas; |
||||
|
break; |
||||
|
case "rules": |
||||
|
if (child.Value.NodeType != YamlNodeType.Sequence) |
||||
|
{ |
||||
|
throw new TyeYamlException(child.Value.Start, CoreStrings.FormatExpectedYamlSequence(key)); |
||||
|
} |
||||
|
HandleIngressRules((child.Value as YamlSequenceNode)!, configIngress.Rules); |
||||
|
break; |
||||
|
case "bindings": |
||||
|
if (child.Value.NodeType != YamlNodeType.Sequence) |
||||
|
{ |
||||
|
throw new TyeYamlException(child.Value.Start, CoreStrings.FormatExpectedYamlSequence(key)); |
||||
|
} |
||||
|
HandleIngressBindings((child.Value as YamlSequenceNode)!, configIngress.Bindings); |
||||
|
break; |
||||
|
default: |
||||
|
throw new TyeYamlException(child.Key.Start, CoreStrings.FormatUnrecognizedKey(key)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void HandleIngressRules(YamlSequenceNode yamlSequenceNode, List<ConfigIngressRule> rules) |
||||
|
{ |
||||
|
foreach (var child in yamlSequenceNode.Children) |
||||
|
{ |
||||
|
YamlParser.ThrowIfNotYamlMapping(child); |
||||
|
var rule = new ConfigIngressRule(); |
||||
|
HandleIngressRuleMapping((YamlMappingNode)child, rule); |
||||
|
rules.Add(rule); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void HandleIngressRuleMapping(YamlMappingNode yamlMappingNode, ConfigIngressRule rule) |
||||
|
{ |
||||
|
foreach (var child in yamlMappingNode!.Children) |
||||
|
{ |
||||
|
var key = YamlParser.GetScalarValue(child.Key); |
||||
|
|
||||
|
switch (key) |
||||
|
{ |
||||
|
case "host": |
||||
|
rule.Host = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
case "path": |
||||
|
rule.Path = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
case "service": |
||||
|
rule.Service = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
default: |
||||
|
throw new TyeYamlException(child.Key.Start, CoreStrings.FormatUnrecognizedKey(key)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void HandleIngressBindings(YamlSequenceNode yamlSequenceNode, List<ConfigIngressBinding> bindings) |
||||
|
{ |
||||
|
foreach (var child in yamlSequenceNode.Children) |
||||
|
{ |
||||
|
YamlParser.ThrowIfNotYamlMapping(child); |
||||
|
var binding = new ConfigIngressBinding(); |
||||
|
HandleIngressBindingMapping((YamlMappingNode)child, binding); |
||||
|
bindings.Add(binding); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void HandleIngressBindingMapping(YamlMappingNode yamlMappingNode, ConfigIngressBinding binding) |
||||
|
{ |
||||
|
foreach (var child in yamlMappingNode!.Children) |
||||
|
{ |
||||
|
var key = YamlParser.GetScalarValue(child.Key); |
||||
|
|
||||
|
switch (key) |
||||
|
{ |
||||
|
case "name": |
||||
|
binding.Name = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
case "port": |
||||
|
if (!int.TryParse(YamlParser.GetScalarValue(key, child.Value), out var port)) |
||||
|
{ |
||||
|
throw new TyeYamlException(child.Value.Start, CoreStrings.FormatMustBeAnInteger(key)); |
||||
|
} |
||||
|
|
||||
|
binding.Port = port; |
||||
|
break; |
||||
|
case "protocol": |
||||
|
binding.Protocol = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
default: |
||||
|
throw new TyeYamlException(child.Key.Start, CoreStrings.FormatUnrecognizedKey(key)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,225 @@ |
|||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
// See the LICENSE file in the project root for more information.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using Microsoft.Tye.ConfigModel; |
||||
|
using YamlDotNet.RepresentationModel; |
||||
|
|
||||
|
namespace Tye.Serialization |
||||
|
{ |
||||
|
public static class ConfigServiceParser |
||||
|
{ |
||||
|
public static void HandleServiceMapping(YamlSequenceNode yamlSequenceNode, List<ConfigService> services) |
||||
|
{ |
||||
|
foreach (var child in yamlSequenceNode.Children) |
||||
|
{ |
||||
|
YamlParser.ThrowIfNotYamlMapping(child); |
||||
|
var service = new ConfigService(); |
||||
|
HandleServiceNameMapping((YamlMappingNode)child, service); |
||||
|
services.Add(service); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void HandleServiceNameMapping(YamlMappingNode yamlMappingNode, ConfigService service) |
||||
|
{ |
||||
|
foreach (var child in yamlMappingNode!.Children) |
||||
|
{ |
||||
|
var key = YamlParser.GetScalarValue(child.Key); |
||||
|
|
||||
|
switch (key) |
||||
|
{ |
||||
|
case "name": |
||||
|
service.Name = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
case "external": |
||||
|
if (!bool.TryParse(YamlParser.GetScalarValue(key, child.Value), out var external)) |
||||
|
{ |
||||
|
throw new TyeYamlException(child.Value.Start, CoreStrings.FormatMustBeABoolean(key)); |
||||
|
} |
||||
|
service.External = external; |
||||
|
break; |
||||
|
case "image": |
||||
|
service.Image = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
case "project": |
||||
|
service.Project = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
case "build": |
||||
|
if (!bool.TryParse(YamlParser.GetScalarValue(key, child.Value), out var build)) |
||||
|
{ |
||||
|
throw new TyeYamlException(child.Value.Start, CoreStrings.FormatMustBeABoolean(key)); |
||||
|
} |
||||
|
service.Build = build; |
||||
|
break; |
||||
|
case "executable": |
||||
|
service.Executable = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
case "workingDirectory": |
||||
|
service.WorkingDirectory = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
case "args": |
||||
|
service.Args = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
case "replicas": |
||||
|
if (!int.TryParse(YamlParser.GetScalarValue(key, child.Value), out var replicas)) |
||||
|
{ |
||||
|
throw new TyeYamlException(child.Value.Start, CoreStrings.FormatMustBeAnInteger(key)); |
||||
|
} |
||||
|
|
||||
|
if (replicas < 0) |
||||
|
{ |
||||
|
throw new TyeYamlException(child.Value.Start, CoreStrings.FormatMustBePositive(key)); |
||||
|
} |
||||
|
|
||||
|
service.Replicas = replicas; |
||||
|
break; |
||||
|
case "bindings": |
||||
|
if (child.Value.NodeType != YamlNodeType.Sequence) |
||||
|
{ |
||||
|
throw new TyeYamlException(child.Value.Start, CoreStrings.FormatExpectedYamlSequence(key)); |
||||
|
} |
||||
|
|
||||
|
HandleServiceBindings((child.Value as YamlSequenceNode)!, service.Bindings); |
||||
|
break; |
||||
|
case "volumes": |
||||
|
if (child.Value.NodeType != YamlNodeType.Sequence) |
||||
|
{ |
||||
|
throw new TyeYamlException(child.Value.Start, CoreStrings.FormatExpectedYamlSequence(key)); |
||||
|
} |
||||
|
|
||||
|
HandleServiceVolumes((child.Value as YamlSequenceNode)!, service.Volumes); |
||||
|
break; |
||||
|
case "env": |
||||
|
case "configuration": |
||||
|
if (child.Value.NodeType != YamlNodeType.Sequence) |
||||
|
{ |
||||
|
throw new TyeYamlException(child.Value.Start, CoreStrings.FormatExpectedYamlSequence(key)); |
||||
|
} |
||||
|
HandleServiceConfiguration((child.Value as YamlSequenceNode)!, service.Configuration); |
||||
|
break; |
||||
|
default: |
||||
|
throw new TyeYamlException(child.Key.Start, CoreStrings.FormatUnrecognizedKey(key)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void HandleServiceBindings(YamlSequenceNode yamlSequenceNode, List<ConfigServiceBinding> bindings) |
||||
|
{ |
||||
|
foreach (var child in yamlSequenceNode.Children) |
||||
|
{ |
||||
|
YamlParser.ThrowIfNotYamlMapping(child); |
||||
|
var binding = new ConfigServiceBinding(); |
||||
|
HandleServiceBindingNameMapping((YamlMappingNode)child, binding); |
||||
|
bindings.Add(binding); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void HandleServiceBindingNameMapping(YamlMappingNode yamlMappingNode, ConfigServiceBinding binding) |
||||
|
{ |
||||
|
foreach (var child in yamlMappingNode.Children) |
||||
|
{ |
||||
|
var key = YamlParser.GetScalarValue(child.Key); |
||||
|
|
||||
|
switch (key) |
||||
|
{ |
||||
|
case "name": |
||||
|
binding.Name = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
case "connectionString": |
||||
|
binding.ConnectionString = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
case "port": |
||||
|
if (!int.TryParse(YamlParser.GetScalarValue(key, child.Value), out var port)) |
||||
|
{ |
||||
|
throw new TyeYamlException(child.Value.Start, CoreStrings.FormatMustBeAnInteger(key)); |
||||
|
} |
||||
|
|
||||
|
binding.Port = port; |
||||
|
break; |
||||
|
case "containerPort": |
||||
|
if (!int.TryParse(YamlParser.GetScalarValue(key, child.Value), out var containerPort)) |
||||
|
{ |
||||
|
throw new TyeYamlException(child.Value.Start, CoreStrings.FormatMustBeAnInteger(key)); |
||||
|
} |
||||
|
|
||||
|
binding.ContainerPort = containerPort; |
||||
|
break; |
||||
|
case "host": |
||||
|
binding.Host = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
case "protocol": |
||||
|
binding.Protocol = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
default: |
||||
|
throw new TyeYamlException(child.Key.Start, CoreStrings.FormatUnrecognizedKey(key)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void HandleServiceVolumes(YamlSequenceNode yamlSequenceNode, List<ConfigVolume> volumes) |
||||
|
{ |
||||
|
foreach (var child in yamlSequenceNode.Children) |
||||
|
{ |
||||
|
YamlParser.ThrowIfNotYamlMapping(child); |
||||
|
var volume = new ConfigVolume(); |
||||
|
HandleServiceVolumeNameMapping((YamlMappingNode)child, volume); |
||||
|
volumes.Add(volume); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void HandleServiceVolumeNameMapping(YamlMappingNode yamlMappingNode, ConfigVolume volume) |
||||
|
{ |
||||
|
foreach (var child in yamlMappingNode!.Children) |
||||
|
{ |
||||
|
var key = YamlParser.GetScalarValue(child.Key); |
||||
|
|
||||
|
switch (key) |
||||
|
{ |
||||
|
case "name": |
||||
|
volume.Name = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
case "source": |
||||
|
volume.Source = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
case "target": |
||||
|
volume.Target = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
default: |
||||
|
throw new TyeYamlException(child.Key.Start, CoreStrings.FormatUnrecognizedKey(key)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void HandleServiceConfiguration(YamlSequenceNode yamlSequenceNode, List<ConfigConfigurationSource> configuration) |
||||
|
{ |
||||
|
foreach (var child in yamlSequenceNode.Children) |
||||
|
{ |
||||
|
YamlParser.ThrowIfNotYamlMapping(child); |
||||
|
var config = new ConfigConfigurationSource(); |
||||
|
HandleServiceConfigurationNameMapping((YamlMappingNode)child, config); |
||||
|
configuration.Add(config); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void HandleServiceConfigurationNameMapping(YamlMappingNode yamlMappingNode, ConfigConfigurationSource config) |
||||
|
{ |
||||
|
foreach (var child in yamlMappingNode!.Children) |
||||
|
{ |
||||
|
var key = YamlParser.GetScalarValue(child.Key); |
||||
|
|
||||
|
switch (key) |
||||
|
{ |
||||
|
case "name": |
||||
|
config.Name = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
case "value": |
||||
|
config.Value = YamlParser.GetScalarValue(key, child.Value); |
||||
|
break; |
||||
|
default: |
||||
|
throw new TyeYamlException(child.Key.Start, CoreStrings.FormatUnrecognizedKey(key)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
// See the LICENSE file in the project root for more information.
|
||||
|
|
||||
|
using System; |
||||
|
using YamlDotNet.Core; |
||||
|
|
||||
|
namespace Tye.Serialization |
||||
|
{ |
||||
|
public class TyeYamlException : Exception |
||||
|
{ |
||||
|
public TyeYamlException(string message) |
||||
|
: base(message) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public TyeYamlException(Mark start, string message) |
||||
|
: this(start, message, null) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public TyeYamlException(Mark start, string message, Exception? innerException) |
||||
|
: base($"Error parsing tye.yaml: ({start.Line}, {start.Column}): {message}", innerException) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public TyeYamlException(string message, Exception? inner) |
||||
|
: base(message, inner) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,118 @@ |
|||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
// See the LICENSE file in the project root for more information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using Microsoft.Tye.ConfigModel; |
||||
|
using YamlDotNet.Core; |
||||
|
using YamlDotNet.RepresentationModel; |
||||
|
|
||||
|
namespace Tye.Serialization |
||||
|
{ |
||||
|
public class YamlParser : IDisposable |
||||
|
{ |
||||
|
private YamlStream _yamlStream; |
||||
|
private FileInfo? _fileInfo; |
||||
|
private TextReader _reader; |
||||
|
|
||||
|
public YamlParser(string yamlContent) |
||||
|
: this(new StringReader(yamlContent)) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public YamlParser(FileInfo fileInfo) |
||||
|
: this(fileInfo.OpenText()) |
||||
|
{ |
||||
|
_fileInfo = fileInfo; |
||||
|
} |
||||
|
|
||||
|
internal YamlParser(TextReader reader) |
||||
|
{ |
||||
|
_reader = reader; |
||||
|
_yamlStream = new YamlStream(); |
||||
|
} |
||||
|
|
||||
|
public ConfigApplication ParseConfigApplication() |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
_yamlStream.Load(_reader); |
||||
|
} |
||||
|
catch (YamlException ex) |
||||
|
{ |
||||
|
throw new TyeYamlException(ex.Start, "Unable to parse tye.yaml. See inner exception.", ex); |
||||
|
} |
||||
|
|
||||
|
var app = new ConfigApplication(); |
||||
|
|
||||
|
// TODO assuming first document.
|
||||
|
var document = _yamlStream.Documents[0]; |
||||
|
var node = document.RootNode; |
||||
|
ThrowIfNotYamlMapping(node); |
||||
|
ConfigApplicationParser.HandleConfigApplication((YamlMappingNode)node, app); |
||||
|
|
||||
|
app.Source = _fileInfo!; |
||||
|
|
||||
|
// TODO confirm if these are ever null.
|
||||
|
foreach (var service in app.Services) |
||||
|
{ |
||||
|
service.Bindings ??= new List<ConfigServiceBinding>(); |
||||
|
service.Configuration ??= new List<ConfigConfigurationSource>(); |
||||
|
service.Volumes ??= new List<ConfigVolume>(); |
||||
|
} |
||||
|
|
||||
|
foreach (var ingress in app.Ingress) |
||||
|
{ |
||||
|
ingress.Bindings ??= new List<ConfigIngressBinding>(); |
||||
|
ingress.Rules ??= new List<ConfigIngressRule>(); |
||||
|
} |
||||
|
|
||||
|
return app; |
||||
|
} |
||||
|
|
||||
|
public static string GetScalarValue(YamlNode node) |
||||
|
{ |
||||
|
if (node.NodeType != YamlNodeType.Scalar) |
||||
|
{ |
||||
|
throw new TyeYamlException(node.Start, |
||||
|
CoreStrings.FormatUnexpectedType(YamlNodeType.Scalar.ToString(), node.NodeType.ToString())); |
||||
|
} |
||||
|
|
||||
|
return ((YamlScalarNode)node).Value!; |
||||
|
} |
||||
|
|
||||
|
public static string GetScalarValue(string key, YamlNode node) |
||||
|
{ |
||||
|
if (node.NodeType != YamlNodeType.Scalar) |
||||
|
{ |
||||
|
throw new TyeYamlException(node.Start, CoreStrings.FormatExpectedYamlScalar(key)); |
||||
|
} |
||||
|
|
||||
|
return ((YamlScalarNode)node).Value!; |
||||
|
} |
||||
|
|
||||
|
public static void ThrowIfNotYamlSequence(string key, YamlNode node) |
||||
|
{ |
||||
|
if (node.NodeType != YamlNodeType.Sequence) |
||||
|
{ |
||||
|
throw new TyeYamlException(node.Start, CoreStrings.FormatExpectedYamlSequence(key)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void ThrowIfNotYamlMapping(YamlNode node) |
||||
|
{ |
||||
|
if (node.NodeType != YamlNodeType.Mapping) |
||||
|
{ |
||||
|
throw new TyeYamlException(node.Start, |
||||
|
CoreStrings.FormatUnexpectedType(YamlNodeType.Mapping.ToString(), node.NodeType.ToString())); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_reader.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netcoreapp3.1</TargetFramework> |
||||
|
<AssemblyName>Microsoft.Tye.UnitTests</AssemblyName> |
||||
|
<IsTestProject>true</IsTestProject> |
||||
|
<IsUnitTestProject>true</IsUnitTestProject> |
||||
|
<IsPackable>false</IsPackable> |
||||
|
<TestRunnerName>XUnit</TestRunnerName> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="coverlet.collector" Version="1.0.1" /> |
||||
|
<PackageReference Include="coverlet.msbuild" Version="2.8.0"> |
||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
||||
|
<PrivateAssets>all</PrivateAssets> |
||||
|
</PackageReference> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\src\Microsoft.Tye.Hosting\Microsoft.Tye.Hosting.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Microsoft.Tye.Core\Microsoft.Tye.Core.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\tye\tye.csproj" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -0,0 +1,582 @@ |
|||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using Microsoft.Tye.ConfigModel; |
||||
|
using Tye; |
||||
|
using Tye.Serialization; |
||||
|
using Xunit; |
||||
|
using YamlDotNet.RepresentationModel; |
||||
|
using YamlDotNet.Serialization; |
||||
|
using YamlDotNet.Serialization.NamingConventions; |
||||
|
|
||||
|
namespace Microsoft.Tye.UnitTests |
||||
|
{ |
||||
|
public class TyeDeserializationTests |
||||
|
{ |
||||
|
private IDeserializer _deserializer; |
||||
|
|
||||
|
public TyeDeserializationTests() |
||||
|
{ |
||||
|
_deserializer = new DeserializerBuilder() |
||||
|
.WithNamingConvention(CamelCaseNamingConvention.Instance) |
||||
|
.Build(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void ComprehensionalTest() |
||||
|
{ |
||||
|
var input = @"
|
||||
|
name: apps-with-ingress |
||||
|
registry: myregistry |
||||
|
extensions: |
||||
|
- name: dapr |
||||
|
ingress: |
||||
|
- name: ingress |
||||
|
bindings: |
||||
|
- port: 8080 |
||||
|
protocol: http |
||||
|
name: foo |
||||
|
rules: |
||||
|
- path: /A |
||||
|
service: appA |
||||
|
- path: /B |
||||
|
service: appB |
||||
|
- host: a.example.com |
||||
|
service: appA |
||||
|
- host: b.example.com |
||||
|
service: appB |
||||
|
replicas: 2 |
||||
|
services: |
||||
|
- name: appA |
||||
|
project: ApplicationA/ApplicationA.csproj |
||||
|
replicas: 2 |
||||
|
external: false |
||||
|
image: abc |
||||
|
build: false |
||||
|
executable: test.exe |
||||
|
workingDirectory: ApplicationA/ |
||||
|
args: a b c |
||||
|
env: |
||||
|
- name: POSTGRES_PASSWORD |
||||
|
value: ""test"" |
||||
|
- name: POSTGRES_PASSWORD2 |
||||
|
value: ""test2"" |
||||
|
volumes: |
||||
|
- name: volume |
||||
|
source: /data |
||||
|
target: /data |
||||
|
bindings: |
||||
|
- name: test |
||||
|
port: 4444 |
||||
|
connectionString: asdf |
||||
|
containerPort: 80 |
||||
|
host: localhost |
||||
|
protocol: http |
||||
|
- name: appB |
||||
|
project: ApplicationB/ApplicationB.csproj |
||||
|
replicas: 2";
|
||||
|
|
||||
|
using var parser = new YamlParser(input); |
||||
|
var app = parser.ParseConfigApplication(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void IngressIsSetCorrectly() |
||||
|
{ |
||||
|
var input = @"
|
||||
|
ingress: |
||||
|
- name: ingress |
||||
|
bindings: |
||||
|
- port: 8080 |
||||
|
protocol: http |
||||
|
name: foo |
||||
|
rules: |
||||
|
- path: /A |
||||
|
service: appA |
||||
|
- path: /B |
||||
|
service: appB |
||||
|
- host: a.example.com |
||||
|
service: appA |
||||
|
- host: b.example.com |
||||
|
service: appB |
||||
|
replicas: 2";
|
||||
|
|
||||
|
using var parser = new YamlParser(input); |
||||
|
var app = parser.ParseConfigApplication(); |
||||
|
|
||||
|
var expected = _deserializer.Deserialize<ConfigApplication>(new StringReader(input)); |
||||
|
|
||||
|
foreach (var ingress in app.Ingress) |
||||
|
{ |
||||
|
var otherIngress = expected |
||||
|
.Ingress |
||||
|
.Where(o => o.Name == ingress.Name) |
||||
|
.Single(); |
||||
|
Assert.NotNull(otherIngress); |
||||
|
Assert.Equal(otherIngress.Replicas, ingress.Replicas); |
||||
|
|
||||
|
foreach (var rule in ingress.Rules) |
||||
|
{ |
||||
|
var otherRule = otherIngress |
||||
|
.Rules |
||||
|
.Where(o => o.Path == rule.Path && o.Host == rule.Host && o.Service == rule.Service) |
||||
|
.Single(); |
||||
|
Assert.NotNull(otherRule); |
||||
|
} |
||||
|
|
||||
|
foreach (var binding in ingress.Bindings) |
||||
|
{ |
||||
|
var otherBinding = otherIngress |
||||
|
.Bindings |
||||
|
.Where(o => o.Name == binding.Name && o.Port == binding.Port && o.Protocol == binding.Protocol) |
||||
|
.Single(); |
||||
|
|
||||
|
Assert.NotNull(otherBinding); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void ServicesSetCorrectly() |
||||
|
{ |
||||
|
var input = @"services:
|
||||
|
- name: appA |
||||
|
project: ApplicationA/ApplicationA.csproj |
||||
|
replicas: 2 |
||||
|
external: false |
||||
|
image: abc |
||||
|
build: false |
||||
|
executable: test.exe |
||||
|
workingDirectory: ApplicationA/ |
||||
|
args: a b c |
||||
|
env: |
||||
|
- name: POSTGRES_PASSWORD |
||||
|
value: ""test"" |
||||
|
- name: POSTGRES_PASSWORD2 |
||||
|
value: ""test2"" |
||||
|
volumes: |
||||
|
- name: volume |
||||
|
source: /data |
||||
|
target: /data |
||||
|
bindings: |
||||
|
- name: test |
||||
|
port: 4444 |
||||
|
connectionString: asdf |
||||
|
containerPort: 80 |
||||
|
host: localhost |
||||
|
protocol: http |
||||
|
- name: appB |
||||
|
project: ApplicationB/ApplicationB.csproj |
||||
|
replicas: 2";
|
||||
|
using var parser = new YamlParser(input); |
||||
|
var app = parser.ParseConfigApplication(); |
||||
|
|
||||
|
var expected = _deserializer.Deserialize<ConfigApplication>(new StringReader(input)); |
||||
|
|
||||
|
foreach (var service in app.Services) |
||||
|
{ |
||||
|
var otherService = expected |
||||
|
.Services |
||||
|
.Where(o => o.Name == service.Name) |
||||
|
.Single(); |
||||
|
Assert.NotNull(otherService); |
||||
|
Assert.Equal(otherService.Args, service.Args); |
||||
|
Assert.Equal(otherService.Build, service.Build); |
||||
|
Assert.Equal(otherService.Executable, service.Executable); |
||||
|
Assert.Equal(otherService.External, service.External); |
||||
|
Assert.Equal(otherService.Image, service.Image); |
||||
|
Assert.Equal(otherService.Project, service.Project); |
||||
|
Assert.Equal(otherService.Replicas, service.Replicas); |
||||
|
Assert.Equal(otherService.WorkingDirectory, service.WorkingDirectory); |
||||
|
|
||||
|
foreach (var binding in service.Bindings) |
||||
|
{ |
||||
|
var otherBinding = otherService.Bindings |
||||
|
.Where(o => o.Name == binding.Name |
||||
|
&& o.Port == binding.Port |
||||
|
&& o.Protocol == binding.Protocol |
||||
|
&& o.ConnectionString == binding.ConnectionString |
||||
|
&& o.ContainerPort == binding.ContainerPort |
||||
|
&& o.Host == binding.Host) |
||||
|
.Single(); |
||||
|
|
||||
|
Assert.NotNull(otherBinding); |
||||
|
} |
||||
|
|
||||
|
foreach (var binding in service.Bindings) |
||||
|
{ |
||||
|
var otherBinding = otherService.Bindings |
||||
|
.Where(o => o.Name == binding.Name |
||||
|
&& o.Port == binding.Port |
||||
|
&& o.Protocol == binding.Protocol |
||||
|
&& o.ConnectionString == binding.ConnectionString |
||||
|
&& o.ContainerPort == binding.ContainerPort |
||||
|
&& o.Host == binding.Host) |
||||
|
.Single(); |
||||
|
|
||||
|
Assert.NotNull(otherBinding); |
||||
|
} |
||||
|
|
||||
|
foreach (var config in service.Configuration) |
||||
|
{ |
||||
|
var otherConfig = otherService.Configuration |
||||
|
.Where(o => o.Name == config.Name |
||||
|
&& o.Value == config.Value) |
||||
|
.Single(); |
||||
|
|
||||
|
Assert.NotNull(otherConfig); |
||||
|
} |
||||
|
|
||||
|
foreach (var volume in service.Volumes) |
||||
|
{ |
||||
|
var otherVolume = otherService.Volumes |
||||
|
.Where(o => o.Name == volume.Name |
||||
|
&& o.Target == volume.Target |
||||
|
&& o.Source == volume.Source) |
||||
|
.Single(); |
||||
|
Assert.NotNull(otherVolume); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void ExtensionsTest() |
||||
|
{ |
||||
|
var input = @"
|
||||
|
extensions: |
||||
|
- name: dapr";
|
||||
|
using var parser = new YamlParser(input); |
||||
|
|
||||
|
var app = parser.ParseConfigApplication(); |
||||
|
|
||||
|
Assert.Equal("dapr", app.Extensions.Single()["name"]); |
||||
|
|
||||
|
var expected = _deserializer.Deserialize<ConfigApplication>(new StringReader(input)); |
||||
|
|
||||
|
Assert.Equal(expected.Extensions.Count, app.Extensions.Count); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void VotingTest() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"name: VotingSample
|
||||
|
registry: myregistry |
||||
|
services: |
||||
|
- name: vote |
||||
|
project: vote/vote.csproj |
||||
|
- name: redis |
||||
|
image: redis |
||||
|
bindings: |
||||
|
- port: 6379 |
||||
|
- name: worker |
||||
|
project: worker/worker.csproj |
||||
|
- name: postgres |
||||
|
image: postgres |
||||
|
env: |
||||
|
- name: POSTGRES_PASSWORD |
||||
|
value: ""test"" |
||||
|
bindings: |
||||
|
- port: 5432 |
||||
|
- name: results |
||||
|
project: results/results.csproj");
|
||||
|
var app = parser.ParseConfigApplication(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
[Fact] |
||||
|
public void UnrecognizedConfigApplicationField_ThrowException() |
||||
|
{ |
||||
|
using var parser = new YamlParser("asdf: 123"); |
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatUnrecognizedKey("asdf"), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Replicas_MustBeInteger() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"services:
|
||||
|
- name: app |
||||
|
replicas: asdf");
|
||||
|
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatMustBeAnInteger("replicas"), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Replicas_MustBePositive() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"services:
|
||||
|
- name: app |
||||
|
replicas: -1");
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatMustBePositive("replicas"), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Name_MustBeScalar() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"name:
|
||||
|
- a: b");
|
||||
|
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatExpectedYamlScalar("name"), exception.Message); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
[Fact] |
||||
|
public void YamlIsCaseSensitive() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"Name: abc"); |
||||
|
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatUnrecognizedKey("Name"), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Registry_MustBeScalar() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"registry:
|
||||
|
- a: b");
|
||||
|
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatExpectedYamlScalar("registry"), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Ingress_MustBeSequence() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"ingress: a"); |
||||
|
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatExpectedYamlSequence("ingress"), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Services_MustBeSequence() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"services: a"); |
||||
|
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatExpectedYamlSequence("services"), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void ConfigApplication_MustBeMappings() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"- name: app
|
||||
|
replicas: -1");
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatUnexpectedType(YamlNodeType.Mapping.ToString(), YamlNodeType.Sequence.ToString()), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Services_MustBeMappings() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"services:
|
||||
|
- name");
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatUnexpectedType(YamlNodeType.Mapping.ToString(), YamlNodeType.Scalar.ToString()), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Ingress_MustBeMappings() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"ingress:
|
||||
|
- name");
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatUnexpectedType(YamlNodeType.Mapping.ToString(), YamlNodeType.Scalar.ToString()), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Ingress_Replicas_MustBeInteger() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"ingress:
|
||||
|
- replicas: asdf");
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatMustBeAnInteger("replicas"), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Ingress_Replicas_MustBePositive() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"ingress:
|
||||
|
- replicas: -1");
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatMustBePositive("replicas"), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Ingress_UnrecognizedKey() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"ingress:
|
||||
|
- abc: abc");
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatUnrecognizedKey("abc"), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Ingress_Rules_MustSequence() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"ingress:
|
||||
|
- rules: abc");
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatExpectedYamlSequence("rules"), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Ingress_Rules_MustBeMappings() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"ingress:
|
||||
|
- rules: |
||||
|
- abc");
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatUnexpectedType(YamlNodeType.Mapping.ToString(), YamlNodeType.Scalar.ToString()), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Ingress_Bindings_MustBeMappings() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"ingress:
|
||||
|
- bindings: |
||||
|
- abc");
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatUnexpectedType(YamlNodeType.Mapping.ToString(), YamlNodeType.Scalar.ToString()), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Ingress_RulesMapping_UnrecognizedKey() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"ingress:
|
||||
|
- rules: |
||||
|
- abc: 123");
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatUnrecognizedKey("abc"), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Ingress_Bindings_MustSequence() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"ingress:
|
||||
|
- bindings: abc");
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatExpectedYamlSequence("bindings"), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Ingress_Bindings_Port_MustBeInteger() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"ingress:
|
||||
|
- name: ingress |
||||
|
bindings: |
||||
|
- port: abc |
||||
|
protocol: http |
||||
|
name: foo");
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatMustBeAnInteger("port"), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Ingress_Bindings_UnrecognizedKey() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"ingress:
|
||||
|
- name: ingress |
||||
|
bindings: |
||||
|
- abc: abc");
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatUnrecognizedKey("abc"), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Services_External_MustBeBool() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"services:
|
||||
|
- name: ingress |
||||
|
external: abc");
|
||||
|
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatMustBeABoolean("external"), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Services_Build_MustBeBool() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"services:
|
||||
|
- name: ingress |
||||
|
build: abc");
|
||||
|
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatMustBeABoolean("build"), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Services_Bindings_MustBeSequence() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"services:
|
||||
|
- name: ingress |
||||
|
bindings: abc");
|
||||
|
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatExpectedYamlSequence("bindings"), exception.Message); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
[Fact] |
||||
|
public void Services_Volumes_MustBeSequence() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"services:
|
||||
|
- name: ingress |
||||
|
volumes: abc");
|
||||
|
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatExpectedYamlSequence("volumes"), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Services_Env_MustBeSequence() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"services:
|
||||
|
- name: ingress |
||||
|
env: abc");
|
||||
|
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatExpectedYamlSequence("env"), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Services_UnrecognizedKey() |
||||
|
{ |
||||
|
using var parser = new YamlParser( |
||||
|
@"services:
|
||||
|
- name: ingress |
||||
|
env: abc");
|
||||
|
|
||||
|
var exception = Assert.Throws<TyeYamlException>(() => parser.ParseConfigApplication()); |
||||
|
Assert.Contains(CoreStrings.FormatExpectedYamlSequence("env"), exception.Message); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,133 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
using Tye; |
||||
|
using Tye.Serialization; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Microsoft.Tye.UnitTests |
||||
|
{ |
||||
|
public class TyeDeserializationValidationTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void MultipleIngressBindingsMustHaveNames() |
||||
|
{ |
||||
|
var input = @"
|
||||
|
ingress: |
||||
|
- name: ingress |
||||
|
bindings: |
||||
|
- port: 8080 |
||||
|
protocol: http |
||||
|
- port: 8080 |
||||
|
protocol: http";
|
||||
|
|
||||
|
using var parser = new YamlParser(input); |
||||
|
var app = parser.ParseConfigApplication(); |
||||
|
var exception = Assert.Throws<TyeYamlException>(() => app.Validate()); |
||||
|
Assert.Contains(CoreStrings.MultipleIngressBindingWithoutName, exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void MultipleServicesBindingsMustHaveNames() |
||||
|
{ |
||||
|
var input = @"
|
||||
|
services: |
||||
|
- name: app |
||||
|
bindings: |
||||
|
- port: 8080 |
||||
|
protocol: http |
||||
|
- port: 8080 |
||||
|
protocol: http";
|
||||
|
|
||||
|
using var parser = new YamlParser(input); |
||||
|
var app = parser.ParseConfigApplication(); |
||||
|
var exception = Assert.Throws<TyeYamlException>(() => app.Validate()); |
||||
|
Assert.Contains(CoreStrings.MultipleServiceBindingsWithoutName, exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void MultipleIngressBindingsMustUniqueNames() |
||||
|
{ |
||||
|
var input = @"
|
||||
|
ingress: |
||||
|
- name: ingress |
||||
|
bindings: |
||||
|
- port: 8080 |
||||
|
protocol: http |
||||
|
name: a |
||||
|
- port: 8080 |
||||
|
protocol: http |
||||
|
name: a";
|
||||
|
|
||||
|
using var parser = new YamlParser(input); |
||||
|
var app = parser.ParseConfigApplication(); |
||||
|
var exception = Assert.Throws<TyeYamlException>(() => app.Validate()); |
||||
|
Assert.Contains(CoreStrings.MultipleIngressBindingWithSameName, exception.Message); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
[Fact] |
||||
|
public void IngressProtocolsShouldBeHttpOrHttps() |
||||
|
{ |
||||
|
var input = @"
|
||||
|
ingress: |
||||
|
- name: ingress |
||||
|
bindings: |
||||
|
- port: 8080 |
||||
|
protocol: tls |
||||
|
name: a";
|
||||
|
|
||||
|
using var parser = new YamlParser(input); |
||||
|
var app = parser.ParseConfigApplication(); |
||||
|
var exception = Assert.Throws<TyeYamlException>(() => app.Validate()); |
||||
|
Assert.Contains(CoreStrings.IngressBindingMustBeHttpOrHttps, exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void MultipleServicesBindingsMustUniqueNames() |
||||
|
{ |
||||
|
var input = @"
|
||||
|
services: |
||||
|
- name: app |
||||
|
bindings: |
||||
|
- port: 8080 |
||||
|
protocol: http |
||||
|
name: a |
||||
|
- port: 8080 |
||||
|
protocol: http |
||||
|
name: a";
|
||||
|
|
||||
|
using var parser = new YamlParser(input); |
||||
|
var app = parser.ParseConfigApplication(); |
||||
|
var exception = Assert.Throws<TyeYamlException>(() => app.Validate()); |
||||
|
Assert.Contains(CoreStrings.MultipleServiceBindingsWithSameName, exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void IngressMustReferenceService() |
||||
|
{ |
||||
|
var input = @"
|
||||
|
ingress: |
||||
|
- name: ingress |
||||
|
bindings: |
||||
|
- port: 8080 |
||||
|
protocol: http |
||||
|
name: foo |
||||
|
rules: |
||||
|
- path: /A |
||||
|
service: appA |
||||
|
- path: /B |
||||
|
service: appB |
||||
|
- host: a.example.com |
||||
|
service: appA |
||||
|
- host: b.example.com |
||||
|
service: appB |
||||
|
replicas: 2";
|
||||
|
|
||||
|
using var parser = new YamlParser(input); |
||||
|
var app = parser.ParseConfigApplication(); |
||||
|
var exception = Assert.Throws<TyeYamlException>(() => app.Validate()); |
||||
|
Assert.Contains(CoreStrings.IngressRuleMustReferenceService, exception.Message); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue