Example #1
0
        public static async Task <ApplicationBuilder> CreateAsync(OutputContext output, FileInfo source, ApplicationFactoryFilter?filter = null)
        {
            if (source is null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            var queue   = new Queue <(ConfigApplication, HashSet <string>)>();
            var visited = new HashSet <string>(StringComparer.OrdinalIgnoreCase);

            var rootConfig = ConfigFactory.FromFile(source);

            rootConfig.Validate();

            var root = new ApplicationBuilder(source, rootConfig.Name !);

            root.Namespace = rootConfig.Namespace;

            queue.Enqueue((rootConfig, new HashSet <string>()));

            while (queue.Count > 0)
            {
                var item   = queue.Dequeue();
                var config = item.Item1;

                // dependencies represents a set of all dependencies
                var dependencies = item.Item2;
                if (!visited.Add(config.Source.FullName))
                {
                    continue;
                }

                if (config == rootConfig && !string.IsNullOrEmpty(config.Registry))
                {
                    root.Registry = new ContainerRegistry(config.Registry);
                }

                if (config == rootConfig)
                {
                    root.Network = rootConfig.Network;
                }

                foreach (var configExtension in config.Extensions)
                {
                    var extension = new ExtensionConfiguration((string)configExtension["name"]);
                    foreach (var kvp in configExtension)
                    {
                        if (kvp.Key == "name")
                        {
                            continue;
                        }

                        extension.Data.Add(kvp.Key, kvp.Value);
                    }

                    root.Extensions.Add(extension);
                }

                var services = filter?.ServicesFilter != null?
                               config.Services.Where(filter.ServicesFilter).ToList() :
                                   config.Services;

                foreach (var configService in services)
                {
                    ServiceBuilder service;
                    if (root.Services.Any(s => s.Name == configService.Name))
                    {
                        // Even though this service has already created a service, we still need
                        // to update dependency information
                        AddToRootServices(root, dependencies, configService.Name);
                        continue;
                    }

                    if (!string.IsNullOrEmpty(configService.Project))
                    {
                        var expandedProject = Environment.ExpandEnvironmentVariables(configService.Project);
                        var projectFile     = new FileInfo(Path.Combine(config.Source.DirectoryName, expandedProject));
                        var project         = new DotnetProjectServiceBuilder(configService.Name !, projectFile);
                        service = project;

                        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;

                        project.Liveness = configService.Liveness != null?GetProbeBuilder(configService.Liveness) : null;

                        project.Readiness = configService.Readiness != null?GetProbeBuilder(configService.Readiness) : null;

                        // We don't apply more container defaults here because we might need
                        // to prompt for the registry name.
                        project.ContainerInfo = new ContainerInfo()
                        {
                            UseMultiphaseDockerfile = false,
                        };

                        await ProjectReader.ReadProjectDetailsAsync(output, project);

                        // Do k8s by default.
                        project.ManifestInfo = new KubernetesManifestInfo();
                    }
                    else if (!string.IsNullOrEmpty(configService.Image))
                    {
                        var container = new ContainerServiceBuilder(configService.Name !, configService.Image !)
                        {
                            Args     = configService.Args,
                            Replicas = configService.Replicas ?? 1
                        };
                        service = container;

                        container.Liveness = configService.Liveness != null?GetProbeBuilder(configService.Liveness) : null;

                        container.Readiness = configService.Readiness != null?GetProbeBuilder(configService.Readiness) : null;
                    }
                    else if (!string.IsNullOrEmpty(configService.DockerFile))
                    {
                        var dockerFile = new DockerFileServiceBuilder(configService.Name !, configService.Image !)
                        {
                            Args       = configService.Args,
                            Build      = configService.Build ?? true,
                            Replicas   = configService.Replicas ?? 1,
                            DockerFile = Path.Combine(source.DirectoryName, configService.DockerFile),
                            // Supplying an absolute path with trailing slashes fails for DockerFileContext when calling docker build, so trim trailing slash.
                            DockerFileContext = GetDockerFileContext(source, configService),
                            BuildArgs         = configService.DockerFileArgs
                        };
                        service = dockerFile;

                        dockerFile.Liveness = configService.Liveness != null?GetProbeBuilder(configService.Liveness) : null;

                        dockerFile.Readiness = configService.Readiness != null?GetProbeBuilder(configService.Readiness) : null;

                        // We don't apply more container defaults here because we might need
                        // to prompt for the registry name.
                        dockerFile.ContainerInfo = new ContainerInfo()
                        {
                            UseMultiphaseDockerfile = false,
                        };

                        // Do k8s by default.
                        dockerFile.ManifestInfo = new KubernetesManifestInfo();
                    }
                    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")
                        {
                            expandedExecutable = Path.GetFullPath(Path.Combine(config.Source.Directory.FullName, expandedExecutable));
                            workingDirectory   = Path.GetDirectoryName(expandedExecutable) !;
                        }

                        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;

                        executable.Liveness = configService.Liveness != null?GetProbeBuilder(configService.Liveness) : null;

                        executable.Readiness = configService.Readiness != null?GetProbeBuilder(configService.Readiness) : null;
                    }
                    else if (!string.IsNullOrEmpty(configService.Include))
                    {
                        var expandedYaml = Environment.ExpandEnvironmentVariables(configService.Include);

                        var nestedConfig = GetNestedConfig(rootConfig, Path.Combine(config.Source.DirectoryName, expandedYaml));
                        queue.Enqueue((nestedConfig, new HashSet <string>()));

                        AddToRootServices(root, dependencies, configService.Name);
                        continue;
                    }
                    else if (!string.IsNullOrEmpty(configService.Repository))
                    {
                        // clone to .tye folder
                        var path = configService.CloneDirectory ?? Path.Join(rootConfig.Source.DirectoryName, ".tye", "deps");
                        if (!Directory.Exists(path))
                        {
                            Directory.CreateDirectory(path);
                        }

                        var clonePath = Path.Combine(path, configService.Name);

                        if (!Directory.Exists(clonePath))
                        {
                            if (!await GitDetector.Instance.IsGitInstalled.Value)
                            {
                                throw new CommandException($"Cannot clone repository {configService.Repository} because git is not installed. Please install git if you'd like to use \"repository\" in tye.yaml.");
                            }

                            var result = await ProcessUtil.RunAsync("git", $"clone {configService.Repository} {clonePath}", workingDirectory : path, throwOnError : false);

                            if (result.ExitCode != 0)
                            {
                                throw new CommandException($"Failed to clone repository {configService.Repository} with exit code {result.ExitCode}.{Environment.NewLine}{result.StandardError}{result.StandardOutput}.");
                            }
                        }

                        if (!ConfigFileFinder.TryFindSupportedFile(clonePath, out var file, out var errorMessage))
                        {
                            throw new CommandException(errorMessage !);
                        }

                        // pick different service type based on what is in the repo.
                        var nestedConfig = GetNestedConfig(rootConfig, file);

                        queue.Enqueue((nestedConfig, new HashSet <string>()));

                        AddToRootServices(root, dependencies, configService.Name);

                        continue;
                    }
                    else if (!string.IsNullOrEmpty(configService.AzureFunction))
                    {
                        var azureFunctionDirectory = Path.Combine(config.Source.DirectoryName !, configService.AzureFunction);

                        var functionBuilder = new AzureFunctionServiceBuilder(
                            configService.Name,
                            azureFunctionDirectory)
                        {
                            Args               = configService.Args,
                            Replicas           = configService.Replicas ?? 1,
                            FuncExecutablePath = configService.FuncExecutable,
                        };

                        foreach (var proj in Directory.EnumerateFiles(azureFunctionDirectory))
                        {
                            var fileInfo = new FileInfo(proj);
                            if (fileInfo.Extension == ".csproj" || fileInfo.Extension == ".fsproj")
                            {
                                functionBuilder.ProjectFile = fileInfo.FullName;
                                break;
                            }
                        }

                        // TODO liveness?
                        service = functionBuilder;
                    }
                    else if (configService.External)
                    {
                        var external = new ExternalServiceBuilder(configService.Name);
                        service = external;
                    }
                    else
                    {
                        throw new CommandException("Unable to determine service type.");
                    }

                    // Add dependencies to ourself before adding ourself to avoid self reference
                    service.Dependencies.UnionWith(dependencies);

                    AddToRootServices(root, dependencies, service.Name);

                    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)
                    {
                        // HTTP is the default binding
                        service.Bindings.Add(new BindingBuilder()
                        {
                            Protocol = "http"
                        });
                        service.Bindings.Add(new BindingBuilder()
                        {
                            Name = "https", Protocol = "https"
                        });
                    }
Example #2
0
        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);
            }

            builder.Network = config.Network;

            foreach (var configExtension in config.Extensions)
            {
                var extension = new ExtensionConfiguration((string)configExtension["name"]);
                foreach (var kvp in configExtension)
                {
                    if (kvp.Key == "name")
                    {
                        continue;
                    }

                    extension.Data.Add(kvp.Key, kvp.Value);
                }

                builder.Extensions.Add(extension);
            }

            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;

                    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 prompt for the registry name.
                    project.ContainerInfo = new ContainerInfo()
                    {
                        UseMultiphaseDockerfile = false,
                    };

                    // Do k8s by default.
                    project.ManifestInfo = new KubernetesManifestInfo();
                }
                else if (!string.IsNullOrEmpty(configService.Image))
                {
                    var container = new ContainerServiceBuilder(configService.Name, configService.Image)
                    {
                        Args     = configService.Args,
                        Replicas = configService.Replicas ?? 1
                    };
                    service = container;
                }
                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")
                    {
                        expandedExecutable = Path.GetFullPath(Path.Combine(builder.Source.Directory.FullName, expandedExecutable));
                        workingDirectory   = Path.GetDirectoryName(expandedExecutable) !;
                    }

                    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.");
                }

                builder.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)
                {
                    // HTTP is the default binding
                    service.Bindings.Add(new BindingBuilder()
                    {
                        Protocol = "http"
                    });

                    service.Bindings.Add(new BindingBuilder()
                    {
                        Name     = "https",
                        Protocol = "https"
                    });
                }
