public void TestGetInstallationPropertiesPath() { string installedPath = GCloudWrapper.GetInstallationPropertiesPath().Result; Assert.IsNotNullOrEmpty(installedPath); Assert.IsTrue(System.IO.File.Exists(installedPath), "Installation Path should points to a file"); }
public void TestGetAccessToken() { ActiveUserToken token = GCloudWrapper.GetAccessToken(new System.Threading.CancellationToken()).Result; Assert.IsNotNull(token); Assert.IsNotNullOrEmpty(token.AccessToken, "Token returned by GetAccessToken should have an access token."); Assert.IsNotNull(token.ExpiredTime, "Token returned by GetAccessToken should have an Issued DateTime."); Assert.IsFalse(token.IsExpired, "Token returned by GetAccessToken should be valid."); }
public void TestGetActiveConfig() { string config = GCloudWrapper.GetActiveConfig().Result; Assert.IsNotNull(config); JToken parsedConfigJson = JObject.Parse(config); Assert.IsNotNull(parsedConfigJson.SelectToken("sentinels.config_sentinel"), "Config returned by GetActiveConfig should have a sentinel file."); Assert.IsNotNull(parsedConfigJson.SelectToken("credential"), "Config returned by GetActiveConfig should have a credential property."); Assert.IsNotNull(parsedConfigJson.SelectToken("configuration.properties"), "Config returned by GetActiveConfig should have a configuration."); }
private static Task <bool> DeployAppBundleAsync( string stageDirectory, string version, bool promote, Context context, Action <string> outputAction) { var appYamlPath = Path.Combine(stageDirectory, AppYamlName); return(GCloudWrapper.DeployAppAsync( appYaml: appYamlPath, version: version, promote: promote, outputAction: outputAction, context: context)); }
private async Task <WindowsInstanceCredentials> CreateOrResetCredentials(string user) { try { Debug.WriteLine("The user requested the password to be generated."); if (!UserPromptUtils.ActionPrompt( prompt: String.Format(Resources.ResetPasswordConfirmationPromptMessage, user, _instance.Name), title: Resources.ResetPasswordConfirmationPromptTitle, message: Resources.UiOperationCannotBeUndone, actionCaption: Resources.UiResetButtonCaption, isWarning: true)) { Debug.WriteLine("The user cancelled resetting the password."); return(null); } Debug.WriteLine($"Resetting the password for the user {user}"); // Check that gcloud is in the right state to invoke the reset credentials method. if (!await VerifyGCloudDependencies()) { Debug.WriteLine("Missing gcloud dependencies for resetting password."); return(null); } var context = new GCloudContext { CredentialsPath = CredentialsStore.Default.CurrentAccountPath, ProjectId = CredentialsStore.Default.CurrentProjectId, AppName = GoogleCloudExtensionPackage.ApplicationName, AppVersion = GoogleCloudExtensionPackage.ApplicationVersion, }; return(await GCloudWrapper.ResetWindowsCredentialsAsync( instanceName : _instance.Name, zoneName : _instance.GetZoneName(), userName : user, context : context)); } catch (GCloudException ex) { UserPromptUtils.ErrorPrompt( message: String.Format(Resources.ResetPasswordFailedPromptMessage, _instance.Name), title: Resources.ResetPasswordConfirmationPromptTitle, errorDetails: ex.Message); return(null); } }
/// <summary> /// This method stages the application into the <paramref name="stageDirectory"/> by invoking the WebPublish target /// present in all Web projects. It publishes to the staging directory by using the FileSystem method. /// </summary> private static async Task <bool> CreateAppBundleAsync( IParsedProject project, string stageDirectory, IToolsPathProvider toolsPathProvider, Action <string> outputAction) { var arguments = $@"""{project.FullPath}""" + " " + "/p:Configuration=Release " + "/p:Platform=AnyCPU " + "/t:WebPublish " + "/p:WebPublishMethod=FileSystem " + "/p:DeleteExistingFiles=True " + $@"/p:publishUrl=""{stageDirectory}"""; outputAction($"msbuild.exe {arguments}"); bool result = await ProcessUtils.RunCommandAsync(toolsPathProvider.GetMsbuildPath(), arguments, (o, e) => outputAction(e.Line)); await GCloudWrapper.GenerateSourceContext(project.DirectoryPath, stageDirectory); return(result); }
private async Task <bool> VerifyGCloudDependencies() { if (!await GCloudWrapper.CanUseGKEAsync()) { if (!GCloudWrapper.IsGCloudCliInstalled()) { LinkPromptDialogWindow.PromptUser( Resources.ResetPasswordMissingGcloudTitle, Resources.ResetPasswordGcloudMissingMessage, new LinkInfo(link: "https://cloud.google.com/sdk/", caption: Resources.ResetPasswordGcloudLinkCaption)); } else { UserPromptUtils.ErrorPrompt( message: Resources.GkePublishMissingKubectlMessage, title: Resources.GcloudMissingComponentTitle); } return(false); } return(true); }
/// <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))) { var appRootPath = Path.Combine(stageDirectory, "app"); var buildFilePath = Path.Combine(stageDirectory, "cloudbuild.yaml"); if (!await ProgressHelper.UpdateProgress( NetCoreAppUtils.CreateAppBundleAsync(project, appRootPath, toolsPathProvider, outputAction), progress, from: 0.1, to: 0.3)) { Debug.WriteLine("Failed to create app bundle."); return(null); } NetCoreAppUtils.CopyOrCreateDockerfile(project, 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 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 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. var services = await KubectlWrapper.GetServicesAsync(options.KubectlContext); 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 KubectlWrapper.DeleteServiceAsync(options.DeploymentName, outputAction, options.KubectlContext)) { 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 KubectlWrapper.ExposeServiceAsync( options.DeploymentName, options.ExposePublicService, outputAction, options.KubectlContext)) { 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.WaitingForServiceIpCallback, options.KubectlContext); } serviceExposed = true; } } else { // The user doesn't want a service exposed. if (service != null) { if (!await KubectlWrapper.DeleteServiceAsync(options.DeploymentName, outputAction, options.KubectlContext)) { 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)); } }
/// <summary> /// Start the publish operation. /// </summary> public override async void Publish() { if (!ValidateInput()) { Debug.WriteLine("Invalid input cancelled the operation."); return; } var project = _publishDialog.Project; try { var verifyGCloudTask = VerifyGCloudDependencies(); _publishDialog.TrackTask(verifyGCloudTask); if (!await verifyGCloudTask) { Debug.WriteLine("Aborting deployment, no kubectl was found."); return; } var gcloudContext = new GCloudContext { CredentialsPath = CredentialsStore.Default.CurrentAccountPath, ProjectId = CredentialsStore.Default.CurrentProjectId, AppName = GoogleCloudExtensionPackage.ApplicationName, AppVersion = GoogleCloudExtensionPackage.ApplicationVersion, }; var kubectlContextTask = GCloudWrapper.GetKubectlContextForClusterAsync( cluster: SelectedCluster.Name, zone: SelectedCluster.Zone, context: gcloudContext); _publishDialog.TrackTask(kubectlContextTask); using (var kubectlContext = await kubectlContextTask) { var deploymentExistsTask = KubectlWrapper.DeploymentExistsAsync(DeploymentName, kubectlContext); _publishDialog.TrackTask(deploymentExistsTask); if (await deploymentExistsTask) { if (!UserPromptUtils.ActionPrompt( String.Format(Resources.GkePublishDeploymentAlreadyExistsMessage, DeploymentName), Resources.GkePublishDeploymentAlreadyExistsTitle, actionCaption: Resources.UiUpdateButtonCaption)) { return; } } var options = new GkeDeployment.DeploymentOptions { Cluster = SelectedCluster.Name, Zone = SelectedCluster.Zone, DeploymentName = DeploymentName, DeploymentVersion = DeploymentVersion, ExposeService = ExposeService, GCloudContext = gcloudContext, KubectlContext = kubectlContext, Replicas = int.Parse(Replicas), WaitingForServiceIpCallback = () => GcpOutputWindow.OutputLine(Resources.GkePublishWaitingForServiceIpMessage) }; GcpOutputWindow.Activate(); GcpOutputWindow.Clear(); GcpOutputWindow.OutputLine(String.Format(Resources.GkePublishDeployingToGkeMessage, project.Name)); _publishDialog.FinishFlow(); GkeDeploymentResult result; using (var frozen = StatusbarHelper.Freeze()) using (var animationShown = StatusbarHelper.ShowDeployAnimation()) using (var progress = StatusbarHelper.ShowProgressBar(Resources.GkePublishDeploymentStatusMessage)) using (var deployingOperation = ShellUtils.SetShellUIBusy()) { result = await GkeDeployment.PublishProjectAsync( project.FullPath, options, progress, GcpOutputWindow.OutputLine); } if (result != null) { GcpOutputWindow.OutputLine(String.Format(Resources.GkePublishDeploymentSuccessMessage, project.Name)); if (result.DeploymentUpdated) { GcpOutputWindow.OutputLine(String.Format(Resources.GkePublishDeploymentUpdatedMessage, options.DeploymentName)); } if (result.DeploymentScaled) { GcpOutputWindow.OutputLine(String.Format(Resources.GkePublishDeploymentScaledMessage, options.DeploymentName, options.Replicas)); } if (result.WasExposed) { if (result.ServiceIpAddress != null) { GcpOutputWindow.OutputLine( String.Format(Resources.GkePublishServiceIpMessage, DeploymentName, result.ServiceIpAddress)); } else { GcpOutputWindow.OutputLine(Resources.GkePublishServiceIpTimeoutMessage); } } StatusbarHelper.SetText(Resources.PublishSuccessStatusMessage); if (OpenWebsite && result.WasExposed && result.ServiceIpAddress != null) { Process.Start($"http://{result.ServiceIpAddress}"); } } else { GcpOutputWindow.OutputLine(String.Format(Resources.GkePublishDeploymentFailureMessage, project.Name)); StatusbarHelper.SetText(Resources.PublishFailureStatusMessage); } } } catch (Exception ex) when(!ErrorHandlerUtils.IsCriticalException(ex)) { GcpOutputWindow.OutputLine(String.Format(Resources.GkePublishDeploymentFailureMessage, project.Name)); StatusbarHelper.SetText(Resources.PublishFailureStatusMessage); _publishDialog.FinishFlow(); } }
private async void OnOkCommand() { if (!UserPromptUtils.YesNoPrompt( String.Format(Resources.ResetPasswordConfirmationPromptMessage, UserName, _instance.Name), Resources.ResetPasswordConfirmationPromptTitle)) { Debug.WriteLine("The user cancelled resetting the password."); return; } try { Debug.WriteLine($"Resetting the password for the user {UserName}"); ResettingPassword = true; // The operation cannot be cancelled once it started, so we have to disable the buttons while // it is in flight. OkCommand.CanExecuteCommand = false; CancelCommand.CanExecuteCommand = false; _owner.IsCloseButtonEnabled = false; // Check that gcloud is in the right state to invoke the reset credentials method. if (!await GCloudWrapper.CanUseResetWindowsCredentialsAsync()) { if (!GCloudWrapper.IsGCloudCliInstalled()) { LinkPromptDialogWindow.PromptUser( Resources.ResetPasswordMissingGcloudTitle, Resources.ResetPasswordGcloudMissingMessage, new LinkInfo(link: "https://cloud.google.com/sdk/", caption: Resources.ResetPasswordGcloudLinkCaption)); } else { UserPromptUtils.ErrorPrompt( message: Resources.ResetPasswordGcloudMissingBetaMessage, title: Resources.ResetPasswordGcloudMissingComponentTitle); } return; } var context = new Context { CredentialsPath = CredentialsStore.Default.CurrentAccountPath, ProjectId = _projectId, AppName = GoogleCloudExtensionPackage.ApplicationName, AppVersion = GoogleCloudExtensionPackage.ApplicationVersion, }; var newCredentials = await GCloudWrapper.ResetWindowsCredentialsAsync( instanceName : _instance.Name, zoneName : _instance.GetZoneName(), userName : _userName, context : context); ResettingPassword = false; ShowPasswordWindow.PromptUser( userName: UserName, password: newCredentials.Password, instanceName: _instance.Name); } catch (GCloudException ex) { UserPromptUtils.ErrorPrompt( String.Format(Resources.ResetPasswordFailedPromptMessage, _instance.Name, ex.Message), Resources.ResetPasswordConfirmationPromptTitle); } finally { _owner.Close(); } }
/// <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)); } }