public override Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service)
        {
            if (SkipWithoutProject(output, service, out var project))
            {
                return(Task.CompletedTask);
            }

            if (SkipWithoutContainerInfo(output, service, out var container))
            {
                return(Task.CompletedTask);
            }

            if (project is DotnetProjectServiceBuilder dotnetProject)
            {
                DockerfileGenerator.ApplyContainerDefaults(application, dotnetProject, container);
            }
            else if (project is DockerFileServiceBuilder dockerFile)
            {
                DockerfileGenerator.ApplyContainerDefaults(application, dockerFile, container);
            }
            return(Task.CompletedTask);
        }
Exemplo n.º 2
0
        public async Task ExecuteAsync(ApplicationBuilder application)
        {
            foreach (var service in application.Services)
            {
                using var tracker = output.BeginStep($"Processing Service '{service.Name}'...");
                foreach (var step in ServiceSteps)
                {
                    using var stepTracker = output.BeginStep(step.DisplayText);
                    await step.ExecuteAsync(output, application, service);

                    stepTracker.MarkComplete();
                }
                tracker.MarkComplete();
            }

            foreach (var ingress in application.Ingress)
            {
                using var tracker = output.BeginStep($"Processing Ingress '{ingress.Name}'...");
                foreach (var step in IngressSteps)
                {
                    using var stepTracker = output.BeginStep(step.DisplayText);
                    await step.ExecuteAsync(output, application, ingress);

                    stepTracker.MarkComplete();
                }
                tracker.MarkComplete();
            }

            {
                foreach (var step in ApplicationSteps)
                {
                    using var stepTracker = output.BeginStep(step.DisplayText);
                    await step.ExecuteAsync(output, application);

                    stepTracker.MarkComplete();
                }
            }
        }
Exemplo n.º 3
0
        public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service)
        {
            if (SkipWithoutProject(output, service, out var project))
            {
                return;
            }

            if (SkipWithoutContainerInfo(output, service, out var container))
            {
                return;
            }

            if (container.UseMultiphaseDockerfile != false)
            {
                return;
            }

            var outputDirectory = Path.Combine(project.ProjectFile.DirectoryName, "bin", "Release", project.TargetFramework, "publish");

            output.WriteDebugLine("Running 'dotnet publish'.");
            output.WriteCommandLine("dotnet", $"publish \"{project.ProjectFile.FullName}\" -c Release -o \"{outputDirectory}\"");
            var capture  = output.Capture();
            var exitCode = await Process.ExecuteAsync(
                $"dotnet",
                $"publish \"{project.ProjectFile.FullName}\" -c Release -o \"{outputDirectory}\"",
                project.ProjectFile.DirectoryName,
                stdOut : capture.StdOut,
                stdErr : capture.StdErr);

            output.WriteDebugLine($"Done running 'dotnet publish' exit code: {exitCode}");
            if (exitCode != 0)
            {
                throw new CommandException("'dotnet publish' failed.");
            }

            output.WriteInfoLine($"Created Publish Output: '{outputDirectory}'");
            service.Outputs.Add(new ProjectPublishOutput(new DirectoryInfo(outputDirectory)));
        }
        public override Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service)
        {
            if (SkipWithoutContainerOutput(output, service))
            {
                return(Task.CompletedTask);
            }

            if (SkipWithoutProject(output, service, out var project))
            {
                return(Task.CompletedTask);
            }

            var deployment = project.ManifestInfo?.Deployment;

            if (deployment is null)
            {
                return(Task.CompletedTask);
            }

            // Initialize defaults for deployment-related settings
            deployment.Labels.TryAdd("app.kubernetes.io/name", project.Name);
            deployment.Labels.TryAdd("app.kubernetes.io/part-of", application.Name);

            service.Outputs.Add(KubernetesManifestGenerator.CreateDeployment(output, application, project, deployment));

            if (service.Bindings.Count > 0 &&
                project.ManifestInfo?.Service is ServiceManifestInfo k8sService)
            {
                // Initialize defaults for service-related settings
                k8sService.Labels.TryAdd("app.kubernetes.io/name", project.Name);
                k8sService.Labels.TryAdd("app.kubernetes.io/part-of", application.Name);

                service.Outputs.Add(KubernetesManifestGenerator.CreateService(output, application, project, deployment, k8sService));
            }

            return(Task.CompletedTask);
        }
Exemplo n.º 5
0
        public static async Task ExecuteBuildAsync(OutputContext output, ApplicationBuilder application, string environment, bool interactive)
        {
            await application.ProcessExtensionsAsync(output, ExtensionContext.OperationKind.Deploy);

            Program.ApplyRegistry(output, application, interactive, requireRegistry: false);

            var executor = new ApplicationExecutor(output)
            {
                ServiceSteps =
                {
                    new ApplyContainerDefaultsStep(),
                    new CombineStep()
                    {
                        Environment = environment,
                    },
                    new PublishProjectStep(),
                    new BuildDockerImageStep()
                    {
                        Environment = environment,
                    },
                },
            };
            await executor.ExecuteAsync(application);
        }
Exemplo n.º 6
0
        public static async Task ExecuteBuildAsync(OutputContext output, ApplicationBuilder application, string environment, bool interactive)
        {
            var steps = new List <ServiceExecutor.Step>()
            {
                new CombineStep()
                {
                    Environment = environment,
                },
                new PublishProjectStep(),
                new BuildDockerImageStep()
                {
                    Environment = environment,
                },
            };

            Program.ApplyRegistryAndDefaults(output, application, interactive, requireRegistry: false);

            var executor = new ServiceExecutor(output, application, steps);

            foreach (var service in application.Services)
            {
                await executor.ExecuteAsync(service);
            }
        }
Exemplo n.º 7
0
        public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service)
        {
            if (SkipWithoutProject(output, service, out var project))
            {
                return;
            }

            if (SkipWithoutContainerInfo(output, service, out var container))
            {
                return;
            }

            if (!await DockerDetector.Instance.IsDockerInstalled.Value)
            {
                throw new CommandException($"Cannot generate a docker image for '{service.Name}' because docker is not installed.");
            }

            if (!await DockerDetector.Instance.IsDockerConnectedToDaemon.Value)
            {
                throw new CommandException($"Cannot generate a docker image for '{service.Name}' because docker is not running.");
            }

            await DockerContainerBuilder.BuildContainerImageAsync(output, application, project, container);
        }
Exemplo n.º 8
0
        public static void ApplyHelmChartDefaults(ApplicationBuilder application, ServiceBuilder service, ContainerInfo container, HelmChartStep chart)
        {
            if (application is null)
            {
                throw new ArgumentNullException(nameof(application));
            }

            if (service is null)
            {
                throw new ArgumentNullException(nameof(service));
            }

            if (container is null)
            {
                throw new ArgumentNullException(nameof(container));
            }

            if (chart is null)
            {
                throw new ArgumentNullException(nameof(chart));
            }

            chart.ChartName ??= service.Name.ToLowerInvariant();
        }
Exemplo n.º 9
0
        public static KubernetesIngressOutput CreateIngress(
            OutputContext output,
            ApplicationBuilder application,
            IngressBuilder ingress)
        {
            var root = new YamlMappingNode();

            root.Add("kind", "Ingress");
            root.Add("apiVersion", "extensions/v1beta1");

            var metadata = new YamlMappingNode();

            root.Add("metadata", metadata);
            metadata.Add("name", ingress.Name);
            if (!string.IsNullOrEmpty(application.Namespace))
            {
                metadata.Add("namespace", application.Namespace);
            }

            var annotations = new YamlMappingNode();

            metadata.Add("annotations", annotations);
            annotations.Add("kubernetes.io/ingress.class", new YamlScalarNode("nginx")
            {
                Style = ScalarStyle.SingleQuoted,
            });
            annotations.Add("nginx.ingress.kubernetes.io/rewrite-target", new YamlScalarNode("/$2")
            {
                Style = ScalarStyle.SingleQuoted,
            });

            var labels = new YamlMappingNode();

            metadata.Add("labels", labels);
            labels.Add("app.kubernetes.io/part-of", new YamlScalarNode(application.Name)
            {
                Style = ScalarStyle.SingleQuoted,
            });

            var spec = new YamlMappingNode();

            root.Add("spec", spec);

            if (ingress.Rules.Count > 0)
            {
                var rules = new YamlSequenceNode();
                spec.Add("rules", rules);

                // k8s ingress is grouped by host first, then grouped by path
                foreach (var hostgroup in ingress.Rules.GroupBy(r => r.Host))
                {
                    var rule = new YamlMappingNode();
                    rules.Add(rule);

                    if (!string.IsNullOrEmpty(hostgroup.Key))
                    {
                        rule.Add("host", hostgroup.Key);
                    }

                    var http = new YamlMappingNode();
                    rule.Add("http", http);

                    var paths = new YamlSequenceNode();
                    http.Add("paths", paths);

                    foreach (var ingressRule in hostgroup)
                    {
                        var path = new YamlMappingNode();
                        paths.Add(path);

                        var backend = new YamlMappingNode();
                        path.Add("backend", backend);
                        backend.Add("serviceName", ingressRule.Service);

                        var service = application.Services.FirstOrDefault(s => s.Name == ingressRule.Service);
                        if (service is null)
                        {
                            throw new InvalidOperationException($"Could not resolve service '{ingressRule.Service}'.");
                        }

                        var binding = service.Bindings.FirstOrDefault(b => b.Name is null || b.Name == "http");
                        if (binding is null)
                        {
                            throw new InvalidOperationException($"Could not resolve an http binding for service '{service.Name}'.");
                        }

                        backend.Add("servicePort", (binding.Port ?? 80).ToString(CultureInfo.InvariantCulture));

                        // Tye implements path matching similar to this example:
                        // https://kubernetes.github.io/ingress-nginx/examples/rewrite/
                        //
                        // Therefore our rewrite-target is set to $2 - we want to make sure we have
                        // two capture groups.
                        if (string.IsNullOrEmpty(ingressRule.Path) || ingressRule.Path == "/")
                        {
                            path.Add("path", "/()(.*)"); // () is an empty capture group.
                        }
                        else
                        {
                            var regex = $"{ingressRule.Path.TrimEnd('/')}(/|$)(.*)";
                            path.Add("path", regex);
                        }
                    }
                }
            }

            return(new KubernetesIngressOutput(ingress.Name, new YamlDocument(root)));
        }