Example #3
0
        public static async Task <ApplicationBuilder> CreateAsync(OutputContext output, FileInfo source, string?framework = null, ApplicationFactoryFilter?filter = null)
        {
            if (source is null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            var queue   = new Queue <(ConfigApplication, HashSet <string>)>();
            var visited = new HashSet <string>(StringComparer.OrdinalIgnoreCase);

            var rootConfig = ConfigFactory.FromFile(source);

            rootConfig.Validate();

            var root = new ApplicationBuilder(source, rootConfig.Name !, new ContainerEngine(rootConfig.ContainerEngineType))
            {
                Namespace = rootConfig.Namespace
            };

            queue.Enqueue((rootConfig, new HashSet <string>()));

            while (queue.TryDequeue(out var item))
            {
                // dependencies represents a set of all dependencies
                var(config, dependencies) = item;

                if (!visited.Add(config.Source.FullName))
                {
                    continue;
                }

                if (config == rootConfig && !string.IsNullOrEmpty(config.Registry))
                {
                    root.Registry = new ContainerRegistry(config.Registry);
                }

                if (config == rootConfig)
                {
                    root.Network = rootConfig.Network;
                }

                foreach (var configExtension in config.Extensions)
                {
                    var extension = new ExtensionConfiguration((string)configExtension["name"]);
                    foreach (var kvp in configExtension)
                    {
                        if (kvp.Key == "name")
                        {
                            continue;
                        }

                        extension.Data.Add(kvp.Key, kvp.Value);
                    }

                    root.Extensions.Add(extension);
                }

                var services = filter?.ServicesFilter != null?
                               config.Services.Where(filter.ServicesFilter).ToList() :
                                   config.Services;

                var sw = Stopwatch.StartNew();
                // Project services will be restored and evaluated before resolving all other services.
                // This batching will mitigate the performance cost of running MSBuild out of process.
                var projectServices = services.Where(s => !string.IsNullOrEmpty(s.Project));
                var projectMetadata = new Dictionary <string, string>();

                var msbuildEvaluationResult = await EvaluateProjectsAsync(
                    projects : projectServices,
                    configRoot : config.Source.DirectoryName !,
                    output : output);

                var msbuildEvaluationOutput = msbuildEvaluationResult
                                              .StandardOutput
                                              .Split(Environment.NewLine);

                var multiTFMProjects = new List <ConfigService>();

                foreach (var line in msbuildEvaluationOutput)
                {
                    var trimmed = line.Trim();
                    if (trimmed.StartsWith("Microsoft.Tye metadata: "))
                    {
                        var values       = line.Split(':', 3);
                        var projectName  = values[1].Trim();
                        var metadataPath = values[2].Trim();
                        projectMetadata.Add(projectName, metadataPath);

                        output.WriteDebugLine($"Resolved metadata for service {projectName} at {metadataPath}");
                    }
                    else if (trimmed.StartsWith("Microsoft.Tye cross-targeting project: "))
                    {
                        var values      = line.Split(':', 2);
                        var projectName = values[1].Trim();

                        var multiTFMConfigService = projectServices.First(p => string.Equals(p.Name, projectName, StringComparison.OrdinalIgnoreCase));
                        multiTFMConfigService.BuildProperties.Add(new BuildProperty {
                            Name = "TargetFramework", Value = framework ?? string.Empty
                        });
                        multiTFMProjects.Add(multiTFMConfigService);
                    }
                }

                if (multiTFMProjects.Any())
                {
                    output.WriteDebugLine("Re-evaluating multi-targeted projects");

                    var multiTFMEvaluationResult = await EvaluateProjectsAsync(
                        projects : multiTFMProjects,
                        configRoot : config.Source.DirectoryName !,
                        output : output);

                    var multiTFMEvaluationOutput = multiTFMEvaluationResult
                                                   .StandardOutput
                                                   .Split(Environment.NewLine);

                    foreach (var line in multiTFMEvaluationOutput)
                    {
                        var trimmed = line.Trim();
                        if (trimmed.StartsWith("Microsoft.Tye metadata: "))
                        {
                            var values       = line.Split(':', 3);
                            var projectName  = values[1].Trim();
                            var metadataPath = values[2].Trim();
                            projectMetadata.Add(projectName, metadataPath);

                            output.WriteDebugLine($"Resolved metadata for service {projectName} at {metadataPath}");
                        }
                        else if (trimmed.StartsWith("Microsoft.Tye cross-targeting project: "))
                        {
                            var values      = line.Split(':', 2);
                            var projectName = values[1].Trim();
                            throw new CommandException($"Unable to run {projectName}. Your project targets multiple frameworks. Specify which framework to run using '--framework' or a build property in tye.yaml.");
                        }
                    }
                }

                output.WriteDebugLine($"Restore and project evaluation took: {sw.Elapsed.TotalMilliseconds}ms");

                foreach (var configService in services)
                {
                    ServiceBuilder service;
                    if (root.Services.Any(s => s.Name == configService.Name))
                    {
                        // Even though this service has already created a service, we still need
                        // to update dependency information
                        AddToRootServices(root, dependencies, configService.Name);
                        continue;
                    }

                    if (!string.IsNullOrEmpty(configService.Project))
                    {
                        var project = new DotnetProjectServiceBuilder(configService.Name !, new FileInfo(configService.ProjectFullPath));
                        service = project;

                        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;
                        project.Liveness = configService.Liveness != null?GetProbeBuilder(configService.Liveness) : null;

                        project.Readiness = configService.Readiness != null?GetProbeBuilder(configService.Readiness) : null;

                        // We don't apply more container defaults here because we might need
                        // to prompt for the registry name.
                        project.ContainerInfo = new ContainerInfo()
                        {
                            UseMultiphaseDockerfile = false,
                        };

                        // If project evaluation is successful this should not happen, therefore an exception will be thrown.
                        if (!projectMetadata.ContainsKey(configService.Name))
                        {
                            throw new CommandException($"Evaluated project metadata file could not be found for service {configService.Name}");
                        }

                        ProjectReader.ReadProjectDetails(output, project, projectMetadata[configService.Name]);

                        // Do k8s by default.
                        project.ManifestInfo = new KubernetesManifestInfo();
                    }
                    else if (!string.IsNullOrEmpty(configService.Image))
                    {
                        var container = new ContainerServiceBuilder(configService.Name !, configService.Image !)
                        {
                            Args     = configService.Args,
                            Replicas = configService.Replicas ?? 1
                        };
                        service = container;

                        container.Liveness = configService.Liveness != null?GetProbeBuilder(configService.Liveness) : null;

                        container.Readiness = configService.Readiness != null?GetProbeBuilder(configService.Readiness) : null;
                    }
                    else if (!string.IsNullOrEmpty(configService.DockerFile))
                    {
                        var dockerFile = new DockerFileServiceBuilder(configService.Name !, configService.Image !)
                        {
                            Args       = configService.Args,
                            Build      = configService.Build ?? true,
                            Replicas   = configService.Replicas ?? 1,
                            DockerFile = Path.Combine(source.DirectoryName !, configService.DockerFile),
                            // Supplying an absolute path with trailing slashes fails for DockerFileContext when calling docker build, so trim trailing slash.
                            DockerFileContext = GetDockerFileContext(source, configService),
                            BuildArgs         = configService.DockerFileArgs
                        };
                        service = dockerFile;

                        dockerFile.Liveness = configService.Liveness != null?GetProbeBuilder(configService.Liveness) : null;

                        dockerFile.Readiness = configService.Readiness != null?GetProbeBuilder(configService.Readiness) : null;

                        // We don't apply more container defaults here because we might need
                        // to prompt for the registry name.
                        dockerFile.ContainerInfo = new ContainerInfo()
                        {
                            UseMultiphaseDockerfile = false,
                        };

                        // Do k8s by default.
                        dockerFile.ManifestInfo = new KubernetesManifestInfo();
                    }
                    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")
                        {
                            expandedExecutable = Path.GetFullPath(Path.Combine(config.Source.Directory !.FullName, expandedExecutable));
                            workingDirectory   = Path.GetDirectoryName(expandedExecutable) !;
                        }

                        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;

                        executable.Liveness = configService.Liveness != null?GetProbeBuilder(configService.Liveness) : null;

                        executable.Readiness = configService.Readiness != null?GetProbeBuilder(configService.Readiness) : null;
                    }
                    else if (!string.IsNullOrEmpty(configService.Include))
                    {
                        var expandedYaml = Environment.ExpandEnvironmentVariables(configService.Include);

                        var nestedConfig = GetNestedConfig(rootConfig, Path.Combine(config.Source.DirectoryName !, expandedYaml));
                        queue.Enqueue((nestedConfig, new HashSet <string>()));

                        AddToRootServices(root, dependencies, configService.Name);
                        continue;
                    }
                    else if (!string.IsNullOrEmpty(configService.Repository))
                    {
                        // clone to .tye folder
                        var path = configService.CloneDirectory ?? Path.Join(".tye", "deps");
                        if (!Directory.Exists(path))
                        {
                            Directory.CreateDirectory(path);
                        }

                        var clonePath = Path.Combine(rootConfig.Source.DirectoryName !, path, configService.Name);

                        if (!Directory.Exists(clonePath))
                        {
                            if (!await GitDetector.Instance.IsGitInstalled.Value)
                            {
                                throw new CommandException($"Cannot clone repository {configService.Repository} because git is not installed. Please install git if you'd like to use \"repository\" in tye.yaml.");
                            }

                            var result = await ProcessUtil.RunAsync("git", $"clone {configService.Repository} \"{clonePath}\"", workingDirectory : rootConfig.Source.DirectoryName, throwOnError : false);

                            if (result.ExitCode != 0)
                            {
                                throw new CommandException($"Failed to clone repository {configService.Repository} with exit code {result.ExitCode}.{Environment.NewLine}{result.StandardError}{result.StandardOutput}.");
                            }
                        }

                        if (!ConfigFileFinder.TryFindSupportedFile(clonePath, out var file, out var errorMessage))
                        {
                            throw new CommandException(errorMessage !);
                        }

                        // pick different service type based on what is in the repo.
                        var nestedConfig = GetNestedConfig(rootConfig, file);

                        queue.Enqueue((nestedConfig, new HashSet <string>()));

                        AddToRootServices(root, dependencies, configService.Name);

                        continue;
                    }
                    else if (!string.IsNullOrEmpty(configService.AzureFunction))
                    {
                        var azureFunctionDirectory = Path.Combine(config.Source.DirectoryName !, configService.AzureFunction);

                        var functionBuilder = new AzureFunctionServiceBuilder(
                            configService.Name,
                            azureFunctionDirectory)
                        {
                            Args               = configService.Args,
                            Replicas           = configService.Replicas ?? 1,
                            FuncExecutablePath = configService.FuncExecutable,
                        };

                        foreach (var proj in Directory.EnumerateFiles(azureFunctionDirectory))
                        {
                            var fileInfo = new FileInfo(proj);
                            if (fileInfo.Extension == ".csproj" || fileInfo.Extension == ".fsproj")
                            {
                                functionBuilder.ProjectFile = fileInfo.FullName;
                                break;
                            }
                        }

                        // TODO liveness?
                        service = functionBuilder;
                    }
                    else if (configService.External)
                    {
                        var external = new ExternalServiceBuilder(configService.Name);
                        service = external;
                    }
                    else
                    {
                        throw new CommandException("Unable to determine service type.");
                    }

                    // Add dependencies to ourself before adding ourself to avoid self reference
                    service.Dependencies.UnionWith(dependencies);

                    AddToRootServices(root, dependencies, service.Name);

                    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)
                    {
                        // HTTP is the default binding
                        service.Bindings.Add(new BindingBuilder()
                        {
                            Protocol = "http"
                        });
                        service.Bindings.Add(new BindingBuilder()
                        {
                            Name = "https", Protocol = "https"
                        });
                    }
Example #4
0
        internal static async Task<TemporaryApplicationAdapter> CreateApplicationAdapterAsync(OutputContext output, ConfigApplication application, bool interactive, bool requireRegistry)
        {
            var globals = new ApplicationGlobals()
            {
                Name = application.Name,
                Registry = application.Registry is null ? null : new ContainerRegistry(application.Registry),
            };

            var services = new List<Tye.ServiceEntry>();
            foreach (var configService in application.Services)
            {
                if (configService.Project is string projectFile)
                {
                    var fullPathProjectFile = Path.Combine(application.Source.DirectoryName, projectFile);
                    var project = new Project(fullPathProjectFile);
                    var service = new Service(configService.Name)
                    {
                        Source = project,
                    };

                    service.Replicas = configService.Replicas ?? 1;

                    foreach (var configBinding in configService.Bindings)
                    {
                        var binding = new ServiceBinding(configBinding.Name ?? service.Name)
                        {
                            ConnectionString = configBinding.ConnectionString,
                            Host = configBinding.Host,
                            Port = configBinding.Port,
                            Protocol = configBinding.Protocol,
                        };

                        binding.Protocol ??= "http";

                        if (binding.Port == null && configBinding.AutoAssignPort)
                        {
                            if (binding.Protocol == "http" || binding.Protocol == null)
                            {
                                binding.Port = 80;
                            }
                            else if (binding.Protocol == "https")
                            {
                                binding.Port = 443;
                            }
                        }

                        service.Bindings.Add(binding);
                    }

                    var serviceEntry = new ServiceEntry(service, configService.Name);

                    await ProjectReader.ReadProjectDetailsAsync(output, new FileInfo(fullPathProjectFile), project);

                    var container = new ContainerInfo()
                    {
                        UseMultiphaseDockerfile = false,
                    };
                    service.GeneratedAssets.Container = container;
                    services.Add(serviceEntry);
                }
                else
                {
                    // For a non-project, we don't really need much info about it, just the name and bindings
                    var service = new Service(configService.Name);
                    foreach (var configBinding in configService.Bindings)
                    {
                        service.Bindings.Add(new ServiceBinding(configBinding.Name ?? service.Name)
                        {
                            ConnectionString = configBinding.ConnectionString,
                            Host = configBinding.Host,
                            Port = configBinding.Port,
                            Protocol = configBinding.Protocol,
                        });
                    }

                    var serviceEntry = new ServiceEntry(service, configService.Name);
                    services.Add(serviceEntry);
                }
            }

            var temporaryApplication = new TemporaryApplicationAdapter(application, globals, services);
            if (temporaryApplication.Globals.Registry?.Hostname == null && interactive)
            {
                var registry = output.Prompt("Enter the Container Registry (ex: 'example.azurecr.io' for Azure or 'example' for dockerhub)", allowEmpty: !requireRegistry);
                if (!string.IsNullOrWhiteSpace(registry))
                {
                    temporaryApplication.Globals.Registry = new ContainerRegistry(registry.Trim());
                }
            }
            else if (temporaryApplication.Globals.Registry?.Hostname == null && requireRegistry)
            {
                throw new CommandException("A registry is required for deploy operations. Add the registry to 'tye.yaml' or use '-i' for interactive mode.");
            }
            else
            {
                // No registry specified, and that's OK!
            }

            foreach (var service in temporaryApplication.Services)
            {
                if (service.Service.Source is Project project && service.Service.GeneratedAssets.Container is ContainerInfo container)
                {
                    DockerfileGenerator.ApplyContainerDefaults(temporaryApplication, service, project, container);
                }
            }

            return temporaryApplication;
        }
Example #5
0
        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,
                        ContainerPort    = configBinding.ContainerPort,
                        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);
        }
        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);
        }