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); + } } }