Exemplo n.º 10
0
        public static async Task BuildHelmChartAsync(OutputContext output, ApplicationBuilder application, ProjectServiceBuilder project, ContainerInfo container, HelmChartStep chart)
        {
            if (output is null)
            {
                throw new ArgumentNullException(nameof(output));
            }

            if (application is null)
            {
                throw new ArgumentNullException(nameof(application));
            }

            if (project is null)
            {
                throw new ArgumentNullException(nameof(project));
            }

            if (container is null)
            {
                throw new ArgumentNullException(nameof(container));
            }

            if (chart is null)
            {
                throw new ArgumentNullException(nameof(chart));
            }

            var projectDirectory    = project.ProjectFile.DirectoryName;
            var outputDirectoryPath = Path.Combine(projectDirectory, "bin");

            using var tempDirectory = TempDirectory.Create();

            HelmChartGenerator.ApplyHelmChartDefaults(application, project, container, chart);

            var chartRoot = Path.Combine(projectDirectory, "charts");
            var chartPath = Path.Combine(chartRoot, chart.ChartName);

            output.WriteDebugLine($"Looking for existing chart in '{chartPath}'.");
            if (Directory.Exists(chartPath))
            {
                output.WriteDebugLine($"Found existing chart in '{chartPath}'.");
            }
            else
            {
                chartRoot = tempDirectory.DirectoryPath;
                chartPath = Path.Combine(chartRoot, chart.ChartName);
                output.WriteDebugLine($"Generating chart in '{chartPath}'.");
                await HelmChartGenerator.GenerateAsync(output, application, project, container, chart, new DirectoryInfo(tempDirectory.DirectoryPath));
            }

            output.WriteDebugLine("Running 'helm package'.");
            output.WriteCommandLine("helm", $"package -d \"{outputDirectoryPath}\" --version {project.Version.Replace('+', '-')} --app-version {project.Version.Replace('+', '-')}");
            var capture  = output.Capture();
            var exitCode = await Process.ExecuteAsync(
                "helm",
                $"package . -d \"{outputDirectoryPath}\" --version {project.Version.Replace('+', '-')} --app-version {project.Version.Replace('+', '-')}",
                workingDir : chartPath,
                stdOut : capture.StdOut,
                stdErr : capture.StdErr);

            output.WriteDebugLine($"Running 'helm package' exit code: {exitCode}");
            if (exitCode != 0)
            {
                throw new CommandException("Running 'helm package' failed.");
            }

            output.WriteInfoLine($"Created Helm Chart: {Path.Combine(outputDirectoryPath, chart.ChartName + "-" + project.Version.Replace('+', '-') + ".tgz")}");
        }
Exemplo n.º 11
0
        public static ServiceOutput CreateDeployment(OutputContext output, ApplicationBuilder application, ProjectServiceBuilder project)
        {
            if (output is null)
            {
                throw new ArgumentNullException(nameof(output));
            }

            if (application is null)
            {
                throw new ArgumentNullException(nameof(application));
            }

            if (project is null)
            {
                throw new ArgumentNullException(nameof(project));
            }

            var bindings = project.Outputs.OfType <ComputedBindings>().FirstOrDefault();

            var root = new YamlMappingNode();

            root.Add("kind", "Deployment");
            root.Add("apiVersion", "apps/v1");

            var metadata = new YamlMappingNode();

            root.Add("metadata", metadata);
            metadata.Add("name", project.Name);

            var labels = new YamlMappingNode();

            metadata.Add("labels", labels);
            labels.Add("app.kubernetes.io/name", project.Name);
            labels.Add("app.kubernetes.io/part-of", application.Name);

            var spec = new YamlMappingNode();

            root.Add("spec", spec);

            spec.Add("replicas", project.Replicas.ToString());

            var selector = new YamlMappingNode();

            spec.Add("selector", selector);

            var matchLabels = new YamlMappingNode();

            selector.Add("matchLabels", matchLabels);
            matchLabels.Add("app.kubernetes.io/name", project.Name);

            var template = new YamlMappingNode();

            spec.Add("template", template);

            metadata = new YamlMappingNode();
            template.Add("metadata", metadata);

            labels = new YamlMappingNode();
            metadata.Add("labels", labels);
            labels.Add("app.kubernetes.io/name", project.Name);
            labels.Add("app.kubernetes.io/part-of", application.Name);

            spec = new YamlMappingNode();
            template.Add("spec", spec);

            var containers = new YamlSequenceNode();

            spec.Add("containers", containers);

            var images = project.Outputs.OfType <DockerImageOutput>();

            foreach (var image in images)
            {
                var container = new YamlMappingNode();
                containers.Add(container);
                container.Add("name", project.Name);        // NOTE: to really support multiple images we'd need to generate unique names.
                container.Add("image", $"{image.ImageName}:{image.ImageTag}");
                container.Add("imagePullPolicy", "Always"); // helps avoid problems with development + weak versioning

                if (project.EnvironmentVariables.Count > 0 ||

                    // We generate ASPNETCORE_URLS if there are bindings for http
                    project.Bindings.Any(b => b.Protocol == "http" || b.Protocol is null) ||

                    // We generate environment variables for other services if there dependencies
                    (bindings is object && bindings.Bindings.OfType <EnvironmentVariableInputBinding>().Any()))
                {
                    var env = new YamlSequenceNode();
                    container.Add("env", env);

                    foreach (var kvp in project.EnvironmentVariables)
                    {
                        env.Add(new YamlMappingNode()
                        {
                            { "name", kvp.Name },
                            { "value", new YamlScalarNode(kvp.Value)
                              {
                                  Style = ScalarStyle.SingleQuoted,
                              } },
                        });
                    }

                    if (bindings is object)
                    {
                        foreach (var binding in bindings.Bindings.OfType <EnvironmentVariableInputBinding>())
                        {
                            env.Add(new YamlMappingNode()
                            {
                                { "name", binding.Name },
                                { "value", new YamlScalarNode(binding.Value)
                                  {
                                      Style = ScalarStyle.SingleQuoted,
                                  } },
                            });
                        }
                    }
                }

                if (bindings is object && bindings.Bindings.OfType <SecretInputBinding>().Any())
                {
                    var volumeMounts = new YamlSequenceNode();
                    container.Add("volumeMounts", volumeMounts);

                    foreach (var binding in bindings.Bindings.OfType <SecretInputBinding>())
                    {
                        var volumeMount = new YamlMappingNode();
                        volumeMounts.Add(volumeMount);

                        volumeMount.Add("name", $"{binding.Service.Name}-{binding.Binding.Name ?? binding.Service.Name}");
                        volumeMount.Add("mountPath", $"/var/tye/bindings/{binding.Service.Name}-{binding.Binding.Name ?? binding.Service.Name}");
                        volumeMount.Add("readOnly", "true");
                    }
                }

                if (project.Bindings.Count > 0)
                {
                    var ports = new YamlSequenceNode();
                    container.Add("ports", ports);

                    foreach (var binding in project.Bindings)
                    {
                        if (binding.Protocol == "https")
                        {
                            // We skip https for now in deployment, because the E2E requires certificates
                            // and we haven't done those features yet.
                            continue;
                        }

                        if (binding.Port != null)
                        {
                            var containerPort = new YamlMappingNode();
                            ports.Add(containerPort);
                            containerPort.Add("containerPort", binding.Port.Value.ToString());
                        }
                    }
                }
            }

            if (bindings.Bindings.OfType <SecretInputBinding>().Any())
            {
                var volumes = new YamlSequenceNode();
                spec.Add("volumes", volumes);

                foreach (var binding in bindings.Bindings.OfType <SecretInputBinding>())
                {
                    var volume = new YamlMappingNode();
                    volumes.Add(volume);
                    volume.Add("name", $"{binding.Service.Name}-{binding.Binding.Name ?? binding.Service.Name}");

                    var secret = new YamlMappingNode();
                    volume.Add("secret", secret);
                    secret.Add("secretName", binding.Name !);

                    var items = new YamlSequenceNode();
                    secret.Add("items", items);

                    var item = new YamlMappingNode();
                    items.Add(item);
                    item.Add("key", "connectionstring");
                    item.Add("path", binding.Filename);
                }
            }

            return(new KubernetesDeploymentOutput(project.Name, new YamlDocument(root)));
        }
