Browse Source

Add buildProperties to project service configuration (#383)

* Add buildArgs to service configuration and capture build configuration as global property to setup msbuild project

* Fix format

* Fix - Error CS8600: Converting null literal or possible null value to non-nullable type.

* Improve configuration format for build arguments

* Fix whitespace format

* Fix error CS8618: Non-nullable property 'Properties' is uninitialized. Consider declaring the property as nullable.

* Fix error CS8601: Possible null reference assignment.

* Translate non first class properties as /p:{Key}={Value} into the build command

* Fix property translation

* All properties are used to create the msbuild project

* Change the name (to BuildProperties) and the type (to List<BuildProperty>) of ConfigService property to load build properties

* Add support of build properties when tye run with --docker option

* Fix ComprehensionalTest

* Add tests to verify the output directory for the corresponding build configuration

* Fix whitespace format

* Override the correct CreateTestCasesForTheory to fix error CS0618

* Remove the usage of BuildPropertiesToOptionsMap and fix the code format
pull/396/head
Igr Alexánder Fernández Saúco 6 years ago
committed by GitHub
parent
commit
d429d0b7bc
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      src/Microsoft.Tye.Core/ApplicationFactory.cs
  2. 17
      src/Microsoft.Tye.Core/ConfigModel/BuildProperty.cs
  3. 1
      src/Microsoft.Tye.Core/ConfigModel/ConfigService.cs
  4. 2
      src/Microsoft.Tye.Core/ProjectReader.cs
  5. 2
      src/Microsoft.Tye.Core/ProjectServiceBuilder.cs
  6. 38
      src/Microsoft.Tye.Core/Serialization/ConfigServiceParser.cs
  7. 3
      src/Microsoft.Tye.Hosting/Model/ProjectRunInfo.cs
  8. 7
      src/Microsoft.Tye.Hosting/ProcessRunner.cs
  9. 6
      src/Microsoft.Tye.Hosting/TransformProjectsIntoContainers.cs
  10. 3
      src/tye/tye.csproj
  11. 2
      test/E2ETest/E2ETest.csproj
  12. 16
      test/E2ETest/Infrastructure/ConditionalTheoryAttribute.cs
  13. 41
      test/E2ETest/Infrastructure/ConditionalTheoryDiscoverer.cs
  14. 42
      test/E2ETest/TyeRunTests.cs
  15. 14
      test/E2ETest/testassets/projects/frontend-backend/tye-debug-configuration.yaml
  16. 14
      test/E2ETest/testassets/projects/frontend-backend/tye-release-configuration.yaml
  17. 3
      test/UnitTests/TyeDeserializationTests.cs

26
src/Microsoft.Tye.Core/ApplicationFactory.cs

@ -59,16 +59,17 @@ namespace Microsoft.Tye
project.Build = configService.Build ?? true;
project.Args = configService.Args;
foreach (var buildProperty in configService.BuildProperties)
{
project.BuildProperties.Add(buildProperty.Name, buildProperty.Value);
}
project.Replicas = configService.Replicas ?? 1;
await ProjectReader.ReadProjectDetailsAsync(output, project);
// We don't apply more container defaults here because we might need
// to prompt for the registry name.
project.ContainerInfo = new ContainerInfo()
{
UseMultiphaseDockerfile = false,
};
project.ContainerInfo = new ContainerInfo() { UseMultiphaseDockerfile = false, };
// Do k8s by default.
project.ManifestInfo = new KubernetesManifestInfo();
@ -122,16 +123,9 @@ namespace Microsoft.Tye
project2.IsAspNet)
{
// HTTP is the default binding
service.Bindings.Add(new BindingBuilder()
{
Protocol = "http"
});
service.Bindings.Add(new BindingBuilder() { Protocol = "http" });
service.Bindings.Add(new BindingBuilder()
{
Name = "https",
Protocol = "https"
});
service.Bindings.Add(new BindingBuilder() { Name = "https", Protocol = "https" });
}
else
{
@ -159,10 +153,7 @@ namespace Microsoft.Tye
foreach (var configEnvVar in configService.Configuration)
{
var envVar = new EnvironmentVariableBuilder(configEnvVar.Name)
{
Value = configEnvVar.Value,
};
var envVar = new EnvironmentVariableBuilder(configEnvVar.Name) { Value = configEnvVar.Value, };
if (service is ProjectServiceBuilder project)
{
project.EnvironmentVariables.Add(envVar);
@ -211,6 +202,7 @@ namespace Microsoft.Tye
}
}
foreach (var configIngress in config.Ingress)
{
var ingress = new IngressBuilder(configIngress.Name!);

17
src/Microsoft.Tye.Core/ConfigModel/BuildProperty.cs

@ -0,0 +1,17 @@
// 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.ComponentModel.DataAnnotations;
namespace Microsoft.Tye.ConfigModel
{
public class BuildProperty
{
[Required]
public string Name { get; set; } = default!;
[Required]
public string Value { get; set; } = default!;
}
}

1
src/Microsoft.Tye.Core/ConfigModel/ConfigService.cs

@ -24,5 +24,6 @@ namespace Microsoft.Tye.ConfigModel
public List<ConfigVolume> Volumes { get; set; } = new List<ConfigVolume>();
[YamlMember(Alias = "env")]
public List<ConfigConfigurationSource> Configuration { get; set; } = new List<ConfigConfigurationSource>();
public List<BuildProperty> BuildProperties { get; set; } = new List<BuildProperty>();
}
}

