mirror of https://github.com/dotnet/tye.git
Browse Source
Merged the data model we use for m8s features with the features that come from rynowak/opulence. The `ApplicationBuilder` type (and related types) now capture all of the details we use for everything. There's a lot of just moving stuff around in this change - the goal is to have a separate data model for: - The config we read - The representation we mutate - The data model used by Tye.Hosting So in this PR there's a lot of righthand/lefthand.pull/182/head
62 changed files with 927 additions and 873 deletions
@ -1,231 +0,0 @@ |
|||
// 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 System.Linq; |
|||
using System.Reflection; |
|||
|
|||
namespace Microsoft.Tye |
|||
{ |
|||
public abstract class Application |
|||
{ |
|||
public abstract ApplicationGlobals Globals { get; } |
|||
|
|||
public abstract string RootDirectory { get; } |
|||
|
|||
public abstract IReadOnlyList<ServiceEntry> Services { get; } |
|||
|
|||
public string GetProjectDirectory(Project project) |
|||
{ |
|||
if (project is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(project)); |
|||
} |
|||
|
|||
return Path.GetDirectoryName(Path.Combine(RootDirectory, project.RelativeFilePath))!; |
|||
} |
|||
} |
|||
|
|||
public class ServiceEntry |
|||
{ |
|||
public ServiceEntry(Service service, string friendlyName, IEnumerable<string>? environments = null) |
|||
{ |
|||
if (service is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(service)); |
|||
} |
|||
|
|||
if (friendlyName is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(friendlyName)); |
|||
} |
|||
|
|||
Service = service; |
|||
FriendlyName = friendlyName; |
|||
|
|||
Environments = environments?.ToArray() ?? Array.Empty<string>(); |
|||
} |
|||
|
|||
public IReadOnlyList<string> Environments { get; } |
|||
|
|||
public string FriendlyName { get; } |
|||
|
|||
public Service Service { get; } |
|||
|
|||
public List<ServiceOutput> Outputs { get; } = new List<ServiceOutput>(); |
|||
|
|||
public bool AppliesToEnvironment(string environment) |
|||
{ |
|||
if (environment is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(environment)); |
|||
} |
|||
|
|||
return Environments.Count == 0 || Environments.Contains(environment, StringComparer.OrdinalIgnoreCase); |
|||
} |
|||
|
|||
public bool IsMatchForProject(Application application, FileInfo projectFile) |
|||
{ |
|||
if (application is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(application)); |
|||
} |
|||
|
|||
if (projectFile is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(projectFile)); |
|||
} |
|||
|
|||
if (string.Equals(projectFile.Extension, ".sln", StringComparison.Ordinal)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (Service.Source is Project project && |
|||
string.Equals(application.GetProjectDirectory(project), projectFile.DirectoryName, StringComparison.Ordinal)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
|
|||
public abstract class ServiceOutput |
|||
{ |
|||
} |
|||
|
|||
public class DockerImageOutput : ServiceOutput |
|||
{ |
|||
public DockerImageOutput(string imageName, string imageTag) |
|||
{ |
|||
if (imageName is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(imageName)); |
|||
} |
|||
|
|||
if (imageTag is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(imageTag)); |
|||
} |
|||
|
|||
ImageName = imageName; |
|||
ImageTag = imageTag; |
|||
} |
|||
|
|||
public string ImageName { get; } |
|||
|
|||
public string ImageTag { get; } |
|||
} |
|||
|
|||
|
|||
internal class ApplicationWrapper : Application |
|||
{ |
|||
private readonly object inner; |
|||
private ApplicationGlobals? globals; |
|||
private List<ServiceEntry>? services; |
|||
|
|||
public ApplicationWrapper(object inner, string rootDirectory) |
|||
{ |
|||
if (inner is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(inner)); |
|||
} |
|||
|
|||
if (rootDirectory is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(rootDirectory)); |
|||
} |
|||
|
|||
this.inner = inner; |
|||
RootDirectory = rootDirectory; |
|||
} |
|||
|
|||
public override ApplicationGlobals Globals |
|||
{ |
|||
get |
|||
{ |
|||
if (globals == null) |
|||
{ |
|||
var property = inner.GetType().GetProperty("Globals", BindingFlags.Instance | BindingFlags.Public); |
|||
if (property != null && property.PropertyType == typeof(ApplicationGlobals)) |
|||
{ |
|||
globals = (ApplicationGlobals?)property.GetValue(inner, null); |
|||
} |
|||
|
|||
globals ??= new ApplicationGlobals(); |
|||
} |
|||
|
|||
return globals!; |
|||
} |
|||
} |
|||
|
|||
public override string RootDirectory { get; } |
|||
|
|||
public override IReadOnlyList<ServiceEntry> Services |
|||
{ |
|||
get |
|||
{ |
|||
if (services == null) |
|||
{ |
|||
services = new List<ServiceEntry>(); |
|||
var properties = inner.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); |
|||
for (var i = 0; i < properties.Length; i++) |
|||
{ |
|||
var property = properties[i]; |
|||
if (property.PropertyType == typeof(Service)) |
|||
{ |
|||
var environments = new List<string>(); |
|||
var environmentAttributes = property.GetCustomAttributes<EnvironmentAttribute>(); |
|||
environments.AddRange(environmentAttributes.Select(e => e.EnvironmentName)); |
|||
|
|||
var value = property.GetValue(inner, null); |
|||
if (value == null) |
|||
{ |
|||
throw new InvalidOperationException("Service properties must return a non-null value."); |
|||
} |
|||
|
|||
services.Add(new ServiceEntry((Service)value, property.Name, environments)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return services; |
|||
} |
|||
} |
|||
} |
|||
|
|||
internal class GroveledApplication : Application |
|||
{ |
|||
public GroveledApplication(ApplicationGlobals globals, string rootDirectory, IEnumerable<ServiceEntry> services) |
|||
{ |
|||
if (globals is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(globals)); |
|||
} |
|||
|
|||
if (rootDirectory is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(rootDirectory)); |
|||
} |
|||
|
|||
if (services is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(services)); |
|||
} |
|||
|
|||
Globals = globals; |
|||
RootDirectory = rootDirectory; |
|||
Services = services.ToArray(); |
|||
} |
|||
|
|||
public override ApplicationGlobals Globals { get; } |
|||
|
|||
public override string RootDirectory { get; } |
|||
|
|||
public override IReadOnlyList<ServiceEntry> Services { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
// 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 System.IO; |
|||
|
|||
namespace Microsoft.Tye |
|||
{ |
|||
public sealed class ApplicationBuilder |
|||
{ |
|||
public ApplicationBuilder(FileInfo source, string name) |
|||
{ |
|||
Source = source; |
|||
Name = name; |
|||
} |
|||
|
|||
public FileInfo Source { get; set; } |
|||
|
|||
public string Name { get; set; } |
|||
|
|||
public ContainerRegistry? Registry { get; set; } |
|||
|
|||
public List<ServiceBuilder> Services { get; } = new List<ServiceBuilder>(); |
|||
|
|||
public List<IngressBuilder> Ingress { get; } = new List<IngressBuilder>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,257 @@ |
|||
// 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.ComponentModel.DataAnnotations; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Tye.ConfigModel; |
|||
|
|||
namespace Microsoft.Tye |
|||
{ |
|||
public static class ApplicationFactory |
|||
{ |
|||
public static async Task<ApplicationBuilder> CreateAsync(OutputContext output, FileInfo source) |
|||
{ |
|||
if (source is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(source)); |
|||
} |
|||
|
|||
var config = ConfigFactory.FromFile(source); |
|||
ValidateConfigApplication(config); |
|||
|
|||
var builder = new ApplicationBuilder(source, config.Name ?? source.Directory.Name.ToLowerInvariant()); |
|||
if (!string.IsNullOrEmpty(config.Registry)) |
|||
{ |
|||
builder.Registry = new ContainerRegistry(config.Registry); |
|||
} |
|||
|
|||
foreach (var configService in config.Services) |
|||
{ |
|||
ServiceBuilder service; |
|||
if (!string.IsNullOrEmpty(configService.Project)) |
|||
{ |
|||
var projectFile = new FileInfo(Path.Combine(builder.Source.DirectoryName, configService.Project)); |
|||
var project = new ProjectServiceBuilder(configService.Name, projectFile); |
|||
service = project; |
|||
|
|||
project.Build = configService.Build ?? true; |
|||
project.Args = configService.Args; |
|||
project.Replicas = configService.Replicas ?? 1; |
|||
|
|||
await ProjectReader.ReadProjectDetailsAsync(output, project); |
|||
|
|||
// We don't apply more container defaults here because we might need
|
|||
// to prompty for the registry name.
|
|||
project.ContainerInfo = new ContainerInfo() |
|||
{ |
|||
UseMultiphaseDockerfile = false, |
|||
}; |
|||
} |
|||
else if (!string.IsNullOrEmpty(configService.Image)) |
|||
{ |
|||
var container = new ContainerServiceBuilder(configService.Name, configService.Image); |
|||
container.Args = configService.Args; |
|||
container.Replicas = configService.Replicas ?? 1; |
|||
service = container; |
|||
} |
|||
else if (!string.IsNullOrEmpty(configService.Executable)) |
|||
{ |
|||
var executable = new ExecutableServiceBuilder(configService.Name, configService.Executable); |
|||
executable.Args = configService.Args; |
|||
executable.WorkingDirectory = configService.WorkingDirectory; |
|||
executable.Replicas = configService.Replicas ?? 1; |
|||
service = executable; |
|||
} |
|||
else if (configService.External) |
|||
{ |
|||
var external = new ExternalServiceBuilder(configService.Name); |
|||
service = external; |
|||
} |
|||
else |
|||
{ |
|||
throw new CommandException("Unable to determine service type."); |
|||
} |
|||
|
|||
builder.Services.Add(service); |
|||
|
|||
foreach (var configBinding in configService.Bindings) |
|||
{ |
|||
var binding = new BindingBuilder() |
|||
{ |
|||
Name = configBinding.Name, |
|||
AutoAssignPort = configBinding.AutoAssignPort, |
|||
ConnectionString = configBinding.ConnectionString, |
|||
Host = configBinding.Host, |
|||
InternalPort = configBinding.InternalPort, |
|||
Port = configBinding.Port, |
|||
Protocol = configBinding.Protocol, |
|||
}; |
|||
|
|||
if (binding.ConnectionString == null) |
|||
{ |
|||
binding.Protocol ??= "http"; |
|||
} |
|||
|
|||
service.Bindings.Add(binding); |
|||
} |
|||
|
|||
foreach (var configEnvVar in configService.Configuration) |
|||
{ |
|||
var envVar = new EnvironmentVariable(configEnvVar.Name, configEnvVar.Value); |
|||
if (service is ProjectServiceBuilder project) |
|||
{ |
|||
project.EnvironmentVariables.Add(envVar); |
|||
} |
|||
else if (service is ContainerServiceBuilder container) |
|||
{ |
|||
container.EnvironmentVariables.Add(envVar); |
|||
} |
|||
else if (service is ExecutableServiceBuilder executable) |
|||
{ |
|||
executable.EnvironmentVariables.Add(envVar); |
|||
} |
|||
else if (service is ExternalServiceBuilder) |
|||
{ |
|||
throw new CommandException("External services do not support environment variables."); |
|||
} |
|||
else |
|||
{ |
|||
throw new CommandException("Unable to determine service type."); |
|||
} |
|||
} |
|||
|
|||
foreach (var configVolume in configService.Volumes) |
|||
{ |
|||
var volume = new VolumeBuilder(configVolume.Source, configVolume.Target); |
|||
if (service is ProjectServiceBuilder project) |
|||
{ |
|||
project.Volumes.Add(volume); |
|||
} |
|||
else if (service is ContainerServiceBuilder container) |
|||
{ |
|||
container.Volumes.Add(volume); |
|||
} |
|||
else if (service is ExecutableServiceBuilder executable) |
|||
{ |
|||
throw new CommandException("Executable services do not support volumes."); |
|||
} |
|||
else if (service is ExternalServiceBuilder) |
|||
{ |
|||
throw new CommandException("External services do not support volumes."); |
|||
} |
|||
else |
|||
{ |
|||
throw new CommandException("Unable to determine service type."); |
|||
} |
|||
} |
|||
} |
|||
|
|||
foreach (var configIngress in config.Ingress) |
|||
{ |
|||
var ingress = new IngressBuilder(configIngress.Name); |
|||
ingress.Replicas = configIngress.Replicas ?? 1; |
|||
|
|||
builder.Ingress.Add(ingress); |
|||
|
|||
foreach (var configBinding in configIngress.Bindings) |
|||
{ |
|||
var binding = new IngressBindingBuilder() |
|||
{ |
|||
AutoAssignPort = configBinding.AutoAssignPort, |
|||
Name = configBinding.Name, |
|||
Port = configBinding.Port, |
|||
Protocol = configBinding.Protocol ?? "http", |
|||
}; |
|||
ingress.Bindings.Add(binding); |
|||
} |
|||
|
|||
foreach (var configRule in configIngress.Rules) |
|||
{ |
|||
var rule = new IngressRuleBuilder() |
|||
{ |
|||
Host = configRule.Host, |
|||
Path = configRule.Path, |
|||
Service = configRule.Service, |
|||
}; |
|||
ingress.Rules.Add(rule); |
|||
} |
|||
} |
|||
|
|||
return builder; |
|||
} |
|||
|
|||
private static void ValidateConfigApplication(ConfigApplication config) |
|||
{ |
|||
var context = new ValidationContext(config); |
|||
var results = new List<ValidationResult>(); |
|||
if (!Validator.TryValidateObject(config, context, results, validateAllProperties: true)) |
|||
{ |
|||
throw new CommandException( |
|||
"Configuration validation failed." + Environment.NewLine + |
|||
string.Join(Environment.NewLine, results.Select(r => r.ErrorMessage))); |
|||
} |
|||
|
|||
foreach (var service in config.Services) |
|||
{ |
|||
context = new ValidationContext(service); |
|||
if (!Validator.TryValidateObject(service, context, results, validateAllProperties: true)) |
|||
{ |
|||
throw new CommandException( |
|||
$"Service '{service.Name}' validation failed." + Environment.NewLine + |
|||
string.Join(Environment.NewLine, results.Select(r => r.ErrorMessage))); |
|||
} |
|||
|
|||
foreach (var binding in service.Bindings) |
|||
{ |
|||
context = new ValidationContext(binding); |
|||
if (!Validator.TryValidateObject(binding, context, results, validateAllProperties: true)) |
|||
{ |
|||
throw new CommandException( |
|||
$"Binding '{binding.Name}' of service '{service.Name}' validation failed." + Environment.NewLine + |
|||
string.Join(Environment.NewLine, results.Select(r => r.ErrorMessage))); |
|||
} |
|||
} |
|||
|
|||
foreach (var envVar in service.Configuration) |
|||
{ |
|||
context = new ValidationContext(service); |
|||
if (!Validator.TryValidateObject(service, context, results, validateAllProperties: true)) |
|||
{ |
|||
throw new CommandException( |
|||
$"Environment variable '{envVar.Name}' of service '{service.Name}' validation failed." + Environment.NewLine + |
|||
string.Join(Environment.NewLine, results.Select(r => r.ErrorMessage))); |
|||
} |
|||
} |
|||
|
|||
foreach (var volume in service.Volumes) |
|||
{ |
|||
context = new ValidationContext(service); |
|||
if (!Validator.TryValidateObject(service, context, results, validateAllProperties: true)) |
|||
{ |
|||
throw new CommandException( |
|||
$"Volume '{volume.Source}' of service '{service.Name}' validation failed." + Environment.NewLine + |
|||
string.Join(Environment.NewLine, results.Select(r => r.ErrorMessage))); |
|||
} |
|||
} |
|||
} |
|||
|
|||
foreach (var ingress in config.Ingress) |
|||
{ |
|||
// We don't currently recurse into ingress rules or ingress bindings right now.
|
|||
// There's nothing to validate there.
|
|||
context = new ValidationContext(ingress); |
|||
if (!Validator.TryValidateObject(ingress, context, results, validateAllProperties: true)) |
|||
{ |
|||
throw new CommandException( |
|||
$"Ingress '{ingress.Name}' validation failed." + Environment.NewLine + |
|||
string.Join(Environment.NewLine, results.Select(r => r.ErrorMessage))); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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.
|
|||
|
|||
namespace Microsoft.Tye |
|||
{ |
|||
public sealed class BindingBuilder |
|||
{ |
|||
public string? Name { get; set; } |
|||
public string? ConnectionString { get; set; } |
|||
public bool AutoAssignPort { get; set; } |
|||
public int? Port { get; set; } |
|||
public int? InternalPort { get; set; } |
|||
public string? Host { get; set; } |
|||
public string? Protocol { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// 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.ComponentModel.DataAnnotations; |
|||
using System.IO; |
|||
using YamlDotNet.Serialization; |
|||
|
|||
namespace Microsoft.Tye.ConfigModel |
|||
{ |
|||
// General design note re: nullability - we use [Required] to validate for non-null
|
|||
// so we use = default! to reflect that. Code that creates a ConfigApplication should
|
|||
// validate it before deferencing the properties.
|
|||
public class ConfigApplication |
|||
{ |
|||
// This gets set by all of the code paths that read the application
|
|||
[YamlIgnore] |
|||
public FileInfo Source { get; set; } = default!; |
|||
|
|||
public string? Name { get; set; } |
|||
|
|||
public string? Registry { get; set; } |
|||
|
|||
public List<ConfigService> Services { get; set; } = new List<ConfigService>(); |
|||
|
|||
public List<ConfigIngress> Ingress { get; set; } = new List<ConfigIngress>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
// 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; |
|||
|
|||
namespace Microsoft.Tye |
|||
{ |
|||
public sealed class ContainerServiceBuilder : ServiceBuilder |
|||
{ |
|||
public ContainerServiceBuilder(string name, string image) |
|||
: base(name) |
|||
{ |
|||
Image = image; |
|||
} |
|||
|
|||
public string Image { get; set; } |
|||
|
|||
public string? Args { get; set; } |
|||
|
|||
public int Replicas { get; set; } = 1; |
|||
|
|||
public List<EnvironmentVariable> EnvironmentVariables { get; } = new List<EnvironmentVariable>(); |
|||
|
|||
public List<VolumeBuilder> Volumes { get; } = new List<VolumeBuilder>(); |
|||
} |
|||
} |
|||
@ -1,163 +0,0 @@ |
|||
// 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 System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Build.Construction; |
|||
|
|||
namespace Microsoft.Tye |
|||
{ |
|||
internal class CustomizationPipeline : Pipeline |
|||
{ |
|||
private readonly Dictionary<Type, List<MulticastDelegate>> callbacks = new Dictionary<Type, List<MulticastDelegate>>(); |
|||
|
|||
private readonly OutputContext output; |
|||
private readonly string rootDirectory; |
|||
private readonly string name; |
|||
private readonly SolutionFile? solution; |
|||
private readonly FileInfo? projectFile; |
|||
|
|||
public CustomizationPipeline(OutputContext output, string rootDirectory, string name, SolutionFile? solution, FileInfo? projectFile) |
|||
{ |
|||
if (output is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(output)); |
|||
} |
|||
|
|||
if (rootDirectory is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(rootDirectory)); |
|||
} |
|||
|
|||
if (name is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(name)); |
|||
} |
|||
|
|||
this.output = output; |
|||
this.rootDirectory = rootDirectory; |
|||
this.name = name; |
|||
this.solution = solution; |
|||
this.projectFile = projectFile; |
|||
} |
|||
|
|||
public override async Task<object?> ExecuteAsync() |
|||
{ |
|||
if (callbacks.Count == 0) |
|||
{ |
|||
return Task.FromResult<object?>(null); |
|||
} |
|||
|
|||
if (callbacks.Count > 1) |
|||
{ |
|||
throw new InvalidOperationException("More than one application type is not supported."); |
|||
} |
|||
|
|||
var kvp = callbacks.Single(); |
|||
|
|||
var type = kvp.Key; |
|||
var delegates = kvp.Value; |
|||
|
|||
output.WriteDebugLine($"Creating instance of application type '{type}'."); |
|||
var application = Activator.CreateInstance(type); |
|||
output.WriteDebugLine($"Done creating instance of application type '{type}'."); |
|||
|
|||
var wrapper = new ApplicationWrapper(application!, rootDirectory); |
|||
|
|||
wrapper.Globals.Name ??= name; |
|||
|
|||
foreach (var service in wrapper.Services) |
|||
{ |
|||
output.WriteDebugLine($"Found service '{service.FriendlyName} {{ Name: {service.Service.Name} }}'."); |
|||
|
|||
string? projectRelativeFilePath = null; |
|||
string? projectFilePath = null; |
|||
if (solution != null) |
|||
{ |
|||
var project = FindProjectInSolution(solution, service.FriendlyName); |
|||
if (project == null) |
|||
{ |
|||
output.WriteDebugLine($"Could not find project for service '{service.FriendlyName}'."); |
|||
continue; |
|||
} |
|||
|
|||
output.WriteDebugLine($"Found project '{project.RelativePath}' for service '{service.FriendlyName}'."); |
|||
projectRelativeFilePath = project.RelativePath.Replace('\\', Path.DirectorySeparatorChar); |
|||
projectFilePath = project.AbsolutePath.Replace('\\', Path.DirectorySeparatorChar); |
|||
} |
|||
else if (projectFile != null) |
|||
{ |
|||
var normalized = Names.NormalizeToFriendly(Path.GetFileNameWithoutExtension(projectFile.Name)); |
|||
if (!string.Equals(normalized, service.FriendlyName)) |
|||
{ |
|||
output.WriteDebugLine($"Skipping service '{service.FriendlyName}'."); |
|||
continue; |
|||
} |
|||
|
|||
projectRelativeFilePath = projectFile.FullName; |
|||
projectFilePath = projectFile.FullName; |
|||
} |
|||
|
|||
if (projectFilePath != null) |
|||
{ |
|||
var project = new Project(projectRelativeFilePath!); |
|||
await ProjectReader.ReadProjectDetailsAsync(output, new FileInfo(projectFilePath), project); |
|||
|
|||
service.Service.Source = project; |
|||
|
|||
// Apply defaults to everything that has a project.
|
|||
var container = new ContainerInfo(); |
|||
service.Service.GeneratedAssets.Container = container; |
|||
DockerfileGenerator.ApplyContainerDefaults(wrapper, service, project, container); |
|||
} |
|||
} |
|||
|
|||
output.WriteDebugLine($"Running {delegates.Count} customization callbacks."); |
|||
for (var i = 0; i < delegates.Count; i++) |
|||
{ |
|||
delegates[i].DynamicInvoke(application); |
|||
} |
|||
output.WriteDebugLine($"Done running {delegates.Count} customization callbacks."); |
|||
|
|||
|
|||
return application; |
|||
} |
|||
|
|||
public override void Register<TApplication>(Action<TApplication> callback) |
|||
{ |
|||
if (callback is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(callback)); |
|||
} |
|||
|
|||
if (!callbacks.TryGetValue(typeof(TApplication), out var delegates)) |
|||
{ |
|||
delegates = new List<MulticastDelegate>(); |
|||
callbacks.Add(typeof(TApplication), delegates); |
|||
} |
|||
|
|||
delegates.Add(callback); |
|||
} |
|||
|
|||
private static ProjectInSolution? FindProjectInSolution(SolutionFile solution, string projectName) |
|||
{ |
|||
for (var i = 0; i < solution.ProjectsInOrder.Count; i++) |
|||
{ |
|||
var project = solution.ProjectsInOrder[i]; |
|||
var normalized = Names.NormalizeToFriendly(project.ProjectName); |
|||
|
|||
if (string.Equals(normalized, projectName, StringComparison.Ordinal) && |
|||
project.AbsolutePath.EndsWith(".csproj", StringComparison.Ordinal)) |
|||
{ |
|||
return project; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// 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; |
|||
|
|||
namespace Microsoft.Tye |
|||
{ |
|||
public class DockerImageOutput : ServiceOutput |
|||
{ |
|||
public DockerImageOutput(string imageName, string imageTag) |
|||
{ |
|||
if (imageName is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(imageName)); |
|||
} |
|||
|
|||
if (imageTag is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(imageTag)); |
|||
} |
|||
|
|||
ImageName = imageName; |
|||
ImageTag = imageTag; |
|||
} |
|||
|
|||
public string ImageName { get; } |
|||
|
|||
public string ImageTag { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// 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.
|
|||
|
|||
namespace Microsoft.Tye |
|||
{ |
|||
public sealed class EnvironmentVariable |
|||
{ |
|||
public EnvironmentVariable(string name, string value) |
|||
{ |
|||
Name = name; |
|||
Value = value; |
|||
} |
|||
|
|||
public string Name { get; } |
|||
|
|||
public string Value { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
// 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; |
|||
|
|||
namespace Microsoft.Tye |
|||
{ |
|||
public sealed class ExecutableServiceBuilder : ServiceBuilder |
|||
{ |
|||
public ExecutableServiceBuilder(string name, string executable) |
|||
: base(name) |
|||
{ |
|||
Executable = executable; |
|||
} |
|||
|
|||
public string Executable { get; set; } |
|||
|
|||
public string? WorkingDirectory { get; set; } |
|||
|
|||
public string? Args { get; set; } |
|||
|
|||
public int Replicas { get; set; } = 1; |
|||
|
|||
public List<EnvironmentVariable> EnvironmentVariables { get; } = new List<EnvironmentVariable>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// 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.
|
|||
|
|||
namespace Microsoft.Tye |
|||
{ |
|||
public sealed class ExternalServiceBuilder : ServiceBuilder |
|||
{ |
|||
public ExternalServiceBuilder(string name) |
|||
: base(name) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// 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.
|
|||
|
|||
namespace Microsoft.Tye |
|||
{ |
|||
public sealed class IngressBindingBuilder |
|||
{ |
|||
public string? Name { get; set; } |
|||
public bool AutoAssignPort { get; set; } |
|||
public int? Port { get; set; } |
|||
public string? Protocol { get; set; } // HTTP or HTTPS
|
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
// 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; |
|||
|
|||
namespace Microsoft.Tye |
|||
{ |
|||
public sealed class IngressBuilder |
|||
{ |
|||
public IngressBuilder(string name) |
|||
{ |
|||
Name = name; |
|||
} |
|||
|
|||
public string Name { get; set; } |
|||
|
|||
public int Replicas { get; set; } = 1; |
|||
|
|||
public List<IngressBindingBuilder> Bindings { get; set; } = new List<IngressBindingBuilder>(); |
|||
|
|||
public List<IngressRuleBuilder> Rules { get; set; } = new List<IngressRuleBuilder>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
// 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.
|
|||
|
|||
namespace Microsoft.Tye |
|||
{ |
|||
public sealed class IngressRuleBuilder |
|||
{ |
|||
public string? Path { get; set; } |
|||
public string? Host { get; set; } |
|||
public string? Service { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
// 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 System.IO; |
|||
|
|||
namespace Microsoft.Tye |
|||
{ |
|||
public sealed class ProjectServiceBuilder : ServiceBuilder |
|||
{ |
|||
public ProjectServiceBuilder(string name, FileInfo projectFile) |
|||
: base(name) |
|||
{ |
|||
ProjectFile = projectFile; |
|||
} |
|||
|
|||
public FileInfo ProjectFile { get; } |
|||
|
|||
public int Replicas { get; set; } = 1; |
|||
|
|||
public bool Build { get; set; } |
|||
|
|||
public string? Args { get; set; } |
|||
|
|||
public FrameworkCollection Frameworks { get; } = new FrameworkCollection(); |
|||
|
|||
// This is always set on the ApplicationFactory codepath.
|
|||
public string TargetFramework { get; set; } = default!; |
|||
|
|||
// This is always set on the ApplicationFactory codepath.
|
|||
public string Version { get; set; } = default!; |
|||
|
|||
// Data used for building containers
|
|||
public ContainerInfo? ContainerInfo { get; set; } |
|||
|
|||
public List<EnvironmentVariable> EnvironmentVariables { get; } = new List<EnvironmentVariable>(); |
|||
|
|||
// Used when running in a container locally.
|
|||
public List<VolumeBuilder> Volumes { get; } = new List<VolumeBuilder>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Microsoft.Tye |
|||
{ |
|||
public abstract class ServiceBuilder |
|||
{ |
|||
public ServiceBuilder(string name) |
|||
{ |
|||
Name = name; |
|||
} |
|||
|
|||
public string Name { get; } |
|||
|
|||
public List<BindingBuilder> Bindings { get; } = new List<BindingBuilder>(); |
|||
|
|||
// TODO: this is temporary while refactoring
|
|||
public List<ServiceOutput> Outputs { get; } = new List<ServiceOutput>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
// 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.
|
|||
|
|||
namespace Microsoft.Tye |
|||
{ |
|||
public abstract class ServiceOutput |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// 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.
|
|||
|
|||
namespace Microsoft.Tye |
|||
{ |
|||
public sealed class VolumeBuilder |
|||
{ |
|||
public VolumeBuilder(string source, string target) |
|||
{ |
|||
Source = source; |
|||
Target = target; |
|||
} |
|||
|
|||
public string Source { get; set; } |
|||
|
|||
public string Target { get; set; } |
|||
} |
|||
} |
|||
@ -1,30 +0,0 @@ |
|||
// 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; |
|||
|
|||
namespace Microsoft.Tye |
|||
{ |
|||
internal class TemporaryApplicationAdapter : Tye.Application |
|||
{ |
|||
private readonly ConfigApplication application; |
|||
|
|||
public TemporaryApplicationAdapter( |
|||
ConfigApplication application, |
|||
ApplicationGlobals globals, |
|||
IReadOnlyList<ServiceEntry> services) |
|||
{ |
|||
this.application = application; |
|||
Globals = globals; |
|||
Services = services; |
|||
} |
|||
|
|||
public override ApplicationGlobals Globals { get; } |
|||
|
|||
public override string RootDirectory => application.Source.DirectoryName; |
|||
|
|||
public override IReadOnlyList<ServiceEntry> Services { get; } |
|||
} |
|||
} |
|||
Loading…
Reference in new issue