/// <summary> /// Generates the Dockerfile for this .NET Core project. /// </summary> /// <param name="projectPath">The full path to the project.json file for the project.</param> internal static void GenerateDockerfile(string projectPath) { var projectDirectory = Path.GetDirectoryName(projectPath); var targetDockerfile = Path.Combine(projectDirectory, DockerfileName); var content = String.Format(DockerfileDefaultContent, CommonUtils.GetProjectName(projectPath)); File.WriteAllText(targetDockerfile, content); }
/// <summary> /// Creates the Dockerfile necessary to package up an ASP.NET Core app if one is not already present at the root /// path of the project. /// </summary> /// <param name="projectPath">The full path to the project.json.</param> /// <param name="stageDirectory">The directory where to save the Dockerfile.</param> internal static void CopyOrCreateDockerfile(string projectPath, string stageDirectory) { var sourceDir = Path.GetDirectoryName(projectPath); var sourceDockerfile = Path.Combine(sourceDir, DockerfileName); var targetDockerfile = Path.Combine(stageDirectory, DockerfileName); if (File.Exists(sourceDockerfile)) { File.Copy(sourceDockerfile, targetDockerfile, overwrite: true); } else { var content = String.Format(DockerfileDefaultContent, CommonUtils.GetProjectName(projectPath)); File.WriteAllText(targetDockerfile, content); } }
/// <summary> /// Publishes the ASP.NET Core app using the <paramref name="options"/> to produce the right deployment /// and service (if needed). /// </summary> /// <param name="projectPath">The full path to the project.json file of the startup 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="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( string projectPath, DeploymentOptions options, IProgress <double> progress, Action <string> outputAction) { if (!File.Exists(projectPath)) { Debug.WriteLine($"Cannot find {projectPath}, not a valid project."); return(null); } var stageDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(stageDirectory); progress.Report(0.1); using (var cleanup = new Disposable(() => CommonUtils.Cleanup(stageDirectory))) { var appRootPath = Path.Combine(stageDirectory, "app"); var buildFilePath = Path.Combine(stageDirectory, "cloudbuild.yaml"); var projectName = CommonUtils.GetProjectName(projectPath); if (!await ProgressHelper.UpdateProgress( NetCoreAppUtils.CreateAppBundleAsync(projectPath, appRootPath, outputAction), progress, from: 0.1, to: 0.3)) { Debug.WriteLine("Failed to create app bundle."); return(null); } NetCoreAppUtils.CopyOrCreateDockerfile(projectPath, appRootPath); var image = CloudBuilderUtils.CreateBuildFile( project: options.GCloudContext.ProjectId, imageName: options.DeploymentName, imageVersion: options.DeploymentVersion, buildFilePath: buildFilePath); if (!await ProgressHelper.UpdateProgress( GCloudWrapper.BuildContainerAsync(buildFilePath, appRootPath, outputAction, options.GCloudContext), progress, from: 0.4, to: 0.7)) { Debug.WriteLine("Failed to build container."); return(null); } progress.Report(0.7); string ipAddress = null; bool deploymentUpdated = false; bool deploymentScaled = false; bool serviceExposed = false; // Create or update the deployment. var deployments = await KubectlWrapper.GetDeploymentsAsync(options.KubectlContext); var deployment = deployments?.FirstOrDefault(x => x.Metadata.Name == options.DeploymentName); if (deployment == null) { Debug.WriteLine($"Creating new deployment {options.DeploymentName}"); if (!await KubectlWrapper.CreateDeploymentAsync( name: options.DeploymentName, imageTag: image, replicas: options.Replicas, outputAction: outputAction, context: options.KubectlContext)) { Debug.WriteLine($"Failed to create deployment {options.DeploymentName}"); return(null); } progress.Report(0.8); } else { Debug.WriteLine($"Updating existing deployment {options.DeploymentName}"); if (!await KubectlWrapper.UpdateDeploymentImageAsync( options.DeploymentName, image, outputAction, options.KubectlContext)) { 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 KubectlWrapper.ScaleDeploymentAsync( options.DeploymentName, options.Replicas, outputAction, options.KubectlContext)) { Debug.WriteLine($"Failed to scale up deployment {options.DeploymentName}"); return(null); } deploymentScaled = true; } } // Expose the service if requested and it is not already exposed. if (options.ExposeService) { var services = await KubectlWrapper.GetServicesAsync(options.KubectlContext); var service = services?.FirstOrDefault(x => x.Metadata.Name == options.DeploymentName); if (service == null) { if (!await KubectlWrapper.ExposeServiceAsync(options.DeploymentName, outputAction, options.KubectlContext)) { Debug.WriteLine($"Failed to expose service {options.DeploymentName}"); return(null); } } ipAddress = await WaitForServiceAddressAsync( options.DeploymentName, options.WaitingForServiceIpCallback, options.KubectlContext); serviceExposed = true; } return(new GkeDeploymentResult( serviceIpAddress: ipAddress, wasExposed: serviceExposed, deploymentUpdated: deploymentUpdated, deploymentScaled: deploymentScaled)); } }