Exemplo n.º 12
0
 public abstract Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service);
Exemplo n.º 13
0
        public static ServiceOutput CreateService(OutputContext output, ApplicationBuilder application, ServiceBuilder service)
        {
            if (output is null)
            {
                throw new ArgumentNullException(nameof(output));
            }

            if (application is null)
            {
                throw new ArgumentNullException(nameof(application));
            }

            if (service is null)
            {
                throw new ArgumentNullException(nameof(service));
            }

            var root = new YamlMappingNode();

            root.Add("kind", "Service");
            root.Add("apiVersion", "v1");

            var metadata = new YamlMappingNode();

            root.Add("metadata", metadata);
            metadata.Add("name", service.Name);

            var labels = new YamlMappingNode();

            metadata.Add("labels", labels);
            labels.Add("app.kubernetes.io/name", service.Name);

            labels.Add("app.kubernetes.io/part-of", application.Name);

            var spec = new YamlMappingNode();

            root.Add("spec", spec);

            var selector = new YamlMappingNode();

            spec.Add("selector", selector);
            selector.Add("app.kubernetes.io/name", service.Name);

            spec.Add("type", "ClusterIP");

            var ports = new YamlSequenceNode();

            spec.Add("ports", ports);

            // We figure out the port based on bindings
            foreach (var binding in service.Bindings)
            {
                if (binding.Protocol == "https")
                {
                    // We skip https for now in deployment, because the E2E requires certificates
                    // and we haven't done those features yet.
                    continue;
                }

                if (binding.Port != null)
                {
                    var port = new YamlMappingNode();
                    ports.Add(port);

                    port.Add("name", binding.Name ?? binding.Protocol ?? "http");
                    port.Add("protocol", "TCP"); // we use assume TCP. YOLO
                    port.Add("port", binding.Port.Value.ToString());
                    port.Add("targetPort", binding.Port.Value.ToString());
                }
            }

            return(new KubernetesServiceOutput(service.Name, new YamlDocument(root)));
        }
Exemplo n.º 14
0
        public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder application, IngressBuilder ingress)
        {
            // This code assumes that in the future we might support other ingress types besides nginx.
            //
            // Right now we only know some hardcoded details about ingress-nginx that we use for both
            // validation and generation of manifests.
            //
            // For instance we don't support k8s 1.18.X IngressClass resources because that version
            // isn't broadly available yet.
            var ingressClass = "nginx";

            if (!IngressClasses.Add(ingressClass))
            {
                output.WriteDebugLine($"Already validated ingress class '{ingressClass}'.");
                return;
            }

            if (await KubectlDetector.GetKubernetesServerVersion(output) == null)
            {
                throw new CommandException($"Cannot validate ingress because kubectl is not installed.");
            }

            if (!await KubectlDetector.IsKubectlConnectedToClusterAsync(output))
            {
                throw new CommandException($"Cannot validate ingress because kubectl is not connected to a cluster.");
            }

            output.WriteDebugLine($"Validating ingress class '{ingressClass}'.");
            var config = KubernetesClientConfiguration.BuildDefaultConfig();

            // If namespace is null, set it to default
            config.Namespace ??= "default";

            var kubernetes = new Kubernetes(config);

            // Looking for a deployment using a standard label.
            // Note: using a deployment instead of a service - minikube doesn't create a service for the controller.

            try
            {
                var result = await kubernetes.ListDeploymentForAllNamespacesWithHttpMessagesAsync(
                    labelSelector : "app.kubernetes.io/name in (ingress-nginx, nginx-ingress-controller)");

                if (result.Body.Items.Count > 0)
                {
                    foreach (var service in result.Body.Items)
                    {
                        output.WriteInfoLine($"Found existing ingress controller '{service.Metadata.Name}' in namespace '{service.Metadata.NamespaceProperty}'.");
                    }

                    return;
                }
            }
            catch (HttpOperationException ex) when(ex.Response.StatusCode == HttpStatusCode.NotFound)
            {
                // The kubernetes client uses exceptions for 404s.
            }
            catch (Exception ex)
            {
                output.WriteDebugLine("Failed to query secret.");
                output.WriteDebugLine(ex.ToString());
                throw new CommandException("Unable connect to kubernetes.", ex);
            }

            if (Force)
            {
                output.WriteDebugLine("Skipping because force was specified.");
                return;
            }

            if (!Interactive)
            {
                throw new CommandException(
                          $"An ingress was specified for the application, but the 'ingress-nginx' controller could not be found. " +
                          $"Rerun the command with --interactive to deploy a controller for development, or with --force to skip validation. Alternatively " +
                          $"see our documentation on ingress: https://aka.ms/tye/ingress");
            }

            output.WriteAlwaysLine(
                "Tye can deploy the ingress-nginx controller for you. This will be a basic deployment suitable for " +
                "experimentation and development. Your production needs, or requirements may differ depending on your Kubernetes distribution. " +
                "See: https://aka.ms/tye/ingress for documentation.");
            if (!output.Confirm($"Deploy ingress-nginx"))
            {
                // user skipped deployment of ingress, continue with deployment.
                return;
            }

            // We want to be able to detect minikube because the process for enabling nginx-ingress is different there,
            // it's shipped as an addon.
            //
            // see: https://github.com/telepresenceio/telepresence/blob/4364fd83d5926bef46babd704e7bd6c82a75dbd6/telepresence/startup.py#L220
            if (config.CurrentContext == "minikube")
            {
                output.WriteDebugLine($"Running 'minikube addons enable ingress'");
                output.WriteCommandLine("minikube", "addon enable ingress");
                var capture  = output.Capture();
                var exitCode = await ProcessUtil.ExecuteAsync(
                    $"minikube",
                    $"addons enable ingress",
                    System.Environment.CurrentDirectory,
                    stdOut : capture.StdOut,
                    stdErr : capture.StdErr);

                output.WriteDebugLine($"Done running 'minikube addons enable ingress' exit code: {exitCode}");
                if (exitCode != 0)
                {
                    throw new CommandException("'minikube addons enable ingress' failed.");
                }

                output.WriteInfoLine($"Deployed ingress-nginx.");
            }
            else
            {
                // If we get here then we should deploy the ingress controller.

                // The first time we apply the ingress controller, the validating webhook will not have started.
                // This causes an error to be returned from the process. As this always happens, we are going to
                // not check the error returned and assume the kubectl command worked. This is double checked in
                // the future as well when we try to create the ingress resource.

                output.WriteDebugLine($"Running 'kubectl apply'");
                output.WriteCommandLine("kubectl", $"apply -f \"https://aka.ms/tye/ingress/deploy\"");
                var capture  = output.Capture();
                var exitCode = await ProcessUtil.ExecuteAsync(
                    $"kubectl",
                    $"apply -f \"https://aka.ms/tye/ingress/deploy\"",
                    System.Environment.CurrentDirectory);

                output.WriteDebugLine($"Done running 'kubectl apply' exit code: {exitCode}");

                output.WriteInfoLine($"Waiting for ingress-nginx controller to start.");

                // We need to then wait for the webhooks that are created by ingress-nginx to start. Deploying an ingress immediately
                // after creating the controller will fail if the webhook isn't ready.
                //
                // Internal error occurred: failed calling webhook "validate.nginx.ingress.kubernetes.io":
                // Post https://ingress-nginx-controller-admission.ingress-nginx.svc:443/networking.k8s.io/v1/ingresses?timeout=30s:
                // dial tcp 10.0.31.130:443: connect: connection refused
                //
                // Unfortunately this is the likely case for us.

                try
                {
                    output.WriteDebugLine("Watching for ingress-nginx controller readiness...");
                    var response = await kubernetes.ListNamespacedPodWithHttpMessagesAsync(
                        namespaceParameter : "ingress-nginx",
                        labelSelector : "app.kubernetes.io/component=controller,app.kubernetes.io/name=ingress-nginx",
                        watch : true);

                    var tcs = new TaskCompletionSource <object?>();
                    using var watcher = response.Watch <V1Pod, V1PodList>(
                              onEvent: (@event, pod) =>
                    {
                        // Wait for the readiness-check to pass.
                        if (pod.Status.Conditions.All(c => string.Equals(c.Status, bool.TrueString, StringComparison.OrdinalIgnoreCase)))
                        {
                            tcs.TrySetResult(null);     // Success!
                            output.WriteDebugLine($"Pod '{pod.Metadata.Name}' is ready.");
                        }
                    },
                              onError: ex =>
                    {
                        tcs.TrySetException(ex);
                        output.WriteDebugLine("Watch operation failed.");
                    },
                              onClosed: () =>
                    {
                        // YOLO?
                        tcs.TrySetResult(null);
                        output.WriteDebugLine("Watch operation completed.");
                    });

                    await tcs.Task;
                }
                catch (Exception ex)
                {
                    output.WriteDebugLine("Failed to ingress-nginx pods.");
                    output.WriteDebugLine(ex.ToString());
                    throw new CommandException("Failed to query ingress-nginx pods.", ex);
                }

                output.WriteInfoLine($"Deployed ingress-nginx.");
            }
        }
