Editor/Window/Containers/PushImageAutoStep.cs (174 lines of code) (raw):
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
using System;
using UnityEngine;
using UnityEngine.UIElements;
using System.Threading.Tasks;
using System.IO;
using AmazonGameLiftPlugin.Core.Shared.FileSystem;
using System.Diagnostics;
using Amazon.Runtime.Internal.Util;
namespace AmazonGameLift.Editor
{
/// <summary>
/// The component that's used when docker is installed. We generate and execute the push script,
/// and proceed to the next step automatically
/// </summary>
public class PushImageAutoStep : ContainerStepComponent
{
private const int periodMs = 5 * 1000; // check result of command execution the every 5 seconds
private const int timeoutMs = 10 * 60 * 1000; // 10 minutes timeout
private string _scriptLoggingPath;
private readonly CoreApi _coreApi;
private bool _isStepCompleted = false;
private string _errorMessage = null;
private Button _proceedButton;
private Button _tryAgainButton;
private Button _viewLogButton;
private UnityLogger _logger;
private GameLiftSynchronizationContext _mainThreadContext;
private DeploymentStepTemplate _stepContent;
private VisualElement ButtonContainer => _stepContent.ButtonContainer;
public PushImageAutoStep(VisualElement container, StateManager stateManager) : base(container, stateManager, "EditorWindow/Components/Containers/PushImageAutoStep")
{
TextProvider textProvider = TextProviderFactory.Create();
_logger = UnityLoggerFactory.Create(textProvider);
_coreApi = CoreApi.SharedInstance;
_stepContent = new DeploymentStepTemplate.Builder(Strings.ContainerPushImageAutoStepTitle, Strings.ContainerPushImageAutoStepDescription)
.WithHelpLinks(new DeploymentStepTemplateLink(Urls.ECRUserGuide, Strings.ContainerLinksEcrUserGuideLabel))
.WithBaseButtons()
.Build(container);
_proceedButton = ButtonContainer.Q<Button>(DeploymentStepTemplate.BaseButtonProceed);
_tryAgainButton = ButtonContainer.Q<Button>(DeploymentStepTemplate.BaseButtonTryAgain);
_viewLogButton = ButtonContainer.Q<Button>(DeploymentStepTemplate.BaseButtonViewLogs);
_proceedButton.RegisterCallback<ClickEvent>(_ => {}); // NO-OP
_tryAgainButton.RegisterCallback<ClickEvent>(_ => { base.ResetAndTryStart(); });
_viewLogButton.RegisterCallback<ClickEvent>(_ => { Process.Start($"\"{_scriptLoggingPath}\""); });
Hide(ButtonContainer);
Hide(_proceedButton);
Hide(_tryAgainButton);
Hide(_viewLogButton);
Hide(_stepContent.ContentContainer);
_isStepCompleted = _stateManager.IsContainerPushedToECR;
// Some Unity functions needs to be executed in main thread (called from async functions)
_mainThreadContext = GameLiftSynchronizationContext.Current;
}
private void PopulateTagSection()
{
Show(_stepContent.ContentContainer);
_stepContent.ContentContainer.Q<Label>("ImageTagValue").text = DashIfEmpty(_stateManager.ContainerImageTag);
}
protected void SaveImageTagAndCompleteStep()
{
PopulateTagSection();
_stateManager.IsContainerPushedToECR = true;
_stateManager.ContainerECRImageId = _stateManager.ContainerImageTag;
_stateManager.ContainerECRImageUri = _stateManager.ContainerECRRepositoryUri + ":" + _stateManager.ContainerImageTag;
_isStepCompleted = true;
base.CompleteStep();
}
private void FailStep(string errorText)
{
_errorMessage = errorText;
base.EncounteredException(
statusBoxType: StatusBox.StatusBoxType.Error,
text: errorText,
externalButtonText: "Show logs",
externalButtonLink: _scriptLoggingPath,
externalTargetType: StatusBox.StatusBoxExternalTargetType.File
);
Show(ButtonContainer);
Show(_tryAgainButton);
Show(_viewLogButton);
Hide(_proceedButton);
}
private void UpdateToInProgress()
{
Show(ButtonContainer);
Hide(_tryAgainButton);
Hide(_proceedButton);
Show(_viewLogButton);
_stateManager.IsContainerPushedToECR = false;
PopulateTagSection();
}
protected sealed override void ResetStep()
{
Hide(ButtonContainer);
Hide(_proceedButton);
Hide(_tryAgainButton);
Hide(_viewLogButton);
Hide(_stepContent.ContentContainer);
_isStepCompleted = false;
_errorMessage = null;
_stateManager.IsContainerPushedToECR = false;
_stateManager.ContainerECRImageId = null;
}
protected sealed override async Task StartOrResumeStep()
{
bool stopEarly = false;
if (_errorMessage != null)
{
_mainThreadContext.Send(_ => FailStep(_errorMessage), null);
stopEarly = true;
}
if (_isStepCompleted)
{
_mainThreadContext.Send(_ => SaveImageTagAndCompleteStep(), null);
stopEarly = true;
}
if (stopEarly)
{
return;
}
try
{
StartProcess();
}
catch (Exception e)
{
_mainThreadContext.LogError($"Failed to push image to Amazon ECR due to unexpected exception: {e}.");
_mainThreadContext.Send(_ => FailStep($"Failed to push image to Amazon ECR due to unexpected exception: {e.Message}.\nSee Console for full exception."), null);
throw e;
}
await WaitForProcessToComplete();
}
private void StartProcess() {
var fileWrapper = new FileWrapper();
string containersPath = PathConverter.SharedInstance.GetContainersAbsolutePath();
// Prepare output directory
string containersOutputDirectory = Path.Combine(containersPath, Paths.ContainersOutputFolderName);
if (!fileWrapper.DirectoryExists(containersOutputDirectory))
{
fileWrapper.CreateDirectory(containersOutputDirectory);
}
_scriptLoggingPath = Path.Combine(containersOutputDirectory, "PushExistingImageToECRScriptOutput.txt");
string scriptPath = Path.Combine(containersOutputDirectory, "PushExistingImageToECRScript.ps1");
// Update UI to be in progress
_mainThreadContext.Send(_ => UpdateToInProgress(), null);
// Prepare script and write to output folder
string commandTemplate = fileWrapper.ReadAllText(Path.Combine(containersPath, Paths.ContainerPushImageScriptFileName));
fileWrapper.WriteAllText(scriptPath, GetPreparedCommand(commandTemplate));
_logger.Log($"Generated push script at {scriptPath}", UnityEngine.LogType.Log);
// Execute push script
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo.FileName = "powershell";
process.StartInfo.Arguments = $"powershell -File \'{scriptPath}\' 2>&1"
+ $"| tee -filePath \'{_scriptLoggingPath}\'";
process.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
process.Start();
}
private async Task WaitForProcessToComplete()
{
// Monitor execution status
for (int timeMs = 0; timeMs < timeoutMs; timeMs += periodMs)
{
await Task.Delay(periodMs);
try
{
string log = new FileWrapper().ReadAllText(_scriptLoggingPath);
// Detecting failure/success based on logged messages from the script
if (log.Contains("has failed."))
{
_mainThreadContext.Send(_ =>
FailStep($"Failed to push image to Amazon ECR due to execution failure. Please check the logs for details. Location: {_scriptLoggingPath}"), null);
return;
}
if (log.Contains("Docker image successfully pushed to Amazon ECR."))
{
_mainThreadContext.Send(_ => SaveImageTagAndCompleteStep(), null);
return;
}
}
catch (IOException) { /* Catch IO exception in case we open the file while it being written */ }
}
_mainThreadContext.Send(_ => FailStep($"Failed to push image to Amazon ECR due to execution timed out. Please check the logs for details. Location: {_scriptLoggingPath}"), null);
}
}
}