Browse Source

Added concept of dependencies to tye.yaml

- It's now possible to reference other tye.yaml files to launch the services.
- This allows for splitting yaml files and composing them separately.
davidfowl/dependencies
David Fowler 7 years ago
parent
commit
acd15730e5
  1. 6
      samples/multi-project/tye.yaml
  2. 333
      src/Microsoft.Tye.Core/ApplicationFactory.cs
  3. 2
      src/Microsoft.Tye.Core/ConfigModel/ConfigApplication.cs
  4. 13
      src/Microsoft.Tye.Core/ConfigModel/ConfigDependency.cs

6
samples/multi-project/tye.yaml

@ -4,16 +4,14 @@ name: multi-project
services:
- name: backend
project: backend/backend.csproj
bindings:
- port: 7000
- name: frontend
project: frontend/frontend.csproj
replicas: 2
bindings:
- port: 8000
- name: worker
project: worker/worker.csproj
- name: rabbit
image: rabbitmq:3-management
bindings:
- port: 5672
dependencies:
- path: ../single-project/tye.yaml

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

@ -21,208 +21,229 @@ namespace Microsoft.Tye
throw new ArgumentNullException(nameof(source));
}
var config = ConfigFactory.FromFile(source);
ValidateConfigApplication(config);
var queue = new Queue<ConfigApplication>();
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 expandedProject = Environment.ExpandEnvironmentVariables(configService.Project);
var projectFile = new FileInfo(Path.Combine(builder.Source.DirectoryName, expandedProject));
var project = new ProjectServiceBuilder(configService.Name, projectFile);
service = project;
var rootConfig = ConfigFactory.FromFile(source);
ValidateConfigApplication(rootConfig);
var root = new ApplicationBuilder(source, rootConfig.Name ?? source.Directory.Name.ToLowerInvariant());
project.Build = configService.Build ?? true;
project.Args = configService.Args;
project.Replicas = configService.Replicas ?? 1;
queue.Enqueue(rootConfig);
await ProjectReader.ReadProjectDetailsAsync(output, project);
while (queue.Count > 0)
{
var config = queue.Dequeue();
// We don't apply more container defaults here because we might need
// to promptly for the registry name.
project.ContainerInfo = new ContainerInfo()
{
UseMultiphaseDockerfile = false,
};
}
else if (!string.IsNullOrEmpty(configService.Image))
if (config == rootConfig && !string.IsNullOrEmpty(config.Registry))
{
var container = new ContainerServiceBuilder(configService.Name, configService.Image)
{
Args = configService.Args,
Replicas = configService.Replicas ?? 1
};
service = container;
root.Registry = new ContainerRegistry(config.Registry);
}
else if (!string.IsNullOrEmpty(configService.Executable))
{
var expandedExecutable = Environment.ExpandEnvironmentVariables(configService.Executable);
var workingDirectory = "";
// Special handling of .dlls as executables (it will be executed as dotnet {dll})
if (Path.GetExtension(expandedExecutable) == ".dll")
foreach (var configService in config.Services)
{
ServiceBuilder service;
if (!string.IsNullOrEmpty(configService.Project))
{
expandedExecutable = Path.GetFullPath(Path.Combine(builder.Source.Directory.FullName, expandedExecutable));
workingDirectory = Path.GetDirectoryName(expandedExecutable)!;
}
var expandedProject = Environment.ExpandEnvironmentVariables(configService.Project);
var projectFile = new FileInfo(Path.Combine(config.Source.DirectoryName, expandedProject));
var executable = new ExecutableServiceBuilder(configService.Name, expandedExecutable)
{
Args = configService.Args,
WorkingDirectory = configService.WorkingDirectory != null ?
Path.GetFullPath(Path.Combine(builder.Source.Directory.FullName, Environment.ExpandEnvironmentVariables(configService.WorkingDirectory))) :
workingDirectory,
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.");
}
var project = new ProjectServiceBuilder(configService.Name, projectFile);
service = project;
builder.Services.Add(service);
project.Build = configService.Build ?? true;
project.Args = configService.Args;
project.Replicas = configService.Replicas ?? 1;
// If there are no bindings and we're in ASP.NET Core project then add an HTTP and HTTPS binding
if (configService.Bindings.Count == 0 &&
service is ProjectServiceBuilder project2 &&
project2.IsAspNet)
{
// HTTP is the default binding
service.Bindings.Add(new BindingBuilder()
{
AutoAssignPort = true,
Protocol = "http"
});
await ProjectReader.ReadProjectDetailsAsync(output, project);
service.Bindings.Add(new BindingBuilder()
{
Name = "https",
AutoAssignPort = true,
Protocol = "https"
});
}
else
{
foreach (var configBinding in configService.Bindings)
// We don't apply more container defaults here because we might need
// to promptly for the registry name.
project.ContainerInfo = new ContainerInfo()
{
UseMultiphaseDockerfile = false,
};
}
else if (!string.IsNullOrEmpty(configService.Image))
{
var binding = new BindingBuilder()
var container = new ContainerServiceBuilder(configService.Name, configService.Image)
{
Name = configBinding.Name,
AutoAssignPort = configBinding.AutoAssignPort,
ConnectionString = configBinding.ConnectionString,
Host = configBinding.Host,
ContainerPort = configBinding.ContainerPort,
Port = configBinding.Port,
Protocol = configBinding.Protocol,
Args = configService.Args,
Replicas = configService.Replicas ?? 1
};
service = container;
}
else if (!string.IsNullOrEmpty(configService.Executable))
{
var expandedExecutable = Environment.ExpandEnvironmentVariables(configService.Executable);
var workingDirectory = "";
// Assume HTTP for projects only (containers may be different)
if (binding.ConnectionString == null && configService.Project != null)
// Special handling of .dlls as executables (it will be executed as dotnet {dll})
if (Path.GetExtension(expandedExecutable) == ".dll")
{
binding.Protocol ??= "http";
expandedExecutable = Path.GetFullPath(Path.Combine(config.Source.Directory.FullName, expandedExecutable));
workingDirectory = Path.GetDirectoryName(expandedExecutable)!;
}
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);
var executable = new ExecutableServiceBuilder(configService.Name, expandedExecutable)
{
Args = configService.Args,
WorkingDirectory = configService.WorkingDirectory != null ?
Path.GetFullPath(Path.Combine(config.Source.Directory.FullName, Environment.ExpandEnvironmentVariables(configService.WorkingDirectory))) :
workingDirectory,
Replicas = configService.Replicas ?? 1
};
service = executable;
}
else if (service is ExternalServiceBuilder)
else if (configService.External)
{
throw new CommandException("External services do not support environment variables.");
var external = new ExternalServiceBuilder(configService.Name);
service = external;
}
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)
// There's no hierarchy, just add it to the list of services.
root.Services.Add(service);
// If there are no bindings and we're in ASP.NET Core project then add an HTTP and HTTPS binding
if (configService.Bindings.Count == 0 &&
service is ProjectServiceBuilder project2 &&
project2.IsAspNet)
{
container.Volumes.Add(volume);
// HTTP is the default binding
service.Bindings.Add(new BindingBuilder()
{
AutoAssignPort = true,
Protocol = "http"
});
service.Bindings.Add(new BindingBuilder()
{
Name = "https",
AutoAssignPort = true,
Protocol = "https"
});
}
else if (service is ExecutableServiceBuilder executable)
else
{
throw new CommandException("Executable services do not support volumes.");
foreach (var configBinding in configService.Bindings)
{
var binding = new BindingBuilder()
{
Name = configBinding.Name,
AutoAssignPort = configBinding.AutoAssignPort,
ConnectionString = configBinding.ConnectionString,
Host = configBinding.Host,
ContainerPort = configBinding.ContainerPort,
Port = configBinding.Port,
Protocol = configBinding.Protocol,
};
// Assume HTTP for projects only (containers may be different)
if (binding.ConnectionString == null && configService.Project != null)
{
binding.Protocol ??= "http";
}
service.Bindings.Add(binding);
}
}
else if (service is ExternalServiceBuilder)
foreach (var configEnvVar in configService.Configuration)
{
throw new CommandException("External services do not support volumes.");
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.");
}
}
else
foreach (var configVolume in configService.Volumes)
{
throw new CommandException("Unable to determine service type.");
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;
foreach (var configIngress in config.Ingress)
{
var ingress = new IngressBuilder(configIngress.Name);
ingress.Replicas = configIngress.Replicas ?? 1;
builder.Ingress.Add(ingress);
root.Ingress.Add(ingress);
foreach (var configBinding in configIngress.Bindings)
{
var binding = new IngressBindingBuilder()
foreach (var configBinding in configIngress.Bindings)
{
AutoAssignPort = configBinding.AutoAssignPort,
Name = configBinding.Name,
Port = configBinding.Port,
Protocol = configBinding.Protocol ?? "http",
};
ingress.Bindings.Add(binding);
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);
}
}
foreach (var configRule in configIngress.Rules)
foreach (var configDependency in config.Dependencies)
{
var rule = new IngressRuleBuilder()
{
Host = configRule.Host,
Path = configRule.Path,
Service = configRule.Service,
};
ingress.Rules.Add(rule);
// TODO: Validate the extension is yaml
var expandedPath = Environment.ExpandEnvironmentVariables(configDependency.Path);
var dependencyFile = new FileInfo(Path.Combine(config.Source.DirectoryName, expandedPath));
var dependencyConfig = ConfigFactory.FromFile(dependencyFile);
ValidateConfigApplication(dependencyConfig);
queue.Enqueue(dependencyConfig);
}
}
return builder;
return root;
}
private static void ValidateConfigApplication(ConfigApplication config)

2
src/Microsoft.Tye.Core/ConfigModel/ConfigApplication.cs

@ -26,5 +26,7 @@ namespace Microsoft.Tye.ConfigModel
public List<ConfigService> Services { get; set; } = new List<ConfigService>();
public List<ConfigIngress> Ingress { get; set; } = new List<ConfigIngress>();
public List<ConfigDependency> Dependencies { get; set; } = new List<ConfigDependency>();
}
}

13
src/Microsoft.Tye.Core/ConfigModel/ConfigDependency.cs

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;
namespace Microsoft.Tye.ConfigModel
{
public class ConfigDependency
{
[Required]
public string? Path { get; set; }
}
}
Loading…
Cancel
Save