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