Skip to content

Commit

Permalink
(GH-4391) GHA cross-job download artifact support
Browse files Browse the repository at this point in the history
* fixes #4391
  • Loading branch information
devlead committed Nov 9, 2024
1 parent cf55842 commit f730597
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 19 deletions.
13 changes: 12 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,19 @@ on:
- develop
- hotfix/*
jobs:
prepare:
name: Prepare integration tests
runs-on: ubuntu-latest
steps:
- run: echo "Cake Integration Tests" > cake-integration-tests.txt
- uses: actions/upload-artifact@v4
with:
name: cake-integration-tests
path: cake-integration-tests.txt

build:
name: Build
needs: prepare
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
Expand All @@ -34,7 +45,7 @@ jobs:

- name: Run Cake script
id: build-cake
uses: cake-build/cake-action@v1
uses: cake-build/cake-action@master
with:
target: Run-Integration-Tests
cake-version: tool-manifest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using System.Threading.Tasks;
using Cake.Common.Build.GitHubActions.Commands;
using Cake.Common.Build.GitHubActions.Commands.Artifact;
using Cake.Common.Build.GitHubActions.Data;
using Cake.Common.Tests.Fakes;
using Cake.Core;
using Cake.Core.IO;
Expand All @@ -22,21 +21,45 @@ internal sealed class GitHubActionsCommandsFixture : HttpMessageHandler
private const string ArtifactUrl = GitHubActionsInfoFixture.ActionResultsUrl + "twirp/github.actions.results.api.v1.ArtifactService/";
private const string CreateArtifactUrl = ArtifactUrl + "CreateArtifact";
private const string FinalizeArtifactUrl = ArtifactUrl + "FinalizeArtifact";
private const string GetSignedArtifactURLurl = ArtifactUrl + "GetSignedArtifactURL";
private const string GetSignedArtifactURLUrl = ArtifactUrl + "GetSignedArtifactURL";
private const string ListArtifactsUrl = ArtifactUrl + "ListArtifacts";
private const string UploadFileUrl = "https://cake.build.net/actions-results/a9d82106-d5d5-4310-8f60-0bfac035cf02/workflow-job-run-1d849a45-2f30-5fbb-3226-b730a17a93af/artifacts/91e64594182918fa8012cdbf7d1a4f801fa0c35f485c3277268aad8e3f45377c.zip?sig=upload";
private const string DownloadFileUrl = "https://cake.build.net/actions-results/a9d82106-d5d5-4310-8f60-0bfac035cf02/workflow-job-run-1d849a45-2f30-5fbb-3226-b730a17a93af/artifacts/91e64594182918fa8012cdbf7d1a4f801fa0c35f485c3277268aad8e3f45377c.zip?sig=download";
private const string CreateArtifactResponse = @"{
""ok"": true,
""signed_upload_url"": """ + UploadFileUrl + @"""
}";
private const string FinalizeArtifactResponse = @"{
""ok"": true,
""artifact_id"": ""1991105334""
}";
private const string GetSignedArtifactURLResponse = @"{
""name"": ""artifact"",
""signed_url"": """ + DownloadFileUrl + @"""
}";
private const string CreateArtifactResponse =
$$"""
{
"ok": true,
"signed_upload_url": "{{UploadFileUrl}}"
}
""";
private const string FinalizeArtifactResponse =
"""
{
"ok": true,
"artifact_id": "1991105334"
}
""";
private const string GetSignedArtifactURLResponse =
$$"""
{
"name": "artifact",
"signed_url": "{{DownloadFileUrl}}"
}
""";
private const string ListArtifactsResponse =
$$"""
{
"artifacts": [
{
"workflow_run_backend_id": "b9e28153-ca20-4b86-91dd-09e8f644efdf",
"workflow_job_run_backend_id": "1d849a45-2f30-5fbb-3226-b730a17a93af",
"database_id": "1",
"name": "artifact",
"created_at": "2024-11-09T21:53:00.7110204+00:00"
}
]
}
""";

private GitHubActionsInfoFixture GitHubActionsInfoFixture { get; }
private ICakeEnvironment Environment { get; }
Expand Down Expand Up @@ -122,7 +145,7 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
// Get Signed Artifact Url
case
{
RequestUri: { AbsoluteUri: GetSignedArtifactURLurl },
RequestUri: { AbsoluteUri: GetSignedArtifactURLUrl },
Method: { Method: "POST" },
}:
{
Expand Down Expand Up @@ -189,6 +212,16 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
return Ok();
}

// List Artifacts
case
{
RequestUri: { AbsoluteUri: ListArtifactsUrl },
Method: { Method: "POST" }
}:
{
return Ok(new StringContent(ListArtifactsResponse));
}

// Download File
case
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,43 @@ internal record GitHubActionsArtifactService(
private static readonly Uri CreateArtifactUrl = new Uri("CreateArtifact", UriKind.Relative);
private static readonly Uri FinalizeArtifactUrl = new Uri("FinalizeArtifact", UriKind.Relative);
private static readonly Uri GetSignedArtifactURLUrl = new Uri("GetSignedArtifactURL", UriKind.Relative);
private static readonly Uri ListArtifactsUrl = new Uri("ListArtifacts", UriKind.Relative);

internal async Task DownloadArtifactFiles(
string artifactName,
DirectoryPath directoryPath)
{
var listArtifactsResponse = await ListArtifacts(
artifactName);

if (listArtifactsResponse.Artifacts.FirstOrDefault(artifact => artifact.Name == artifactName)
is { WorkflowRunBackendId.Length: > 0 } and { WorkflowJobRunBackendId.Length: > 0 } artifact)
{
var signedArtifactURLResponse = await GetSignedArtifactURL(artifact.WorkflowRunBackendId, artifact.WorkflowJobRunBackendId, artifactName);

await DownloadArtifact(signedArtifactURLResponse.SignedUrl, directoryPath);
}
else
{
throw new CakeException($"Artifact {artifactName} not found.");
}
}

private async Task<ListArtifactsResponse> ListArtifacts(
string nameFilter = null,
long? idFilter = null)
{
GetWorkflowBackendIds(out var workflowRunBackendId, out var workflowJobRunBackendId);

var (_, signedUrl) = await GetSignedArtifactURL(workflowRunBackendId, workflowJobRunBackendId, artifactName);
var listArtifactsRequest = new ListArtifactsRequest(
workflowRunBackendId,
workflowJobRunBackendId,
nameFilter,
idFilter);

await DownloadArtifact(signedUrl, directoryPath);
return await PostArtifactService<ListArtifactsRequest, ListArtifactsResponse>(
ListArtifactsUrl,
listArtifactsRequest);
}

private async Task DownloadArtifact(string signedUrl, DirectoryPath directoryPath)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Text.Json.Serialization;

namespace Cake.Common.Build.GitHubActions.Commands.Artifact
{
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
internal record ListArtifactsRequest(
[property: JsonPropertyName("workflow_run_backend_id")]
string WorkflowRunBackendId,
[property: JsonPropertyName("workflow_job_run_backend_id")]
string WorkflowJobRunBackendId,
[property: JsonPropertyName("name_filter")]
string NameFilter = null,
[property: JsonPropertyName("id_filter")]
long? IdFilter = null);
#pragma warning restore SA1313 // Parameter names should begin with lower-case letter
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Text.Json.Serialization;

namespace Cake.Common.Build.GitHubActions.Commands.Artifact
{
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
internal record ListArtifactsResponse(
[property: JsonPropertyName("artifacts")]
ListArtifactsResponse.MonolithArtifact[] Artifacts)
{
internal record MonolithArtifact(
[property: JsonPropertyName("workflow_run_backend_id")]
string WorkflowRunBackendId,
[property: JsonPropertyName("workflow_job_run_backend_id")]
string WorkflowJobRunBackendId,
[property: JsonPropertyName("database_id")]
string DatabaseId,
[property: JsonPropertyName("name")]
string Name,
[property: JsonPropertyName("created_at")]
DateTimeOffset CreatedAt);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,21 @@ Task("Cake.Common.Build.GitHubActionsProvider.Commands.DownloadArtifact")
Assert.True(FileHashEquals(data.AssemblyPath, targetArtifactPath), $"{data.AssemblyPath.FullPath}=={targetArtifactPath.FullPath}");
});

Task("Cake.Common.Build.GitHubActionsProvider.Commands.DownloadArtifact.PreviousJob")
.Does(async () => {
// Given
var targetPath = Paths.Temp.Combine("./Cake.Common.Build.GitHubActionsProvider.Commands.DownloadArtifact.PreviousJob");
EnsureDirectoryExists(targetPath);
var targetArtifactPath = targetPath.CombineWithFilePath("cake-integration-tests.txt");
// When
await GitHubActions.Commands.DownloadArtifact("cake-integration-tests", targetPath);
// Then
Assert.True(System.IO.File.Exists(targetArtifactPath.FullPath), $"{targetArtifactPath.FullPath} Missing");
Assert.Equal("Cake Integration Tests\n", System.IO.File.ReadAllText(targetArtifactPath.FullPath));
});

Task("Cake.Common.Build.GitHubActionsProvider.Environment.Runner.Architecture")
.Does(() => {
// Given / When
Expand Down Expand Up @@ -193,7 +208,8 @@ if (GitHubActions.Environment.Runtime.IsRuntimeAvailable)
gitHubActionsProviderTask
.IsDependentOn("Cake.Common.Build.GitHubActionsProvider.Commands.UploadArtifact.File")
.IsDependentOn("Cake.Common.Build.GitHubActionsProvider.Commands.UploadArtifact.Directory")
.IsDependentOn("Cake.Common.Build.GitHubActionsProvider.Commands.DownloadArtifact");
.IsDependentOn("Cake.Common.Build.GitHubActionsProvider.Commands.DownloadArtifact")
.IsDependentOn("Cake.Common.Build.GitHubActionsProvider.Commands.DownloadArtifact.PreviousJob");
}

public class GitHubActionsData
Expand Down

0 comments on commit f730597

Please sign in to comment.