diff --git a/src/Runner.Worker/ActionManager.cs b/src/Runner.Worker/ActionManager.cs index 69c4a338d..5236b3f61 100644 --- a/src/Runner.Worker/ActionManager.cs +++ b/src/Runner.Worker/ActionManager.cs @@ -115,6 +115,14 @@ namespace GitHub.Runner.Worker executionContext.Result = TaskResult.Failed; throw; } + catch (InvalidActionArchiveException ex) + { + // Log the error and fail the PrepareActionsAsync Initialization. + Trace.Error($"Caught exception from PrepareActionsAsync Initialization: {ex}"); + executionContext.InfrastructureError(ex.Message); + executionContext.Result = TaskResult.Failed; + throw; + } if (!FeatureManager.IsContainerHooksEnabled(executionContext.Global.Variables)) { if (state.ImagesToPull.Count > 0) @@ -907,7 +915,14 @@ namespace GitHub.Runner.Worker Directory.CreateDirectory(stagingDirectory); #if OS_WINDOWS - ZipFile.ExtractToDirectory(archiveFile, stagingDirectory); + try + { + ZipFile.ExtractToDirectory(archiveFile, stagingDirectory); + } + catch (InvalidDataException e) + { + throw new InvalidActionArchiveException($"Can't un-zip archive file: {archiveFile}. action being checked out: {downloadInfo.NameWithOwner}@{downloadInfo.Ref}. error: {e}."); + } #else string tar = WhichUtil.Which("tar", require: true, trace: Trace); @@ -933,7 +948,7 @@ namespace GitHub.Runner.Worker int exitCode = await processInvoker.ExecuteAsync(stagingDirectory, tar, $"-xzf \"{archiveFile}\"", null, executionContext.CancellationToken); if (exitCode != 0) { - throw new NotSupportedException($"Can't use 'tar -xzf' extract archive file: {archiveFile}. return code: {exitCode}."); + throw new InvalidActionArchiveException($"Can't use 'tar -xzf' extract archive file: {archiveFile}. Action being checked out: {downloadInfo.NameWithOwner}@{downloadInfo.Ref}. return code: {exitCode}."); } } #endif diff --git a/src/Sdk/DTWebApi/WebApi/Exceptions.cs b/src/Sdk/DTWebApi/WebApi/Exceptions.cs index 29bf26240..97505bb6a 100644 --- a/src/Sdk/DTWebApi/WebApi/Exceptions.cs +++ b/src/Sdk/DTWebApi/WebApi/Exceptions.cs @@ -2516,4 +2516,23 @@ namespace GitHub.DistributedTask.WebApi { } } + + [Serializable] + public sealed class InvalidActionArchiveException : DistributedTaskException + { + public InvalidActionArchiveException(String message) + : base(message) + { + } + + public InvalidActionArchiveException(String message, Exception innerException) + : base(message, innerException) + { + } + + private InvalidActionArchiveException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } } diff --git a/src/Test/L0/Worker/ActionManagerL0.cs b/src/Test/L0/Worker/ActionManagerL0.cs index a3c45351d..dda20bdd8 100644 --- a/src/Test/L0/Worker/ActionManagerL0.cs +++ b/src/Test/L0/Worker/ActionManagerL0.cs @@ -96,6 +96,63 @@ namespace GitHub.Runner.Common.Tests.Worker } } + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public async void PrepareActions_DownloadActionFromDotCom_ZipFileError() + { + try + { + // Arrange + Setup(); + const string ActionName = "ownerName/sample-action"; + var actions = new List + { + new Pipelines.ActionStep() + { + Name = "action", + Id = Guid.NewGuid(), + Reference = new Pipelines.RepositoryPathReference() + { + Name = ActionName, + Ref = "main", + RepositoryType = "GitHub" + } + } + }; + + // Create a corrupted ZIP file for testing + var tempDir = _hc.GetDirectory(WellKnownDirectory.Temp); + Directory.CreateDirectory(tempDir); + var archiveFile = Path.Combine(tempDir, Path.GetRandomFileName()); + using (var fileStream = new FileStream(archiveFile, FileMode.Create)) + { + // Used Co-Pilot for magic bytes here. They represent the tar header and just need to be invalid for the CLI to break. + var buffer = new byte[] { 0x50, 0x4B, 0x03, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00 }; + fileStream.Write(buffer, 0, buffer.Length); + } + using var stream = File.OpenRead(archiveFile); + + string dotcomArchiveLink = GetLinkToActionArchive("https://api.github.com", ActionName, "main"); + var mockClientHandler = new Mock(); + mockClientHandler.Protected().Setup>("SendAsync", ItExpr.Is(m => m.RequestUri == new Uri(dotcomArchiveLink)), ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(stream) }); + + var mockHandlerFactory = new Mock(); + mockHandlerFactory.Setup(p => p.CreateClientHandler(It.IsAny())).Returns(mockClientHandler.Object); + _hc.SetSingleton(mockHandlerFactory.Object); + + _configurationStore.Object.GetSettings().IsHostedServer = true; + + // Act + Assert + await Assert.ThrowsAsync(async () => await _actionManager.PrepareActionsAsync(_ec.Object, actions)); + } + finally + { + Teardown(); + } + } + [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")]