Exemplo n.º 15
0
        private static async Task GenerateApplicationManifestAsync(OutputContext output, ApplicationBuilder application, string environment)
        {
            using var step = output.BeginStep("Generating Application Manifests...");

            var outputFilePath = Path.GetFullPath(Path.Combine(application.Source.DirectoryName, $"{application.Name}-generate-{environment}.yaml"));

            output.WriteInfoLine($"Writing output to '{outputFilePath}'.");
            {
                File.Delete(outputFilePath);

                await using var stream = File.OpenWrite(outputFilePath);
                await using var writer = new StreamWriter(stream, Encoding.UTF8, leaveOpen: true);

                await ApplicationYamlWriter.WriteAsync(output, writer, application);
            }

            step.MarkComplete();
        }
Exemplo n.º 16
0
        public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service)
        {
            var bindings = service.Outputs.OfType <ComputedBindings>().FirstOrDefault();

            if (bindings is null)
            {
                return;
            }

            foreach (var binding in bindings.Bindings)
            {
                if (binding is SecretInputBinding secretInputBinding)
                {
                    if (!Secrets.Add(secretInputBinding.Name))
                    {
                        output.WriteDebugLine($"Already validated secret '{secretInputBinding.Name}'.");
                        continue;
                    }

                    output.WriteDebugLine($"Validating secret '{secretInputBinding.Name}'.");

                    var config = KubernetesClientConfiguration.BuildDefaultConfig();

                    // Workaround for https://github.com/kubernetes-client/csharp/issues/372
                    var store = await KubernetesClientConfiguration.LoadKubeConfigAsync();

                    var context = store.Contexts.Where(c => c.Name == config.CurrentContext).FirstOrDefault();
                    config.Namespace ??= context?.ContextDetails?.Namespace;

                    var kubernetes = new Kubernetes(config);

                    try
                    {
                        var result = await kubernetes.ReadNamespacedSecretWithHttpMessagesAsync(secretInputBinding.Name, config.Namespace ?? "default");

                        output.WriteInfoLine($"Found existing secret '{secretInputBinding.Name}'.");
                        continue;
                    }
                    catch (HttpOperationException ex) when(ex.Response.StatusCode == HttpStatusCode.NotFound)
                    {
                        // The kubernetes client uses exceptions for 404s.
                    }
                    catch (Exception ex)
                    {
                        output.WriteDebugLine("Failed to query secret.");
                        output.WriteDebugLine(ex.ToString());
                        throw new CommandException("Unable connect to kubernetes.", ex);
                    }

                    if (Force)
                    {
                        output.WriteDebugLine("Skipping because force was specified.");
                        continue;
                    }

                    if (!Interactive)
                    {
                        throw new CommandException(
                                  $"The secret '{secretInputBinding.Name}' used for service '{secretInputBinding.Service.Name}' is missing from the deployment environment. " +
                                  $"Rerun the command with --interactive to specify the value interactively, or with --force to skip validation. Alternatively " +
                                  $"use the following command to manually create the secret." + System.Environment.NewLine +
                                  $"kubectl create secret generic {secretInputBinding.Name} --from-literal=connectionstring=<value>");
                    }

                    // If we get here then we should create the secret.
                    var text = output.Prompt($"Enter the connection string to use for service '{secretInputBinding.Service.Name}'", allowEmpty: true);
                    if (string.IsNullOrWhiteSpace(text))
                    {
                        output.WriteAlways($"Skipping creation of secret for '{secretInputBinding.Service.Name}'. This may prevent creation of pods until secrets are created.");
                        output.WriteAlways($"Manually create a secret with:");
                        output.WriteAlways($"kubectl create secret generic {secretInputBinding.Name} --from-literal=connectionstring=<value>");
                        continue;
                    }

                    var secret = new V1Secret(type: "Opaque", stringData: new Dictionary <string, string>()
                    {
                        { "connectionstring", text },
                    });
                    secret.Metadata      = new V1ObjectMeta();
                    secret.Metadata.Name = secretInputBinding.Name;

                    output.WriteDebugLine($"Creating secret '{secret.Metadata.Name}'.");

                    try
                    {
                        await kubernetes.CreateNamespacedSecretWithHttpMessagesAsync(secret, config.Namespace ?? "default");

                        output.WriteInfoLine($"Created secret '{secret.Metadata.Name}'.");
                    }
                    catch (Exception ex)
                    {
                        output.WriteDebugLine("Failed to create secret.");
                        output.WriteDebugLine(ex.ToString());
                        throw new CommandException("Failed to create secret.", ex);
                    }
                }
            }

            var yaml = service.Outputs.OfType <IYamlManifestOutput>().ToArray();

            if (yaml.Length == 0)
            {
                output.WriteDebugLine($"No yaml manifests found for service '{service.Name}'. Skipping.");
                return;
            }

            using var tempFile = TempFile.Create();
            output.WriteDebugLine($"Writing output to '{tempFile.FilePath}'.");

            {
                await using var stream = File.OpenWrite(tempFile.FilePath);
                await using var writer = new StreamWriter(stream, Encoding.UTF8, bufferSize: -1, leaveOpen: true);
                var yamlStream = new YamlStream(yaml.Select(y => y.Yaml));
                yamlStream.Save(writer, assignAnchors: false);
            }

            // kubectl apply logic is implemented in the client in older versions of k8s. The capability
            // to get the same behavior in the server isn't present in every version that's relevant.
            //
            // https://kubernetes.io/docs/reference/using-api/api-concepts/#server-side-apply
            //
            output.WriteDebugLine("Running 'kubectl apply'.");
            output.WriteCommandLine("kubectl", $"apply -f \"{tempFile.FilePath}\"");
            var capture  = output.Capture();
            var exitCode = await Process.ExecuteAsync(
                $"kubectl",
                $"apply -f \"{tempFile.FilePath}\"",
                System.Environment.CurrentDirectory,
                stdOut : capture.StdOut,
                stdErr : capture.StdErr);

            output.WriteDebugLine($"Done running 'kubectl apply' exit code: {exitCode}");
            if (exitCode != 0)
            {
                throw new CommandException("'kubectl apply' failed.");
            }

            output.WriteInfoLine($"Deployed service '{service.Name}'.");
        }
Exemplo n.º 17
0
        public static KubernetesServiceOutput CreateService(
            OutputContext output,
            ApplicationBuilder application,
            ProjectServiceBuilder project,
            DeploymentManifestInfo deployment,
            ServiceManifestInfo service)
        {
            var root = new YamlMappingNode();

            root.Add("kind", "Service");
            root.Add("apiVersion", "v1");

            var metadata = new YamlMappingNode();

            root.Add("metadata", metadata);
            metadata.Add("name", project.Name);

            if (!string.IsNullOrEmpty(application.Namespace))
            {
                metadata.Add("namespace", application.Namespace);
            }

            if (service.Annotations.Count > 0)
            {
                var annotations = new YamlMappingNode();
                metadata.Add("annotations", annotations);

                foreach (var annotation in service.Annotations)
                {
                    annotations.Add(annotation.Key, new YamlScalarNode(annotation.Value)
                    {
                        Style = ScalarStyle.SingleQuoted,
                    });
                }
            }

            var labels = new YamlMappingNode();

            metadata.Add("labels", labels);
            foreach (var label in service.Labels)
            {
                labels.Add(label.Key, new YamlScalarNode(label.Value)
                {
                    Style = ScalarStyle.SingleQuoted,
                });
            }

            var spec = new YamlMappingNode();

            root.Add("spec", spec);

            var selector = new YamlMappingNode();

            spec.Add("selector", selector);

            // We need the name so we can use it with selector.
            if (!deployment.Labels.TryGetValue("app.kubernetes.io/name", out var selectorLabelValue))
            {
                throw new InvalidOperationException("The label 'app.kubernetes.io/name` is required.");
            }
            selector.Add("app.kubernetes.io/name", selectorLabelValue);

            spec.Add("type", "ClusterIP");

            var ports = new YamlSequenceNode();

            spec.Add("ports", ports);

            // We figure out the port based on bindings
            foreach (var binding in project.Bindings)
            {
                if (binding.Protocol == "https")
                {
                    // We skip https for now in deployment, because the E2E requires certificates
                    // and we haven't done those features yet.
                    continue;
                }

                if (binding.Port != null)
                {
                    var port = new YamlMappingNode();
                    ports.Add(port);

                    port.Add("name", binding.Name ?? binding.Protocol ?? "http");
                    port.Add("protocol", "TCP"); // we use assume TCP. YOLO
                    port.Add("port", binding.Port.Value.ToString());
                    port.Add("targetPort", binding.Port.Value.ToString());
                }
            }

            return(new KubernetesServiceOutput(project.Name, new YamlDocument(root)));
        }
Exemplo n.º 18
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);

            config.Validate();
            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;
                    foreach (var buildProperty in configService.BuildProperties)
                    {
                        project.BuildProperties.Add(buildProperty.Name, buildProperty.Value);
                    }
                    project.Replicas = configService.Replicas ?? 1;

                    await ProjectReader.ReadProjectDetailsAsync(output, project);

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

                    // 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"
                    });
                }
