コード例 #1
0
        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");
        }
コード例 #2
0
        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.");
        }
コード例 #3
0
        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.");
        }
コード例 #4
0
        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));
        }
コード例 #5
0
        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);
            }
        }
コード例 #6
0
        /// <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);
        }
コード例 #7
0
        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);
        }
コード例 #8
0
        /// <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));
            }
        }
コード例 #9
0
        /// <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();
            }
        }
コード例 #10
0
        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();
            }
        }
コード例 #11
0
        /// <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));
            }
        }