2
src/Microsoft.Tye.Core/ProjectReader.cs

@ -26,6 +26,7 @@ namespace Microsoft.Tye
public static class ProjectReader
{
private static object @lock = new object();
private static bool registered;
public static IEnumerable<FileInfo> EnumerateProjects(FileInfo solutionFile)
@ -165,6 +166,7 @@ namespace Microsoft.Tye
msbuildProject = Microsoft.Build.Evaluation.Project.FromFile(project.ProjectFile.FullName, new ProjectOptions()
{
ProjectCollection = projectCollection,
GlobalProperties = project.BuildProperties
});
projectInstance = msbuildProject.CreateProjectInstance();
output.WriteDebugLine($"Loaded project '{project.ProjectFile.FullName}'.");

2
src/Microsoft.Tye.Core/ProjectServiceBuilder.cs

@ -49,5 +49,7 @@ namespace Microsoft.Tye
// Used when running in a container locally.
public List<VolumeBuilder> Volumes { get; } = new List<VolumeBuilder>();
public Dictionary<string, string> BuildProperties { get; } = new Dictionary<string, string>();
}
}

38
src/Microsoft.Tye.Core/Serialization/ConfigServiceParser.cs

@ -45,6 +45,13 @@ namespace Tye.Serialization
case "project":
service.Project = YamlParser.GetScalarValue(key, child.Value);
break;
case "buildProperties":
if (child.Value.NodeType != YamlNodeType.Sequence)
{
throw new TyeYamlException(child.Value.Start, CoreStrings.FormatExpectedYamlSequence(key));
}
HandleBuildProperties((child.Value as YamlSequenceNode)!, service.BuildProperties);
break;
case "build":
if (!bool.TryParse(YamlParser.GetScalarValue(key, child.Value), out var build))
{
@ -191,6 +198,17 @@ namespace Tye.Serialization
}
}
private static void HandleBuildProperties(YamlSequenceNode yamlSequenceNode, List<BuildProperty> buildProperties)
{
foreach (var child in yamlSequenceNode.Children)
{
YamlParser.ThrowIfNotYamlMapping(child);
var buildProperty = new BuildProperty();
HandleServiceBuildPropertyNameMapping((YamlMappingNode)child, buildProperty);
buildProperties.Add(buildProperty);
}
}
private static void HandleServiceConfiguration(YamlSequenceNode yamlSequenceNode, List<ConfigConfigurationSource> configuration)
{
foreach (var child in yamlSequenceNode.Children)
@ -221,5 +239,25 @@ namespace Tye.Serialization
}
}
}
private static void HandleServiceBuildPropertyNameMapping(YamlMappingNode yamlMappingNode, BuildProperty buildProperty)
{
foreach (var child in yamlMappingNode!.Children)
{
var key = YamlParser.GetScalarValue(child.Key);
switch (key)
{
case "name":
buildProperty.Name = YamlParser.GetScalarValue(key, child.Value);
break;
case "value":
buildProperty.Value = YamlParser.GetScalarValue(key, child.Value);
break;
default:
throw new TyeYamlException(child.Key.Start, CoreStrings.FormatUnrecognizedKey(key));
}
}
}
}
}

3
src/Microsoft.Tye.Hosting/Model/ProjectRunInfo.cs