Exemplo n.º 19
0
        public static async Task GenerateAsync(OutputContext output, ApplicationBuilder application, ProjectServiceBuilder project, ContainerInfo container, HelmChartStep chart, DirectoryInfo outputDirectory)
        {
            if (output is null)
            {
                throw new ArgumentNullException(nameof(output));
            }

            if (application is null)
            {
                throw new ArgumentNullException(nameof(application));
            }

            if (project is null)
            {
                throw new ArgumentNullException(nameof(project));
            }

            if (container is null)
            {
                throw new ArgumentNullException(nameof(container));
            }

            if (chart is null)
            {
                throw new ArgumentNullException(nameof(chart));
            }

            if (outputDirectory is null)
            {
                throw new ArgumentNullException(nameof(outputDirectory));
            }

            ApplyHelmChartDefaults(application, project, container, chart);

            // The directory with the charts needs to be the same as the chart name
            var chartDirectoryPath = Path.Combine(outputDirectory.FullName, chart.ChartName);

            Directory.CreateDirectory(chartDirectoryPath);

            var templateDirectoryPath = Path.Combine(
                Path.GetDirectoryName(typeof(HelmChartGenerator).Assembly.Location) !,
                "Templates",
                "Helm");

            DirectoryCopy.Copy(templateDirectoryPath, chartDirectoryPath);

            // Write Chart.yaml
            //
            // apiVersion: v1
            // name: <appname>
            // version: <version>
            // appVersion: <version>
            await File.WriteAllLinesAsync(Path.Combine(chartDirectoryPath, "Chart.yaml"), new[]
            {
                $"apiVersion: v1",
                $"name: {chart.ChartName}",
                $"# helm requires the version and appVersion to specified in Chart.yaml",
                $"# tye will override these values when packaging the chart",
                $"version: {project.Version.Replace('+', '-')}",
                $"appVersion: {project.Version.Replace('+', '-')}"
            });

            // Write values.yaml
            //
            // image:
            //   repository: rynowak.azurecr.io/rochambot/gamemaster
            await File.WriteAllLinesAsync(Path.Combine(chartDirectoryPath, "values.yaml"), new[]
            {
                $"image:",
                $"  repository: {container.ImageName}",
            });
        }
Exemplo n.º 20
0
        private static async Task DeployApplicationManifestAsync(OutputContext output, ApplicationBuilder application, string applicationName, string environment)
        {
            using var step = output.BeginStep("Deploying Application Manifests...");

            using var tempFile = TempFile.Create();
            output.WriteInfoLine($"Writing output to '{tempFile.FilePath}'.");

            {
                using var stream = File.OpenWrite(tempFile.FilePath);
                using var writer = new StreamWriter(stream, Encoding.UTF8, leaveOpen: true);

                await ApplicationYamlWriter.WriteAsync(output, writer, application);
            }

            output.WriteDebugLine("Running 'kubectl apply'.");
            output.WriteCommandLine("kubectl", $"apply -f \"{tempFile.FilePath}\"");
            var capture  = output.Capture();
            var exitCode = await Process.ExecuteAsync(
                $"kubectl",
                $"apply -f \"{tempFile.FilePath}\"",
                System.Environment.CurrentDirectory,
                stdOut : capture.StdOut,
                stdErr : capture.StdErr);

            output.WriteDebugLine($"Done running 'kubectl apply' exit code: {exitCode}");
            if (exitCode != 0)
            {
                throw new CommandException("'kubectl apply' failed.");
            }

            output.WriteInfoLine($"Deployed application '{applicationName}'.");

            step.MarkComplete();
        }
Exemplo n.º 21
0
        public static KubernetesDeploymentOutput CreateDeployment(
            OutputContext output,
            ApplicationBuilder application,
            ProjectServiceBuilder project,
            DeploymentManifestInfo deployment)
        {
            var bindings = project.Outputs.OfType <ComputedBindings>().FirstOrDefault();

            var root = new YamlMappingNode();

            root.Add("kind", "Deployment");
            root.Add("apiVersion", "apps/v1");

            var metadata = new YamlMappingNode();

            root.Add("metadata", metadata);
            metadata.Add("name", project.Name);
            if (!string.IsNullOrEmpty(application.Namespace))
            {
                metadata.Add("namespace", application.Namespace);
            }

            if (deployment.Annotations.Count > 0)
            {
                var annotations = new YamlMappingNode();
                metadata.Add("annotations", annotations);

                foreach (var annotation in deployment.Annotations)
                {
                    annotations.Add(annotation.Key, new YamlScalarNode(annotation.Value)
                    {
                        Style = ScalarStyle.SingleQuoted,
                    });
                }
            }

            var labels = new YamlMappingNode();

            metadata.Add("labels", labels);
            foreach (var label in deployment.Labels)
            {
                labels.Add(label.Key, new YamlScalarNode(label.Value)
                {
                    Style = ScalarStyle.SingleQuoted,
                });
            }

            var spec = new YamlMappingNode();

            root.Add("spec", spec);

            spec.Add("replicas", project.Replicas.ToString());

            var selector = new YamlMappingNode();

            spec.Add("selector", selector);

            var matchLabels = new YamlMappingNode();

            selector.Add("matchLabels", matchLabels);

            // We need the name so we can use it with matchLabels.
            if (!deployment.Labels.TryGetValue("app.kubernetes.io/name", out var matchLabelsLabelValue))
            {
                throw new InvalidOperationException("The label 'app.kubernetes.io/name` is required.");
            }
            matchLabels.Add("app.kubernetes.io/name", matchLabelsLabelValue);

            var template = new YamlMappingNode();

            spec.Add("template", template);

            metadata = new YamlMappingNode();
            template.Add("metadata", metadata);

            if (deployment.Annotations.Count > 0)
            {
                var annotations = new YamlMappingNode();
                metadata.Add("annotations", annotations);

                foreach (var annotation in deployment.Annotations)
                {
                    annotations.Add(annotation.Key, new YamlScalarNode(annotation.Value)
                    {
                        Style = ScalarStyle.SingleQuoted,
                    });
                }
            }

            labels = new YamlMappingNode();
            metadata.Add("labels", labels);
            foreach (var label in deployment.Labels)
            {
                labels.Add(label.Key, new YamlScalarNode(label.Value)
                {
                    Style = ScalarStyle.SingleQuoted,
                });
            }

            spec = new YamlMappingNode();
            template.Add("spec", spec);

            var containers = new YamlSequenceNode();

            spec.Add("containers", containers);

            var images = project.Outputs.OfType <DockerImageOutput>();

            foreach (var image in images)
            {
                var container = new YamlMappingNode();
                containers.Add(container);
                container.Add("name", project.Name);        // NOTE: to really support multiple images we'd need to generate unique names.
                container.Add("image", $"{image.ImageName}:{image.ImageTag}");
                container.Add("imagePullPolicy", "Always"); // helps avoid problems with development + weak versioning

                if (project.EnvironmentVariables.Count > 0 ||

                    // We generate ASPNETCORE_URLS if there are bindings for http
                    project.Bindings.Any(b => b.Protocol == "http" || b.Protocol is null) ||

                    // We generate environment variables for other services if there dependencies
                    (bindings is object && bindings.Bindings.Any()))
                {
                    var env = new YamlSequenceNode();
                    container.Add("env", env);

                    foreach (var kvp in project.EnvironmentVariables)
                    {
                        env.Add(new YamlMappingNode()
                        {
                            { "name", kvp.Name },
                            { "value", new YamlScalarNode(kvp.Value)
                              {
                                  Style = ScalarStyle.SingleQuoted,
                              } },
                        });
                    }

                    if (bindings is object)
                    {
                        foreach (var binding in bindings.Bindings.OfType <EnvironmentVariableInputBinding>())
                        {
                            env.Add(new YamlMappingNode()
                            {
                                { "name", binding.Name },
                                { "value", new YamlScalarNode(binding.Value)
                                  {
                                      Style = ScalarStyle.SingleQuoted,
                                  } },
                            });
                        }

                        foreach (var binding in bindings.Bindings.OfType <SecretInputBinding>())
                        {
                            //- name: SECRET_USERNAME
                            //  valueFrom:
                            //    secretKeyRef:
                            //      name: mysecret
                            //      key: username

                            if (binding is SecretConnectionStringInputBinding connectionStringBinding)
                            {
                                env.Add(new YamlMappingNode()
                                {
                                    { "name", connectionStringBinding.KeyName },
                                    { "valueFrom", new YamlMappingNode()
                                      {
                                          { "secretKeyRef", new YamlMappingNode()
                                            {
                                                { "name", new YamlScalarNode(binding.Name)
                                                    {
                                                        Style = ScalarStyle.SingleQuoted
                                                    } },
                                                { "key", new YamlScalarNode("connectionstring")
                                                    {
                                                        Style = ScalarStyle.SingleQuoted
                                                    } },
                                            } },
                                      } },
                                });
                            }
                            else if (binding is SecretUrlInputBinding urlBinding)
                            {
                                env.Add(new YamlMappingNode()
                                {
                                    { "name", $"{urlBinding.KeyNameBase}__PROTOCOL" },
                                    { "valueFrom", new YamlMappingNode()
                                      {
                                          { "secretKeyRef", new YamlMappingNode()
                                            {
                                                { "name", new YamlScalarNode(binding.Name)
                                                    {
                                                        Style = ScalarStyle.SingleQuoted
                                                    } },
                                                { "key", new YamlScalarNode("protocol")
                                                    {
                                                        Style = ScalarStyle.SingleQuoted
                                                    } },
                                            } },
                                      } },
                                });

                                env.Add(new YamlMappingNode()
                                {
                                    { "name", $"{urlBinding.KeyNameBase}__HOST" },
                                    { "valueFrom", new YamlMappingNode()
                                      {
                                          { "secretKeyRef", new YamlMappingNode()
                                            {
                                                { "name", new YamlScalarNode(binding.Name)
                                                    {
                                                        Style = ScalarStyle.SingleQuoted
                                                    } },
                                                { "key", new YamlScalarNode("host")
                                                    {
                                                        Style = ScalarStyle.SingleQuoted
                                                    } },
                                            } },
                                      } },
                                });

                                env.Add(new YamlMappingNode()
                                {
                                    { "name", $"{urlBinding.KeyNameBase}__PORT" },
                                    { "valueFrom", new YamlMappingNode()
                                      {
                                          { "secretKeyRef", new YamlMappingNode()
                                            {
                                                { "name", new YamlScalarNode(binding.Name)
                                                    {
                                                        Style = ScalarStyle.SingleQuoted
                                                    } },
                                                { "key", new YamlScalarNode("port")
                                                    {
                                                        Style = ScalarStyle.SingleQuoted
                                                    } },
                                            } },
                                      } },
                                });
                            }
                        }
                    }
                }

                if (project.Bindings.Count > 0)
                {
                    var ports = new YamlSequenceNode();
                    container.Add("ports", ports);

                    foreach (var binding in project.Bindings)
                    {
                        if (binding.Protocol == "https")
                        {
                            // We skip https for now in deployment, because the E2E requires certificates
                            // and we haven't done those features yet.
                            continue;
                        }

                        if (binding.Port != null)
                        {
                            var containerPort = new YamlMappingNode();
                            ports.Add(containerPort);
                            containerPort.Add("containerPort", binding.Port.Value.ToString());
                        }
                    }
                }
            }

            return(new KubernetesDeploymentOutput(project.Name, new YamlDocument(root)));
        }
