private async Task <string> BuildImageAsync(IParsedProject project, Options options, IProgress <double> progress) { ShellUtils.SaveAllFiles(); string stageDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); await progress.ReportAsync(0.1); using (new Disposable(() => CommonUtils.Cleanup(stageDirectory))) { Task <bool> createAppBundleTask = NetCoreAppUtils.CreateAppBundleAsync( project, stageDirectory, GcpOutputWindow.OutputLineAsync, options.Configuration); if (!await progress.UpdateProgressAsync(createAppBundleTask, 0.1, 0.3)) { return(null); } NetCoreAppUtils.CopyOrCreateDockerfile(project, stageDirectory); string imageTag = CloudBuilderUtils.GetImageTag( options.KubectlContext.ProjectId, options.DeploymentName, options.DeploymentVersion); Task <bool> buildContainerTask = options.KubectlContext.BuildContainerAsync(imageTag, stageDirectory, GcpOutputWindow.OutputLineAsync); if (!await progress.UpdateProgressAsync(buildContainerTask, 0.4, 0.7)) { return(null); } return(imageTag); } }
/// <summary> /// Publishes the ASP.NET Core app using the <paramref name="options"/> to produce the right deployment /// and service (if needed). /// </summary> /// <param name="project">The project.</param> /// <param name="options">The options to use for the deployment.</param> /// <param name="progress">The progress interface for progress notifications.</param> /// <param name="toolsPathProvider">Provides the path to the publish tools.</param> /// <param name="outputAction">The output callback to invoke for output from the process.</param> /// <returns>Returns a <seealso cref="GkeDeploymentResult"/> if the deployment succeeded null otherwise.</returns> public static async Task <GkeDeploymentResult> PublishProjectAsync( IParsedProject project, DeploymentOptions options, IProgress <double> progress, IToolsPathProvider toolsPathProvider, Action <string> outputAction) { var stageDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(stageDirectory); progress.Report(0.1); using (var cleanup = new Disposable(() => CommonUtils.Cleanup(stageDirectory))) { if (!await ProgressHelper.UpdateProgress( NetCoreAppUtils.CreateAppBundleAsync(project, stageDirectory, toolsPathProvider, outputAction, options.Configuration), progress, from: 0.1, to: 0.3)) { Debug.WriteLine("Failed to create app bundle."); return(null); } NetCoreAppUtils.CopyOrCreateDockerfile(project, stageDirectory); var imageTag = CloudBuilderUtils.GetImageTag( project: options.KubectlContext.ProjectId, imageName: options.DeploymentName, imageVersion: options.DeploymentVersion); if (!await ProgressHelper.UpdateProgress( options.KubectlContext.BuildContainerAsync(imageTag, stageDirectory, outputAction), progress, from: 0.4, to: 0.7)) { Debug.WriteLine("Failed to build container."); return(null); } progress.Report(0.7); string publicIpAddress = null; string clusterIpAddress = null; bool deploymentUpdated = false; bool deploymentScaled = false; bool serviceExposed = false; bool serviceUpdated = false; bool serviceDeleted = false; // Create or update the deployment. var deployments = await options.KubectlContext.GetDeploymentsAsync(); var deployment = deployments?.FirstOrDefault(x => x.Metadata.Name == options.DeploymentName); if (deployment == null) { Debug.WriteLine($"Creating new deployment {options.DeploymentName}"); if (!await options.KubectlContext.CreateDeploymentAsync(options.DeploymentName, imageTag, options.Replicas, outputAction)) { Debug.WriteLine($"Failed to create deployment {options.DeploymentName}"); return(null); } progress.Report(0.8); } else { Debug.WriteLine($"Updating existing deployment {options.DeploymentName}"); if (!await options.KubectlContext.UpdateDeploymentImageAsync(options.DeploymentName, imageTag, outputAction)) { Debug.WriteLine($"Failed to update deployemnt {options.DeploymentName}"); return(null); } deploymentUpdated = true; // If the deployment already exists but the replicas number requested is not the // same as the existing number we will scale up/down the deployment. if (deployment.Spec.Replicas != options.Replicas) { Debug.WriteLine("Updating the replicas for the deployment."); if (!await options.KubectlContext.ScaleDeploymentAsync(options.DeploymentName, options.Replicas, outputAction)) { Debug.WriteLine($"Failed to scale up deployment {options.DeploymentName}"); return(null); } deploymentScaled = true; } } // Expose the service if requested and it is not already exposed. var services = await options.KubectlContext.GetServicesAsync(); var service = services?.FirstOrDefault(x => x.Metadata.Name == options.DeploymentName); if (options.ExposeService) { var requestedType = options.ExposePublicService ? GkeServiceSpec.LoadBalancerType : GkeServiceSpec.ClusterIpType; if (service != null && service.Spec?.Type != requestedType) { Debug.WriteLine($"The existing service is {service.Spec?.Type} the requested is {requestedType}"); if (!await options.KubectlContext.DeleteServiceAsync(options.DeploymentName, outputAction)) { Debug.WriteLine($"Failed to delete serive {options.DeploymentName}"); } service = null; // Now the service is gone, needs to be re-created with the new options. serviceUpdated = true; } if (service == null) { // The service needs to be exposed but it wasn't. Expose a new service here. if (!await options.KubectlContext.ExposeServiceAsync(options.DeploymentName, options.ExposePublicService, outputAction)) { Debug.WriteLine($"Failed to expose service {options.DeploymentName}"); return(null); } clusterIpAddress = await WaitForServiceClusterIpAddressAsync(options.DeploymentName, options.KubectlContext); if (options.ExposePublicService) { publicIpAddress = await WaitForServicePublicIpAddressAsync( options.DeploymentName, options.KubectlContext); } serviceExposed = true; } } else { // The user doesn't want a service exposed. if (service != null) { if (!await options.KubectlContext.DeleteServiceAsync(options.DeploymentName, outputAction)) { Debug.WriteLine($"Failed to delete service {options.DeploymentName}"); return(null); } } serviceDeleted = true; } return(new GkeDeploymentResult( publicIpAddress: publicIpAddress, privateIpAddress: clusterIpAddress, serviceExposed: serviceExposed, serviceUpdated: serviceUpdated, serviceDeleted: serviceDeleted, deploymentUpdated: deploymentUpdated, deploymentScaled: deploymentScaled)); } }