@ -14,6 +14,7 @@ namespace Microsoft.Tye.Hosting.Model
ProjectFile = project.ProjectFile;
Args = project.Args;
Build = project.Build;
BuildProperties = project.BuildProperties;
TargetFramework = project.TargetFramework;
TargetFrameworkName = project.TargetFrameworkName;
TargetFrameworkVersion = project.TargetFrameworkVersion;
@ -25,6 +26,8 @@ namespace Microsoft.Tye.Hosting.Model
PublishOutputPath = project.PublishDir;
}
public Dictionary<string, string> BuildProperties { get; } = new Dictionary<string, string>();
public string? Args { get; }
public bool Build { get; }
public FileInfo ProjectFile { get; }

7
src/Microsoft.Tye.Hosting/ProcessRunner.cs

@ -51,12 +51,15 @@ namespace Microsoft.Tye.Hosting
string path;
string args;
var buildArgs = string.Empty;
string workingDirectory;
if (serviceDescription.RunInfo is ProjectRunInfo project)
{
path = project.RunCommand;
workingDirectory = project.ProjectFile.Directory.FullName;
args = project.Args == null ? project.RunArguments : project.RunArguments + " " + project.Args;
buildArgs = project.BuildProperties.Aggregate(string.Empty, (current, property) => current + $" /p:{property.Key}={property.Value}").TrimStart();
service.Status.ProjectFilePath = project.ProjectFile.FullName;
}
else if (serviceDescription.RunInfo is ExecutableRunInfo executable)
@ -90,9 +93,9 @@ namespace Microsoft.Tye.Hosting
// Sometimes building can fail because of file locking (like files being open in VS)
_logger.LogInformation("Building project {ProjectFile}", service.Status.ProjectFilePath);
service.Logs.OnNext($"dotnet build \"{service.Status.ProjectFilePath}\" /nologo");
service.Logs.OnNext($"dotnet build \"{service.Status.ProjectFilePath}\" {buildArgs} /nologo");
var buildResult = await ProcessUtil.RunAsync("dotnet", $"build \"{service.Status.ProjectFilePath}\" /nologo", throwOnError: false, workingDirectory: workingDirectory);
var buildResult = await ProcessUtil.RunAsync("dotnet", $"build \"{service.Status.ProjectFilePath}\" {buildArgs} /nologo", throwOnError: false, workingDirectory: workingDirectory);
service.Logs.OnNext(buildResult.StandardOutput);

6
src/Microsoft.Tye.Hosting/TransformProjectsIntoContainers.cs