Exemplo n.º 22
0
        public static void ApplyContainerDefaults(ApplicationBuilder application, DotnetProjectServiceBuilder project, ContainerInfo container)
        {
            if (application is null)
            {
                throw new ArgumentNullException(nameof(application));
            }

            if (project is null)
            {
                throw new ArgumentNullException(nameof(project));
            }

            if (container is null)
            {
                throw new ArgumentNullException(nameof(container));
            }

            if (string.IsNullOrEmpty(container.BaseImageTag) && (project.TargetFrameworkName == "netcoreapp" || project.TargetFrameworkName == "net"))
            {
                container.BaseImageTag = project.TargetFrameworkVersion;
            }

            if (string.IsNullOrEmpty(container.BaseImageTag))
            {
                throw new CommandException($"Unsupported TFM {project.TargetFramework}.");
            }

            if (string.IsNullOrEmpty(container.BaseImageName))
            {
                if (TagIs50OrNewer(container.BaseImageTag))
                {
                    container.BaseImageName = project.IsAspNet ? "mcr.microsoft.com/dotnet/aspnet" : "mcr.microsoft.com/dotnet/runtime";
                }
                else
                {
                    container.BaseImageName = project.IsAspNet ? "mcr.microsoft.com/dotnet/core/aspnet" : "mcr.microsoft.com/dotnet/core/runtime";
                }
            }

            container.BuildImageTag ??= project.TargetFrameworkVersion;

            if (string.IsNullOrEmpty(container.BuildImageName))
            {
                container.BuildImageName = TagIs50OrNewer(container.BuildImageTag) ? "mcr.microsoft.com/dotnet/sdk" : "mcr.microsoft.com/dotnet/core/sdk";
            }

            if (container.ImageName == null && application.Registry?.Hostname == null)
            {
                container.ImageName ??= project.Name.ToLowerInvariant();
            }
            else if (container.ImageName == null && application.Registry?.Hostname != null)
            {
                container.ImageName ??= $"{application.Registry?.Hostname}/{project.Name.ToLowerInvariant()}";
            }

            container.ImageTag ??= project.Version?.Replace("+", "-") ?? "latest";

            // Disable color in the logs
            project.EnvironmentVariables.Add(new EnvironmentVariableBuilder("DOTNET_LOGGING__CONSOLE__DISABLECOLORS")
            {
                Value = "true"
            });
        }
Exemplo n.º 23
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 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 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)
                    {
                        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.Frameworks.Any(f => f.Name == "Microsoft.AspNetCore.App"))
                {
                    // 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"
                    });
                }
Exemplo n.º 24
0
        private static async Task ExecuteDeployAsync(OutputContext output, ApplicationBuilder application, string environment, bool interactive, bool force)
        {
            var watch = System.Diagnostics.Stopwatch.StartNew();

            if (!await KubectlDetector.IsKubectlInstalledAsync(output))
            {
                throw new CommandException($"Cannot apply manifests because kubectl is not installed.");
            }

            if (!await KubectlDetector.IsKubectlConnectedToClusterAsync(output))
            {
                throw new CommandException($"Cannot apply manifests because kubectl is not connected to a cluster.");
            }

            await application.ProcessExtensionsAsync(options : null, output, ExtensionContext.OperationKind.Deploy);

            ApplyRegistry(output, application, interactive, requireRegistry: true);

            var executor = new ApplicationExecutor(output)
            {
                ServiceSteps =
                {
                    new ApplyContainerDefaultsStep(),
                    new CombineStep()
                    {
                        Environment = environment,
                    },
                    new PublishProjectStep(),
                    new BuildDockerImageStep()
                    {
                        Environment = environment,
                    },
                    new PushDockerImageStep()
                    {
                        Environment = environment,
                    },
                    new ValidateSecretStep()
                    {
                        Environment = environment,   Interactive= interactive, Force = force,
                    },
                    new GenerateServiceKubernetesManifestStep()
                    {
                        Environment = environment,
                    },
                },

                IngressSteps =
                {
                    new ValidateIngressStep()
                    {
                        Environment = environment, Interactive = interactive, Force = force,
                    },
                    new GenerateIngressKubernetesManifestStep(),
                },

                ApplicationSteps =
                {
                    new DeployApplicationKubernetesManifestStep(),
                }
            };

            await executor.ExecuteAsync(application);

            watch.Stop();

            TimeSpan elapsedTime = watch.Elapsed;

            output.WriteAlwaysLine($"Time Elapsed: {elapsedTime.Hours:00}:{elapsedTime.Minutes:00}:{elapsedTime.Seconds:00}:{elapsedTime.Milliseconds / 10:00}");
        }
