diff --git a/src/Microsoft.Tye.Core/ConfigModel/ConfigApplication.cs b/src/Microsoft.Tye.Core/ConfigModel/ConfigApplication.cs
index 1144d879..fa6a448b 100644
--- a/src/Microsoft.Tye.Core/ConfigModel/ConfigApplication.cs
+++ b/src/Microsoft.Tye.Core/ConfigModel/ConfigApplication.cs
@@ -66,11 +66,26 @@ namespace Microsoft.Tye.ConfigModel
string.Join(Environment.NewLine, results.Select(r => r.ErrorMessage)));
}
- if (config.Services.Where(o => o.Name == service.Name).Count() > 1)
+ if (config.Services.Count(o => o.Name == service.Name) > 1)
{
throw new TyeYamlException(CoreStrings.ServiceMustHaveUniqueNames);
}
+ if (service.Project != null && service.Image != null)
+ {
+ throw new TyeYamlException(CoreStrings.FormatProjectImageExecutableExclusive("project", "image"));
+ }
+
+ if (service.Project != null && service.Executable != null)
+ {
+ throw new TyeYamlException(CoreStrings.FormatProjectImageExecutableExclusive("project", "executable"));
+ }
+
+ if (service.Image != null && service.Executable != null)
+ {
+ throw new TyeYamlException(CoreStrings.FormatProjectImageExecutableExclusive("image", "executable"));
+ }
+
foreach (var binding in service.Bindings)
{
context = new ValidationContext(binding);
@@ -83,11 +98,17 @@ namespace Microsoft.Tye.ConfigModel
if (string.IsNullOrEmpty(binding.Name) && service.Bindings.Count > 1)
{
- throw new TyeYamlException(CoreStrings.MultipleServiceBindingsWithoutName);
+ throw new TyeYamlException(CoreStrings.FormatMultipleBindingWithoutName("service"));
}
- if (service.Bindings.Where(o => o.Name == binding.Name).Count() > 1)
+
+ if (service.Bindings.Count(o => o.Name == binding.Name) > 1)
{
- throw new TyeYamlException(CoreStrings.MultipleServiceBindingsWithSameName);
+ throw new TyeYamlException(CoreStrings.FormatMultipleBindingWithSameName("service"));
+ }
+
+ if (service.Bindings.Count(o => o.Port == binding.Port) > 1)
+ {
+ throw new TyeYamlException(CoreStrings.FormatMultipleBindingWithSamePort("service"));
}
}
@@ -135,16 +156,20 @@ namespace Microsoft.Tye.ConfigModel
}
if (string.IsNullOrEmpty(binding.Name) && ingress.Bindings.Count > 1)
{
- throw new TyeYamlException(CoreStrings.MultipleIngressBindingWithoutName);
+ throw new TyeYamlException(CoreStrings.FormatMultipleBindingWithoutName("ingress"));
}
- if (ingress.Bindings.Where(o => o.Name == binding.Name).Count() > 1)
+ if (ingress.Bindings.Count(o => o.Name == binding.Name) > 1)
{
- throw new TyeYamlException(CoreStrings.MultipleIngressBindingWithSameName);
+ throw new TyeYamlException(CoreStrings.FormatMultipleBindingWithSameName("ingress"));
}
if (binding.Protocol != "http" && binding.Protocol != "https" && binding.Protocol != null)
{
throw new TyeYamlException(CoreStrings.IngressBindingMustBeHttpOrHttps);
}
+ if (ingress.Bindings.Count(o => o.Port == binding.Port) > 1)
+ {
+ throw new TyeYamlException(CoreStrings.FormatMultipleBindingWithSamePort("ingress"));
+ }
}
// Make sure all ingress rules have an associated service
@@ -158,7 +183,7 @@ namespace Microsoft.Tye.ConfigModel
string.Join(Environment.NewLine, results.Select(r => r.ErrorMessage)));
}
- if (config.Services.Where(o => o.Name == rule.Service).Count() != 1)
+ if (config.Services.Count(o => o.Name == rule.Service) != 1)
{
throw new TyeYamlException(CoreStrings.IngressRuleMustReferenceService);
}
diff --git a/src/Microsoft.Tye.Core/CoreStrings.resx b/src/Microsoft.Tye.Core/CoreStrings.resx
index 6c9b508d..0904ae0c 100644
--- a/src/Microsoft.Tye.Core/CoreStrings.resx
+++ b/src/Microsoft.Tye.Core/CoreStrings.resx
@@ -132,17 +132,14 @@
Ingress rules references a service that does not exist.
-
- Cannot have multiple ingress bindings without names. Please specify names for each ingress binding.
+
+ Cannot have multiple {0} bindings without names. Please specify names for each {0} binding.
-
- Cannot have multiple ingress bindings with the same name.
+
+ Cannot have multiple {0} bindings with the same name.
-
- Cannot have multiple service bindings without names. Please specify names for each service binding.
-
-
- Cannot have multiple service bindings with the same name.
+
+ Cannot have multiple {0} bindings with the same port.
"{value}" must be a boolean value (true/false).
@@ -153,6 +150,9 @@
"{value}" value cannot be negative.
+
+ Cannot have both "{0}" and "{1}" set for a service. Only one of project, image, and executable can be set for a given service.
+
Services must have unique names.
diff --git a/test/UnitTests/TyeDeserializationValidationTests.cs b/test/UnitTests/TyeDeserializationValidationTests.cs
index b246bc89..fab197b5 100644
--- a/test/UnitTests/TyeDeserializationValidationTests.cs
+++ b/test/UnitTests/TyeDeserializationValidationTests.cs
@@ -24,7 +24,7 @@ ingress:
using var parser = new YamlParser(input);
var app = parser.ParseConfigApplication();
var exception = Assert.Throws(() => app.Validate());
- Assert.Contains(CoreStrings.MultipleIngressBindingWithoutName, exception.Message);
+ Assert.Contains(CoreStrings.FormatMultipleBindingWithoutName("ingress"), exception.Message);
}
[Fact]
@@ -36,13 +36,13 @@ services:
bindings:
- port: 8080
protocol: http
- - port: 8080
+ - port: 8081
protocol: http";
using var parser = new YamlParser(input);
var app = parser.ParseConfigApplication();
var exception = Assert.Throws(() => app.Validate());
- Assert.Contains(CoreStrings.MultipleServiceBindingsWithoutName, exception.Message);
+ Assert.Contains(CoreStrings.FormatMultipleBindingWithoutName("service"), exception.Message);
}
[Fact]
@@ -62,7 +62,7 @@ ingress:
using var parser = new YamlParser(input);
var app = parser.ParseConfigApplication();
var exception = Assert.Throws(() => app.Validate());
- Assert.Contains(CoreStrings.MultipleIngressBindingWithSameName, exception.Message);
+ Assert.Contains(CoreStrings.FormatMultipleBindingWithSameName("ingress"), exception.Message);
}
@@ -100,7 +100,7 @@ services:
using var parser = new YamlParser(input);
var app = parser.ParseConfigApplication();
var exception = Assert.Throws(() => app.Validate());
- Assert.Contains(CoreStrings.MultipleServiceBindingsWithSameName, exception.Message);
+ Assert.Contains(CoreStrings.FormatMultipleBindingWithSameName("service"), exception.Message);
}
[Fact]
@@ -129,5 +129,68 @@ ingress:
var exception = Assert.Throws(() => app.Validate());
Assert.Contains(CoreStrings.IngressRuleMustReferenceService, exception.Message);
}
+
+ [Fact]
+ public void IngressMustHaveUniquePorts()
+ {
+ var input = @"
+ingress:
+ - name: ingress
+ bindings:
+ - port: 8080
+ protocol: http
+ name: foo
+ - port: 8080
+ protocol: https
+ name: bar";
+
+ using var parser = new YamlParser(input);
+ var app = parser.ParseConfigApplication();
+ var exception = Assert.Throws(() => app.Validate());
+ Assert.Contains(CoreStrings.FormatMultipleBindingWithSamePort("ingress"), exception.Message);
+ }
+
+
+ [Fact]
+ public void ServicesMustHaveUniquePorts()
+ {
+ var input = @"
+services:
+ - name: app
+ bindings:
+ - port: 8080
+ protocol: http
+ name: a
+ - port: 8080
+ protocol: https
+ name: b";
+
+ using var parser = new YamlParser(input);
+ var app = parser.ParseConfigApplication();
+ var exception = Assert.Throws(() => app.Validate());
+ Assert.Contains(CoreStrings.FormatMultipleBindingWithSamePort("service"), exception.Message);
+ }
+
+
+ [Theory]
+ [InlineData("image", "executable")]
+ [InlineData("project", "image")]
+ [InlineData("project", "executable")]
+ public void ImageExeProjectMutuallyExclusive(string a, string b)
+ {
+ var input = @$"
+services:
+ - name: app
+ {a}: foo
+ {b}: baz
+ bindings:
+ - port: 8080
+ protocol: http";
+
+ using var parser = new YamlParser(input);
+ var app = parser.ParseConfigApplication();
+ var exception = Assert.Throws(() => app.Validate());
+ Assert.Contains(CoreStrings.FormatProjectImageExecutableExclusive(a, b), exception.Message);
+ }
}
}