@ -9,6 +9,8 @@ using Microsoft.Tye.Hosting.Model;
namespace Microsoft.Tye.Hosting
{
using System.Linq;
public class TransformProjectsIntoContainers : IApplicationProcessor
{
private readonly ILogger _logger;
@ -44,7 +46,9 @@ namespace Microsoft.Tye.Hosting
// Sometimes building can fail because of file locking (like files being open in VS)
_logger.LogInformation("Publishing project {ProjectFile}", service.Status.ProjectFilePath);
var publishCommand = $"publish \"{service.Status.ProjectFilePath}\" --framework {targetFramework} /nologo";
var buildArgs = project.BuildProperties.Aggregate(string.Empty, (current, property) => current + $" /p:{property.Key}={property.Value}").TrimStart();
var publishCommand = $"publish \"{service.Status.ProjectFilePath}\" --framework {targetFramework} {buildArgs} /nologo";
service.Logs.OnNext($"dotnet {publishCommand}");

3
src/tye/tye.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
@ -7,6 +7,7 @@
<AssemblyName>tye</AssemblyName>
<PackageId>Microsoft.Tye</PackageId>
<ToolCommandName>tye</ToolCommandName>
<PackageVersion>0.2.0-dev-1</PackageVersion>
<PackAsTool>true</PackAsTool>
</PropertyGroup>

2
test/E2ETest/E2ETest.csproj

@ -39,6 +39,8 @@
<None Remove="testassets\generate\single-project.yaml" />
<None Remove="testassets\init\frontend-backend.yaml" />
<None Remove="testassets\init\multi-project.yaml" />
<None Remove="testassets\projects\frontend-backend\tye-debug-configuration.yaml" />
<None Remove="testassets\projects\frontend-backend\tye-release-configuration.yaml" />
</ItemGroup>
</Project>

16
test/E2ETest/Infrastructure/ConditionalTheoryAttribute.cs

@ -0,0 +1,16 @@
// 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 Xunit;
using Xunit.Sdk;
namespace E2ETest
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
[XunitTestCaseDiscoverer("E2ETest." + nameof(ConditionalTheoryDiscoverer), "Microsoft.Tye.E2ETest")]
public class ConditionalTheoryAttribute : TheoryAttribute
{
}
}

41
test/E2ETest/Infrastructure/ConditionalTheoryDiscoverer.cs

@ -0,0 +1,41 @@
// 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 Xunit.Abstractions;
using Xunit.Sdk;
namespace E2ETest
{
internal class ConditionalTheoryDiscoverer : TheoryDiscoverer
{
private readonly IMessageSink _diagnosticMessageSink;
public ConditionalTheoryDiscoverer(IMessageSink diagnosticMessageSink)
: base(diagnosticMessageSink)
{
_diagnosticMessageSink = diagnosticMessageSink;
}
protected override IEnumerable<IXunitTestCase> CreateTestCasesForTheory(
ITestFrameworkDiscoveryOptions discoveryOptions,
ITestMethod testMethod,
IAttributeInfo theoryAttribute)
{
var skipReason = testMethod.EvaluateSkipConditions();
return skipReason != null
? new IXunitTestCase[]
{
new SkippedTestCase(
skipReason,
_diagnosticMessageSink,
discoveryOptions.MethodDisplayOrDefault(),
TestMethodDisplayOptions.None,
testMethod)
}
: base.CreateTestCasesForTheory(discoveryOptions, testMethod, theoryAttribute);
}
}
}

42
test/E2ETest/TyeRunTests.cs

@ -108,6 +108,48 @@ namespace E2ETest
});
}
[ConditionalTheory]
[SkipIfDockerNotRunning]
[InlineData("Debug")]
[InlineData("Release")]
public async Task FrontendBackendRunTestWithDockerAndBuildConfigurationAsProperty(string buildConfiguration)
{
using var projectDirectory = CopyTestProjectDirectory("frontend-backend");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, $"tye-{buildConfiguration.ToLower()}-configuration.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new[] { "--docker" }, async (app, uri) =>
{
// Make sure we're running containers
Assert.True(app.Services.All(s => s.Value.Description.RunInfo is DockerRunInfo));
var frontendUri = await GetServiceUrl(client, uri, "frontend");
var backendUri = await GetServiceUrl(client, uri, "backend");
var backendResponse = await client.GetAsync(backendUri);
var frontendResponse = await client.GetAsync(frontendUri);
Assert.True(backendResponse.IsSuccessStatusCode);
Assert.True(frontendResponse.IsSuccessStatusCode);
Assert.True(app.Services.All(s => s.Value.Description.RunInfo != null && ((DockerRunInfo)s.Value.Description.RunInfo).VolumeMappings.Count > 0));
var outputFileInfos = app.Services.Select(s => new FileInfo((s.Value?.Description?.RunInfo as DockerRunInfo)?.VolumeMappings[0].Source ?? throw new InvalidOperationException())).ToList();
Assert.True(outputFileInfos.All(f => f.Directory?.Parent?.Parent?.Name == buildConfiguration));
});
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task FrontendProjectBackendDocker()

14
test/E2ETest/testassets/projects/frontend-backend/tye-debug-configuration.yaml

@ -0,0 +1,14 @@
# tye application configuration file
# read all about it at https://github.com/dotnet/tye
name: frontend-backend
services:
- name: backend
project: backend/backend.csproj
buildProperties:
- name: Configuration
value: Debug
- name: frontend
project: frontend/frontend.csproj
buildProperties:
- name: Configuration
value: Debug

14
test/E2ETest/testassets/projects/frontend-backend/tye-release-configuration.yaml

@ -0,0 +1,14 @@
# tye application configuration file
# read all about it at https://github.com/dotnet/tye
name: frontend-backend
services:
- name: backend
project: backend/backend.csproj
buildProperties:
- name: Configuration
value: Release
- name: frontend
project: frontend/frontend.csproj
buildProperties:
- name: Configuration
value: Release

3
test/UnitTests/TyeDeserializationTests.cs

@ -49,6 +49,9 @@ ingress:
services:
- name: appA
project: ApplicationA/ApplicationA.csproj
buildProperties:
- name: Configuration
- value: Debug
replicas: 2
external: false
image: abc

Loading…
Cancel
Save