Exemplo n.º 25
0
        public static async Task <ApplicationBuilder> CreateAsync(OutputContext output, FileInfo source)
        {
            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 ?? source.Directory.Name.ToLowerInvariant());

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

                foreach (var configService in config.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 ProjectServiceBuilder(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;

                        // 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) || !string.IsNullOrEmpty(configService.DockerFile))
                    {
                        var container = new ContainerServiceBuilder(configService.Name !, configService.Image !)
                        {
                            Args       = configService.Args,
                            Replicas   = configService.Replicas ?? 1,
                            DockerFile = configService.DockerFile != null?Path.Combine(source.DirectoryName, configService.DockerFile) : null,
                                             DockerFileContext = configService.DockerFileContext != null?Path.Combine(source.DirectoryName, configService.DockerFileContext) : null
                        };
                        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(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;
                    }
                    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 = 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 (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"
                        });
                    }
Exemplo n.º 26
0
        public static Task WriteOamApplicationAsync(TextWriter writer, OutputContext output, ApplicationBuilder application, string environment)
        {
            if (writer is null)
            {
                throw new ArgumentNullException(nameof(writer));
            }

            if (output is null)
            {
                throw new ArgumentNullException(nameof(output));
            }

            if (application is null)
            {
                throw new ArgumentNullException(nameof(application));
            }

            if (environment is null)
            {
                throw new ArgumentNullException(nameof(environment));
            }

            var componentManifests = new List <OamComponentOutput>();
            var documents          = new List <YamlDocument>();

            foreach (var service in application.Services)
            {
                componentManifests.AddRange(service.Outputs.OfType <OamComponentOutput>());
            }

            var root = new YamlMappingNode();

            root.Add("kind", "ApplicationConfiguration");
            root.Add("apiVersion", "core.oam.dev/v1alpha1");

            var metadata = new YamlMappingNode();

            root.Add("metadata", metadata);
            metadata.Add("name", application.Name);

            var spec = new YamlMappingNode();

            root.Add("spec", spec);

            var components = new YamlSequenceNode();

            spec.Add("components", components);

            foreach (var manifest in componentManifests)
            {
                documents.Add(manifest.Yaml);

                var component = new YamlMappingNode();
                components.Add(component);

                component.Add("componentName", manifest.Name);
                component.Add("instanceName", $"{environment.ToLowerInvariant()}-{manifest.Name}");
            }

            documents.Add(new YamlDocument(root));

            var stream = new YamlStream(documents.ToArray());

            stream.Save(writer, assignAnchors: false);

            return(Task.CompletedTask);
        }
Exemplo n.º 27
0
        public static Application ToHostingApplication(this ApplicationBuilder application)
        {
            var services = new Dictionary <string, Service>();

            foreach (var service in application.Services)
            {
                RunInfo?runInfo;
                Probe?  liveness;
                Probe?  readiness;
                int     replicas;
                var     env = new List <EnvironmentVariable>();
                if (service is ExternalServiceBuilder)
                {
                    runInfo   = null;
                    liveness  = null;
                    readiness = null;
                    replicas  = 1;
                }
                else if (service is DockerFileServiceBuilder dockerFile)
                {
                    var dockerRunInfo = new DockerRunInfo(dockerFile.Image, dockerFile.Args)
                    {
                        IsAspNet  = dockerFile.IsAspNet,
                        BuildArgs = dockerFile.BuildArgs
                    };

                    if (!string.IsNullOrEmpty(dockerFile.DockerFile))
                    {
                        dockerRunInfo.DockerFile = new FileInfo(dockerFile.DockerFile);
                        if (!string.IsNullOrEmpty(dockerFile.DockerFileContext))
                        {
                            dockerRunInfo.DockerFileContext = new FileInfo(dockerFile.DockerFileContext);
                        }
                        else
                        {
                            dockerRunInfo.DockerFileContext = new FileInfo(dockerRunInfo.DockerFile.DirectoryName);
                        }
                    }

                    foreach (var mapping in dockerFile.Volumes)
                    {
                        dockerRunInfo.VolumeMappings.Add(new DockerVolume(mapping.Source, mapping.Name, mapping.Target));
                    }

                    runInfo  = dockerRunInfo;
                    replicas = dockerFile.Replicas;
                    liveness = dockerFile.Liveness != null?GetProbeFromBuilder(dockerFile.Liveness) : null;

                    readiness = dockerFile.Readiness != null?GetProbeFromBuilder(dockerFile.Readiness) : null;

                    foreach (var entry in dockerFile.EnvironmentVariables)
                    {
                        env.Add(entry.ToHostingEnvironmentVariable());
                    }
                }

                else if (service is ContainerServiceBuilder container)
                {
                    var dockerRunInfo = new DockerRunInfo(container.Image, container.Args)
                    {
                        IsAspNet = container.IsAspNet
                    };

                    foreach (var mapping in container.Volumes)
                    {
                        dockerRunInfo.VolumeMappings.Add(new DockerVolume(mapping.Source, mapping.Name, mapping.Target));
                    }

                    runInfo  = dockerRunInfo;
                    replicas = container.Replicas;
                    liveness = container.Liveness != null?GetProbeFromBuilder(container.Liveness) : null;

                    readiness = container.Readiness != null?GetProbeFromBuilder(container.Readiness) : null;

                    foreach (var entry in container.EnvironmentVariables)
                    {
                        env.Add(entry.ToHostingEnvironmentVariable());
                    }
                }
                else if (service is ExecutableServiceBuilder executable)
                {
                    runInfo  = new ExecutableRunInfo(executable.Executable, executable.WorkingDirectory, executable.Args);
                    replicas = executable.Replicas;
                    liveness = executable.Liveness != null?GetProbeFromBuilder(executable.Liveness) : null;

                    readiness = executable.Readiness != null?GetProbeFromBuilder(executable.Readiness) : null;

                    foreach (var entry in executable.EnvironmentVariables)
                    {
                        env.Add(entry.ToHostingEnvironmentVariable());
                    }
                }
                else if (service is DotnetProjectServiceBuilder project)
                {
                    if (project.TargetFrameworks.Length > 1)
                    {
                        throw new InvalidOperationException($"Unable to run {project.Name}. Multi-targeted projects are not supported.");
                    }

                    if (project.RunCommand == null)
                    {
                        throw new InvalidOperationException($"Unable to run {project.Name}. The project does not have a run command");
                    }

                    var projectInfo = new ProjectRunInfo(project);

                    foreach (var mapping in project.Volumes)
                    {
                        projectInfo.VolumeMappings.Add(new DockerVolume(mapping.Source, mapping.Name, mapping.Target));
                    }

                    runInfo  = projectInfo;
                    replicas = project.Replicas;
                    liveness = project.Liveness != null?GetProbeFromBuilder(project.Liveness) : null;

                    readiness = project.Readiness != null?GetProbeFromBuilder(project.Readiness) : null;

                    foreach (var entry in project.EnvironmentVariables)
                    {
                        env.Add(entry.ToHostingEnvironmentVariable());
                    }
                }
                else
                {
                    throw new InvalidOperationException($"Cannot figure out how to run service '{service.Name}'.");
                }

                var description = new ServiceDescription(service.Name, runInfo)
                {
                    Replicas  = replicas,
                    Liveness  = liveness,
                    Readiness = readiness
                };

                description.Configuration.AddRange(env);
                description.Dependencies.AddRange(service.Dependencies);

                foreach (var binding in service.Bindings)
                {
                    description.Bindings.Add(new ServiceBinding()
                    {
                        ConnectionString = binding.ConnectionString,
                        Host             = binding.Host,
                        ContainerPort    = binding.ContainerPort,
                        Name             = binding.Name,
                        Port             = binding.Port,
                        Protocol         = binding.Protocol,
                    });
                }

                services.Add(service.Name, new Service(description));
            }

            // Ingress get turned into services for hosting
            foreach (var ingress in application.Ingress)
            {
                var rules = new List <IngressRule>();

                foreach (var rule in ingress.Rules)
                {
                    rules.Add(new IngressRule(rule.Host, rule.Path, rule.Service !));
                }

                var runInfo = new IngressRunInfo(rules);

                var description = new ServiceDescription(ingress.Name, runInfo)
                {
                    Replicas = ingress.Replicas,
                };

                foreach (var binding in ingress.Bindings)
                {
                    description.Bindings.Add(new ServiceBinding()
                    {
                        Name     = binding.Name,
                        Port     = binding.Port,
                        Protocol = binding.Protocol,
                    });
                }

                services.Add(ingress.Name, new Service(description));
            }

            return(new Application(application.Source, services)
            {
                Network = application.Network
            });
        }
Exemplo n.º 28
0
        public override Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service)
        {
            // No need to do this computation for a non-project since we're not deploying it.
            if (!(service is ProjectServiceBuilder project))
            {
                return(Task.CompletedTask);
            }

            // Compute ASPNETCORE_URLS based on the bindings exposed by *this* project.
            foreach (var binding in service.Bindings)
            {
                if (binding.Protocol == null && binding.ConnectionString == null)
                {
                    binding.Protocol = "http";
                }

                if (binding.Port == null && binding.Protocol == "http")
                {
                    binding.Port = 80;
                }

                if (binding.Protocol == "http")
                {
                    var port = binding.Port ?? 80;
                    var urls = $"http://*{(port == 80 ? "" : (":" + port.ToString()))}";
                    project.EnvironmentVariables.Add(new EnvironmentVariableBuilder("ASPNETCORE_URLS")
                    {
                        Value = urls,
                    });
                    project.EnvironmentVariables.Add(new EnvironmentVariableBuilder("PORT")
                    {
                        Value = port.ToString(CultureInfo.InvariantCulture),
                    });
                    break;
                }
            }

            // Process bindings and turn them into environment variables and secrets. There's
            // some duplication with the code in m8s (Application.cs) for populating environments.
            //
            // service.Service.Bindings is the bindings OUT - this step computes bindings IN.
            var bindings = new ComputedBindings();

            service.Outputs.Add(bindings);

            foreach (var other in application.Services)
            {
                if (object.ReferenceEquals(service, other))
                {
                    continue;
                }


                foreach (var binding in other.Bindings)
                {
                    if (other is ProjectServiceBuilder)
                    {
                        // The other thing is a project, and will be deployed along with this
                        // service.
                        var configName =
                            (binding.Name is null || binding.Name == other.Name) ?
                            other.Name.ToUpperInvariant() :
                            $"{other.Name.ToUpperInvariant()}__{binding.Name.ToUpperInvariant()}";

                        if (!string.IsNullOrEmpty(binding.ConnectionString))
                        {
                            // Special case for connection strings
                            bindings.Bindings.Add(new EnvironmentVariableInputBinding($"CONNECTIONSTRINGS__{configName}", binding.ConnectionString));
                            continue;
                        }

                        if (binding.Protocol == "https")
                        {
                            // We skip https for now in deployment, because the E2E requires certificates
                            // and we haven't done those features yet.
                            continue;
                        }

                        if (binding.Protocol == null)
                        {
                            binding.Protocol = "http";
                        }

                        if (binding.Port == null && binding.Protocol == "http")
                        {
                            binding.Port = 80;
                        }

                        if (!string.IsNullOrEmpty(binding.Protocol))
                        {
                            bindings.Bindings.Add(new EnvironmentVariableInputBinding($"SERVICE__{configName}__PROTOCOL", binding.Protocol));
                        }

                        if (binding.Port != null)
                        {
                            bindings.Bindings.Add(new EnvironmentVariableInputBinding($"SERVICE__{configName}__PORT", binding.Port.Value.ToString()));
                        }

                        bindings.Bindings.Add(new EnvironmentVariableInputBinding($"SERVICE__{configName}__HOST", binding.Host ?? other.Name));
                    }
                    else
                    {
                        // The other service is not a project, so we'll use secrets.
                        if (!string.IsNullOrEmpty(binding.ConnectionString))
                        {
                            bindings.Bindings.Add(new SecretConnectionStringInputBinding(
                                                      name: $"binding-{Environment}-{(binding.Name == null ? other.Name.ToLowerInvariant() : (other.Name.ToLowerInvariant() + "-" + binding.Name.ToLowerInvariant()))}-secret",
                                                      other,
                                                      binding,
                                                      keyname: $"CONNECTIONSTRINGS__{(binding.Name == null ? other.Name.ToUpperInvariant() : (other.Name.ToUpperInvariant() + "__" + binding.Name.ToUpperInvariant()))}"));
                        }
                        else
                        {
                            bindings.Bindings.Add(new SecretUrlInputBinding(
                                                      name: $"binding-{Environment}-{(binding.Name == null ? other.Name.ToLowerInvariant() : (other.Name.ToLowerInvariant() + "-" + binding.Name.ToLowerInvariant()))}-secret",
                                                      other,
                                                      binding,
                                                      keynamebase: $"SERVICE__{(binding.Name == null ? other.Name.ToUpperInvariant() : (other.Name.ToUpperInvariant() + "__" + binding.Name.ToUpperInvariant()))}"));
                        }
                    }
                }
            }

            return(Task.CompletedTask);
        }
