// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 using System; using System.Threading.Tasks; using AmazonGameLiftPlugin.Core.DeploymentManagement.Models; using AmazonGameLiftPlugin.Core.Shared; namespace AmazonGameLift.Editor { /// /// Waits until a stack change is applied and allows to cancel. /// public class DeploymentWaiter { private const int StackPollPeriodMs = 5000; private readonly Delay _delay; private DeploymentId _currentRequest; private string _currentToken; private bool _isCreating; private bool _isWaiting; public virtual bool CanCancel { get; private set; } protected CoreApi GameLiftCoreApi { get; } public virtual event Action InfoUpdated; public DeploymentWaiter() { _delay = new Delay(); GameLiftCoreApi = CoreApi.SharedInstance; } internal DeploymentWaiter(Delay delay, CoreApi coreApi) { _delay = delay ?? throw new ArgumentNullException(nameof(delay)); GameLiftCoreApi = coreApi ?? throw new ArgumentNullException(nameof(coreApi)); } public virtual Response CancelDeployment() { if (!CanCancel) { return Response.Fail(new Response() { ErrorCode = ErrorCode.OperationInvalid }); } Response response; if (_isCreating) { response = GameLiftCoreApi.DeleteStack(_currentRequest.Profile, _currentRequest.Region, _currentRequest.StackName); } else { response = GameLiftCoreApi.CancelDeployment(_currentRequest.Profile, _currentRequest.Region, _currentRequest.StackName, _currentToken); } if (response.Success) { CanCancel = false; } return response; } public virtual async Task WaitUntilDone(DeploymentId deploymentId) { if (_isWaiting) { return Response.Fail(new DeploymentResponse(ErrorCode.OperationInvalid)); } _isWaiting = true; _currentRequest = deploymentId; try { return await PollStatusUntilDone(deploymentId); } finally { CleanUpCurrentDeployment(); } } public virtual Response CancelWaiting() { if (!_isWaiting) { return Response.Fail(new Response { ErrorCode = ErrorCode.OperationInvalid }); } CleanUpCurrentDeployment(); return Response.Ok(new Response()); } private async Task PollStatusUntilDone(DeploymentId deploymentId) { var poller = new UntilResponseFailurePoller(_delay); DescribeStackResponse describeStackResponse = await poller.Poll(StackPollPeriodMs, () => { DescribeStackResponse response = GameLiftCoreApi.DescribeStack(deploymentId.Profile, deploymentId.Region, deploymentId.StackName); if (!response.Success) { return response; } if (_currentToken == null && (response.StackStatus == StackStatus.UpdateInProgress || response.StackStatus == StackStatus.CreateInProgress)) { CanCancel = true; _currentToken = Guid.NewGuid().ToString(); _isCreating = response.StackStatus == StackStatus.CreateInProgress; } InfoUpdated?.Invoke(new DeploymentInfo(deploymentId, response, deploymentId.ScenarioName)); return response; }, stopCondition: target => !_isWaiting || target.StackStatus.IsStackStatusOperationDone()); if (!describeStackResponse.Success) { return Response.Fail(new DeploymentResponse(describeStackResponse)); } if (!_isWaiting) { return Response.Fail(new DeploymentResponse(ErrorCode.OperationCancelled)); } if (describeStackResponse.StackStatus != StackStatus.CreateComplete && describeStackResponse.StackStatus != StackStatus.UpdateComplete && describeStackResponse.StackStatus != StackStatus.UpdateCompleteCleanUpInProgress) { return Response.Fail(new DeploymentResponse(ErrorCode.StackStatusInvalid, $"The '{deploymentId.StackName}' stack status is {describeStackResponse.StackStatus}")); } return Response.Ok(new DeploymentResponse(_currentRequest)); } private void CleanUpCurrentDeployment() { _isWaiting = false; _currentToken = null; CanCancel = false; _isCreating = false; _currentRequest = default; } } }