Exemplo n.º 29
0
        private ComputedBindings ComputeBindings(ApplicationBuilder application, IEnumerable <string> dependencies)
        {
            var bindings = new ComputedBindings();

            foreach (var dependency in dependencies)
            {
                var other = application.Services.Single(a => a.Name == dependency);

                foreach (var binding in other.Bindings)
                {
                    if (other is ProjectServiceBuilder)
                    {
                        // The other thing is a project, and will be deployed along with this
                        // service.
                        var configName =
                            (binding.Name is null || binding.Name == other.Name) ?
                            other.Name.ToUpperInvariant() :
                            $"{other.Name.ToUpperInvariant()}__{binding.Name.ToUpperInvariant()}";

                        if (!string.IsNullOrEmpty(binding.ConnectionString))
                        {
                            // Special case for connection strings
                            bindings.Bindings.Add(new EnvironmentVariableInputBinding($"CONNECTIONSTRINGS__{configName}", binding.ConnectionString));
                            continue;
                        }

                        if (binding.Protocol == "https")
                        {
                            // We skip https for now in deployment, because the E2E requires certificates
                            // and we haven't done those features yet.
                            continue;
                        }

                        if (binding.Protocol == null)
                        {
                            binding.Protocol = "http";
                        }

                        if (binding.Port == null && binding.Protocol == "http")
                        {
                            binding.Port = 80;
                        }

                        if (!string.IsNullOrEmpty(binding.Protocol))
                        {
                            bindings.Bindings.Add(new EnvironmentVariableInputBinding($"SERVICE__{configName}__PROTOCOL", binding.Protocol));
                        }

                        if (binding.Port != null)
                        {
                            bindings.Bindings.Add(new EnvironmentVariableInputBinding($"SERVICE__{configName}__PORT", binding.Port.Value.ToString()));
                        }

                        bindings.Bindings.Add(new EnvironmentVariableInputBinding($"SERVICE__{configName}__HOST", binding.Host ?? other.Name));
                    }
                    else
                    {
                        // The other service is not a project, so we'll use secrets.
                        if (!string.IsNullOrEmpty(binding.ConnectionString))
                        {
                            bindings.Bindings.Add(new SecretConnectionStringInputBinding(
                                                      name: $"binding-{Environment}-{(binding.Name == null ? other.Name.ToLowerInvariant() : (other.Name.ToLowerInvariant() + "-" + binding.Name.ToLowerInvariant()))}-secret",
                                                      other,
                                                      binding,
                                                      keyname: $"CONNECTIONSTRINGS__{(binding.Name == null ? other.Name.ToUpperInvariant() : (other.Name.ToUpperInvariant() + "__" + binding.Name.ToUpperInvariant()))}"));
                        }
                        else
                        {
                            bindings.Bindings.Add(new SecretUrlInputBinding(
                                                      name: $"binding-{Environment}-{(binding.Name == null ? other.Name.ToLowerInvariant() : (other.Name.ToLowerInvariant() + "-" + binding.Name.ToLowerInvariant()))}-secret",
                                                      other,
                                                      binding,
                                                      keynamebase: $"SERVICE__{(binding.Name == null ? other.Name.ToUpperInvariant() : (other.Name.ToUpperInvariant() + "__" + binding.Name.ToUpperInvariant()))}"));
                        }
                    }
                }
            }

            return(bindings);
        }
Exemplo n.º 30
0
        public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder application, ServiceBuilder service)
        {
            var bindings = service.Outputs.OfType <ComputedBindings>().FirstOrDefault();

            if (bindings is null)
            {
                return;
            }

            foreach (var binding in bindings.Bindings)
            {
                if (binding is SecretInputBinding secretInputBinding)
                {
                    if (!Secrets.Add(secretInputBinding.Name))
                    {
                        output.WriteDebugLine($"Already validated secret '{secretInputBinding.Name}'.");
                        continue;
                    }

                    output.WriteDebugLine($"Validating secret '{secretInputBinding.Name}'.");

                    var config = KubernetesClientConfiguration.BuildDefaultConfig();

                    // Workaround for https://github.com/kubernetes-client/csharp/issues/372
                    var store = await KubernetesClientConfiguration.LoadKubeConfigAsync();

                    var context = store.Contexts.Where(c => c.Name == config.CurrentContext).FirstOrDefault();

                    // Use namespace of application, or current context, or 'default'
                    config.Namespace = application.Namespace;
                    config.Namespace ??= context?.ContextDetails?.Namespace ?? "default";

                    var kubernetes = new Kubernetes(config);

                    try
                    {
                        var result = await kubernetes.ReadNamespacedSecretWithHttpMessagesAsync(secretInputBinding.Name, config.Namespace);

                        output.WriteInfoLine($"Found existing secret '{secretInputBinding.Name}'.");
                        continue;
                    }
                    catch (HttpOperationException ex) when(ex.Response.StatusCode == HttpStatusCode.NotFound)
                    {
                        // The kubernetes client uses exceptions for 404s.
                    }
                    catch (Exception ex)
                    {
                        output.WriteDebugLine("Failed to query secret.");
                        output.WriteDebugLine(ex.ToString());
                        throw new CommandException("Unable connect to kubernetes.", ex);
                    }

                    if (Force)
                    {
                        output.WriteDebugLine("Skipping because force was specified.");
                        continue;
                    }

                    if (!Interactive && secretInputBinding is SecretConnectionStringInputBinding)
                    {
                        throw new CommandException(
                                  $"The secret '{secretInputBinding.Name}' used for service '{secretInputBinding.Service.Name}' is missing from the deployment environment. " +
                                  $"Rerun the command with --interactive to specify the value interactively, or with --force to skip validation. Alternatively " +
                                  $"use the following command to manually create the secret." + System.Environment.NewLine +
                                  $"kubectl create secret generic {secretInputBinding.Name} --namespace {config.Namespace} --from-literal=connectionstring=<value>");
                    }

                    if (!Interactive && secretInputBinding is SecretUrlInputBinding)
                    {
                        throw new CommandException(
                                  $"The secret '{secretInputBinding.Name}' used for service '{secretInputBinding.Service.Name}' is missing from the deployment environment. " +
                                  $"Rerun the command with --interactive to specify the value interactively, or with --force to skip validation. Alternatively " +
                                  $"use the following command to manually create the secret." + System.Environment.NewLine +
                                  $"kubectl create secret generic {secretInputBinding.Name} --namespace {config.Namespace} --from-literal=protocol=<value> --from-literal=host=<value> --from-literal=port=<value>");
                    }

                    V1Secret secret;
                    if (secretInputBinding is SecretConnectionStringInputBinding)
                    {
                        // If we get here then we should create the secret.
                        var text = output.Prompt($"Enter the connection string to use for service '{secretInputBinding.Service.Name}'", allowEmpty: true);
                        if (string.IsNullOrWhiteSpace(text))
                        {
                            output.WriteAlwaysLine($"Skipping creation of secret for '{secretInputBinding.Service.Name}'. This may prevent creation of pods until secrets are created.");
                            output.WriteAlwaysLine($"Manually create a secret with:");
                            output.WriteAlwaysLine($"kubectl create secret generic {secretInputBinding.Name} --namespace {config.Namespace} --from-literal=connectionstring=<value>");
                            continue;
                        }

                        secret = new V1Secret(type: "Opaque", stringData: new Dictionary <string, string>()
                        {
                            { "connectionstring", text },
                        });
                    }
                    else if (secretInputBinding is SecretUrlInputBinding)
                    {
                        // If we get here then we should create the secret.
                        string text;
                        Uri?   uri = null;
                        while (true)
                        {
                            text = output.Prompt($"Enter the URI to use for service '{secretInputBinding.Service.Name}'", allowEmpty: true);
                            if (string.IsNullOrEmpty(text))
                            {
                                break; // skip
                            }
                            else if (Uri.TryCreate(text, UriKind.Absolute, out uri))
                            {
                                break; // success
                            }

                            output.WriteAlwaysLine($"Invalid URI: '{text}'");
                        }

                        if (string.IsNullOrWhiteSpace(text))
                        {
                            output.WriteAlwaysLine($"Skipping creation of secret for '{secretInputBinding.Service.Name}'. This may prevent creation of pods until secrets are created.");
                            output.WriteAlwaysLine($"Manually create a secret with:");
                            output.WriteAlwaysLine($"kubectl create secret generic {secretInputBinding.Name} --namespace {config.Namespace} --from-literal=protocol=<value> --from-literal=host=<value> --from-literal=port=<value>");
                            continue;
                        }

                        secret = new V1Secret(type: "Opaque", stringData: new Dictionary <string, string>()
                        {
                            { "protocol", uri !.Scheme },