Updated download previous artifacts script.
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.local.json
|
||||||
@@ -8,20 +8,21 @@ inputs:
|
|||||||
username:
|
username:
|
||||||
description: "Gitea user to query with."
|
description: "Gitea user to query with."
|
||||||
required: true
|
required: true
|
||||||
default: "${{ github.repository_owner }}"
|
default: "${{ github.actor }}"
|
||||||
password:
|
password:
|
||||||
description: "Credentials to use for Gitea. If not specified, the GitHub/Gitea token will be used."
|
description: "Credentials to use for Gitea. If not specified, the GitHub/Gitea token will be used."
|
||||||
required: true
|
required: true
|
||||||
default: "${{ github.token }}"
|
default: "${{ github.token }}"
|
||||||
accessToken:
|
repoFullName:
|
||||||
description: "Access token for access to other repositories."
|
description: "The full name of the repository that ran the workflow to download from. Default: ${{ github.repository }}"
|
||||||
required: true
|
required: true
|
||||||
default: ""
|
default: "${{ github.repository }}"
|
||||||
workflowPattern:
|
workflowPattern:
|
||||||
description: "Pattern of the workflow name to match."
|
description: "Pattern of the workflow name to match. Supports wildcard or RegEx. Default: ${{ github.event.repository.workflow }}"
|
||||||
required: true
|
required: true
|
||||||
|
default: "${{ github.event.repository.workflow }}"
|
||||||
artifactPattern:
|
artifactPattern:
|
||||||
description: "Pattern of the artifacts to match."
|
description: "Pattern of the artifacts to match. Supports wildcard or RegEx. Default: *"
|
||||||
required: true
|
required: true
|
||||||
default: "*"
|
default: "*"
|
||||||
artifactName:
|
artifactName:
|
||||||
@@ -32,10 +33,14 @@ inputs:
|
|||||||
description: "Directory to unzip the artifacts into. If not specified, the artifacts will not be unzipped."
|
description: "Directory to unzip the artifacts into. If not specified, the artifacts will not be unzipped."
|
||||||
required: false
|
required: false
|
||||||
default: ""
|
default: ""
|
||||||
|
deleteAfterUnzip:
|
||||||
|
description: "Whether to delete the artifacts after unzipping. Default: false"
|
||||||
|
required: false
|
||||||
|
default: "false"
|
||||||
nugetSources:
|
nugetSources:
|
||||||
description: "List of additional NuGet sources to use."
|
description: "List of additional NuGet sources to use."
|
||||||
required: false
|
required: false
|
||||||
default: "https://gitea.studiowhy.net/api/packages/FORK/nuget/index.json"
|
default: "${{ github.api_url }}/packages/FORK/nuget/index.json"
|
||||||
nugetUsernames:
|
nugetUsernames:
|
||||||
description: "List of additional NuGet usernames to use."
|
description: "List of additional NuGet usernames to use."
|
||||||
required: false
|
required: false
|
||||||
@@ -57,10 +62,6 @@ runs:
|
|||||||
- name: "Download artifacts."
|
- name: "Download artifacts."
|
||||||
uses: act/common/dotnet/dotnet-10@master
|
uses: act/common/dotnet/dotnet-10@master
|
||||||
env:
|
env:
|
||||||
WORKFLOW_FILENAME: ${{ inputs.workflowPattern }}
|
|
||||||
ARTIFACT_NAME: ${{ inputs.filePattern }}
|
|
||||||
ARTIFACT_FILENAME: ${{ inputs.artifactName }}
|
|
||||||
UNZIP_DIR: ${{ inputs.unzipDir }}
|
|
||||||
INPUTS: ${{ toJSON(inputs) }}
|
INPUTS: ${{ toJSON(inputs) }}
|
||||||
with:
|
with:
|
||||||
command: run "${{ github.action_path }}/download-previous-artifacts.cs"
|
command: run "${{ github.action_path }}/download-previous-artifacts.cs"
|
||||||
9
gitea/download-previous-artifacts/appsettings.json
Normal file
9
gitea/download-previous-artifacts/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"host": "https://gitea.studiowhy.net",
|
||||||
|
"username": "your-username",
|
||||||
|
"password": "your-password",
|
||||||
|
"repoFullName": "owner/repo",
|
||||||
|
"workflowPattern": "deployments/itchio.yaml",
|
||||||
|
"artifactPattern": "*Windows",
|
||||||
|
"unzipDir": ""
|
||||||
|
}
|
||||||
151
gitea/download-previous-artifacts/download-previous-artifacts.cs
Normal file
151
gitea/download-previous-artifacts/download-previous-artifacts.cs
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
#!/usr/bin/env -S dotnet run
|
||||||
|
|
||||||
|
#:package StudioWhy.Gitea.Net.API@1.25.0.2
|
||||||
|
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Gitea.Net.Api;
|
||||||
|
using Gitea.Net.Client;
|
||||||
|
using Gitea.Net.Extensions;
|
||||||
|
using Gitea.Net.Model;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
string jsonInput = Environment.GetEnvironmentVariable("INPUTS") ?? "{}";
|
||||||
|
const string name = "Download Previous Artifacts";
|
||||||
|
Console.WriteLine($"::group::{name} - Inputs");
|
||||||
|
Console.Write(jsonInput);
|
||||||
|
Console.WriteLine("::endgroup::");
|
||||||
|
|
||||||
|
byte[] jsonBytes = Encoding.UTF8.GetBytes(jsonInput);
|
||||||
|
using MemoryStream memoryStream = new(jsonBytes);
|
||||||
|
|
||||||
|
string currentDir = Directory.GetCurrentDirectory();
|
||||||
|
IConfiguration configuration = new ConfigurationBuilder()
|
||||||
|
.SetBasePath(currentDir)
|
||||||
|
.AddJsonFile("appsettings.local.json", optional: true)
|
||||||
|
.AddJsonStream(memoryStream)
|
||||||
|
.AddEnvironmentVariables()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
string host = configuration["host"] ?? string.Empty;
|
||||||
|
string username = configuration["username"] ?? string.Empty;
|
||||||
|
string password = configuration["password"] ?? string.Empty;
|
||||||
|
|
||||||
|
string repoFullName = configuration["repoFullName"] ?? string.Empty;
|
||||||
|
|
||||||
|
foreach (string key in configuration.AsEnumerable().Select(kvp => kvp.Key))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"::set-output name={key}::{configuration[key]}");
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] repoParts = repoFullName.Split('/');
|
||||||
|
if (repoParts.Length != 2)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid repository full name format. Expected 'owner/repo'.");
|
||||||
|
}
|
||||||
|
string repoOwner = repoParts[0];
|
||||||
|
string repoName = repoParts[1];
|
||||||
|
|
||||||
|
string workflowPattern = configuration["workflowPattern"] ?? string.Empty;
|
||||||
|
Regex workflowRegex = WildcardToRegex(workflowPattern);
|
||||||
|
|
||||||
|
string artifactPattern = configuration["artifactPattern"] ?? "*";
|
||||||
|
Regex artifactRegex = WildcardToRegex(artifactPattern);
|
||||||
|
|
||||||
|
string unzipDir = configuration["unzipDir"] ?? string.Empty;
|
||||||
|
bool.TryParse(configuration["deleteAfterUnzip"], out bool deleteAfterUnzip);
|
||||||
|
|
||||||
|
// Configure services
|
||||||
|
ServiceCollection services = new();
|
||||||
|
services.AddApi(o =>
|
||||||
|
{
|
||||||
|
BasicToken basicToken = new(username, password);
|
||||||
|
o.AddTokens(basicToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
UriBuilder hostUriBuilder = new(host);
|
||||||
|
hostUriBuilder.Path = "/api/v1";
|
||||||
|
|
||||||
|
HttpClientHandler httpClientHandler = new()
|
||||||
|
{
|
||||||
|
AllowAutoRedirect = false,
|
||||||
|
};
|
||||||
|
using HttpClient httpClient = new(httpClientHandler, true)
|
||||||
|
{
|
||||||
|
BaseAddress = hostUriBuilder.Uri,
|
||||||
|
};
|
||||||
|
|
||||||
|
services.AddLogging(configure => configure.AddConsole());
|
||||||
|
services.AddSingleton(httpClient);
|
||||||
|
services.AddSingleton<RepositoryApi>();
|
||||||
|
|
||||||
|
ServiceProvider serviceProvider = services.BuildServiceProvider();
|
||||||
|
RepositoryApi repositoryApi = serviceProvider.GetRequiredService<RepositoryApi>();
|
||||||
|
|
||||||
|
static Regex WildcardToRegex(string pattern, RegexOptions options = RegexOptions.IgnoreCase)
|
||||||
|
{
|
||||||
|
string regexString = "^" + Regex.Escape(pattern)
|
||||||
|
.Replace(@"\*", ".*")
|
||||||
|
.Replace(@"\?", ".") + "$";
|
||||||
|
return new(regexString, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
IGetWorkflowRunsApiResponse getWorkflowRunsApiResponse = await repositoryApi.GetWorkflowRunsOrDefaultAsync(repoOwner, repoName, status: "success", page: 1, cancellationToken: CancellationToken.None);
|
||||||
|
if (!getWorkflowRunsApiResponse.TryOk(out ActionWorkflowRunsResponse workflowRunResponse))
|
||||||
|
{
|
||||||
|
Console.WriteLine("Failed to retrieve workflow runs.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionWorkflowRun? actionWorkflowRun = workflowRunResponse.WorkflowRuns.FirstOrDefault(w => workflowRegex.IsMatch(w.Path.Split('@')[0]));
|
||||||
|
if (actionWorkflowRun is null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("No matching workflow run found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IGetArtifactsOfRunApiResponse artifacts = await repositoryApi.GetArtifactsOfRunOrDefaultAsync(repoOwner, repoName, (int)actionWorkflowRun.Id!, string.Empty, CancellationToken.None);
|
||||||
|
if (!artifacts.TryOk(out ActionArtifactsResponse artifactsResponse) || artifactsResponse.TotalCount is 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Failed to retrieve artifacts.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<ActionArtifact> artifactsToDownload = artifactsResponse.Artifacts.Where(a => artifactRegex.IsMatch(a.Name));
|
||||||
|
|
||||||
|
foreach (ActionArtifact artifact in artifactsToDownload)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Artifact: {artifact.Name}, URL: {artifact.ArchiveDownloadUrl}");
|
||||||
|
|
||||||
|
IDownloadArtifactApiResponse downloadArtifactApiResponse = await repositoryApi.DownloadArtifactAsync(repoOwner, repoName, artifact.Id.ToString(), CancellationToken.None);
|
||||||
|
Uri? signedUrl = downloadArtifactApiResponse.Headers.Location;
|
||||||
|
if (downloadArtifactApiResponse.StatusCode is not HttpStatusCode.Found || signedUrl is null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Failed to redirect to artifact: {artifact.Name}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
using HttpResponseMessage response = await httpClient.GetAsync(signedUrl, HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
string fileName = $"{artifact.Name}.zip";
|
||||||
|
await using FileStream fs = new(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||||
|
await using Stream stream = await response.Content.ReadAsStreamAsync();
|
||||||
|
await stream.CopyToAsync(fs);
|
||||||
|
fs.Close();
|
||||||
|
|
||||||
|
Console.WriteLine($"Downloaded: {fileName}");
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(unzipDir)) continue;
|
||||||
|
|
||||||
|
Console.WriteLine($"Unzipping: {fileName} to {unzipDir}");
|
||||||
|
System.IO.Compression.ZipFile.ExtractToDirectory(fileName, unzipDir, true);
|
||||||
|
if (deleteAfterUnzip)
|
||||||
|
{
|
||||||
|
File.Delete(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
#!/usr/bin/env -S dotnet run
|
|
||||||
|
|
||||||
#:package StudioWhy.Gitea.Net.API@1.24.2.*
|
|
||||||
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
using Gitea.Net.Api;
|
|
||||||
using Gitea.Net.Client;
|
|
||||||
using Gitea.Net.Extensions;
|
|
||||||
using Gitea.Net.Model;
|
|
||||||
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
string jsonInput = Environment.GetEnvironmentVariable("INPUTS") ?? "{}";
|
|
||||||
const string name = "Download Previous Artifacts";
|
|
||||||
Console.WriteLine($"::group::{name} - Inputs");
|
|
||||||
Console.WriteLine(jsonInput);
|
|
||||||
Console.WriteLine("::endgroup::");
|
|
||||||
byte[] jsonBytes = Encoding.UTF8.GetBytes(jsonInput);
|
|
||||||
using MemoryStream memoryStream = new(jsonBytes);
|
|
||||||
|
|
||||||
IConfiguration configuration = new ConfigurationBuilder()
|
|
||||||
.AddJsonStream(memoryStream)
|
|
||||||
.AddEnvironmentVariables()
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
PrintConfiguration(configuration);
|
|
||||||
|
|
||||||
string host = configuration["Host"]!;
|
|
||||||
string username = configuration["Username"]!;
|
|
||||||
string password = configuration["Password"]!;
|
|
||||||
|
|
||||||
ServiceCollection services = new();
|
|
||||||
services.AddApi(o =>
|
|
||||||
{
|
|
||||||
BasicToken basicToken = new(username, password);
|
|
||||||
o.AddTokens(basicToken);
|
|
||||||
});
|
|
||||||
|
|
||||||
UriBuilder hostUriBuilder = new(host);
|
|
||||||
hostUriBuilder.Path = "/api/v1";
|
|
||||||
using HttpClient httpClient = new()
|
|
||||||
{
|
|
||||||
BaseAddress = hostUriBuilder.Uri,
|
|
||||||
};
|
|
||||||
|
|
||||||
services.AddLogging(configure => configure.AddConsole());
|
|
||||||
services.AddSingleton(httpClient);
|
|
||||||
services.AddSingleton<OrganizationApi>();
|
|
||||||
services.AddSingleton<RepositoryApi>();
|
|
||||||
|
|
||||||
ServiceProvider serviceProvider = services.BuildServiceProvider();
|
|
||||||
OrganizationApi organizationApi = serviceProvider.GetRequiredService<OrganizationApi>();
|
|
||||||
RepositoryApi repositoryApi = serviceProvider.GetRequiredService<RepositoryApi>();
|
|
||||||
|
|
||||||
IOrgGetAllApiResponse response = await organizationApi.OrgGetAllAsync();
|
|
||||||
|
|
||||||
if (response.TryOk(out List<Organization> organizations))
|
|
||||||
{
|
|
||||||
foreach (Organization org in organizations)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Organization: {org.Name}");
|
|
||||||
}
|
|
||||||
//Console.WriteLine($"Repository: {repo?.Name}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// string owner = configuration["GITHUB_REPOSITORY_OWNER"]!;
|
|
||||||
// string repoName = configuration["GITHUB_REPOSITORY"]!;
|
|
||||||
// repoName = Path.GetFileName(repoName);
|
|
||||||
|
|
||||||
|
|
||||||
// Repository repo = await repoApi.RepoGetAsync(owner, repoName);
|
|
||||||
|
|
||||||
// Console.WriteLine($"Repository:\n {JsonSerializer.Serialize(repo)}");
|
|
||||||
|
|
||||||
static void PrintConfiguration(IConfiguration configuration)
|
|
||||||
{
|
|
||||||
foreach (var kvp in configuration.AsEnumerable())
|
|
||||||
{
|
|
||||||
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
module.exports = async ({
|
|
||||||
github,
|
|
||||||
context,
|
|
||||||
core
|
|
||||||
}) => {
|
|
||||||
const owner = context.repo.owner;
|
|
||||||
const repo = context.repo.repo;
|
|
||||||
|
|
||||||
// See: https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#list-repository-workflows
|
|
||||||
const workflows = await github.rest.actions.listRepoWorkflows({
|
|
||||||
owner,
|
|
||||||
repo
|
|
||||||
})
|
|
||||||
|
|
||||||
const workflow = workflows.data.workflows.find(w => w.path.includes(process.env.WORKFLOW_FILENAME));
|
|
||||||
|
|
||||||
if (!workflow) {
|
|
||||||
core.setFailed("No matching workflow found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// See: https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2022-11-28#list-workflow-runs-for-a-repository
|
|
||||||
const runs = await github.rest.actions.listWorkflowRuns({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
workflow_id: workflow.id,
|
|
||||||
status: "success",
|
|
||||||
per_page: 1
|
|
||||||
})
|
|
||||||
|
|
||||||
if (runs.data.total_count === 0) {
|
|
||||||
core.setFailed("No workflow runs found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// See: https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#list-workflow-run-artifacts
|
|
||||||
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
run_id: runs.data.workflow_runs[0].id
|
|
||||||
});
|
|
||||||
|
|
||||||
// Other formats are not supported by the GitHub API for downloading artifacts.
|
|
||||||
const extension = "zip"
|
|
||||||
const artifact = artifacts.data.artifacts.find(artifact => artifact.name === process.env.ARTIFACT_NAME);
|
|
||||||
if (artifact) {
|
|
||||||
// See: https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#download-an-artifact
|
|
||||||
const response = await github.rest.actions.downloadArtifact({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
artifact_id: artifact.id,
|
|
||||||
archive_format: extension
|
|
||||||
});
|
|
||||||
|
|
||||||
const artifactFilename = process.env.ARTIFACT_FILENAME || `${artifact.name}.${extension}`;
|
|
||||||
require('fs').writeFileSync(artifactFilename, Buffer.from(response.data));
|
|
||||||
console.log("Artifact downloaded successfully.");
|
|
||||||
const unzip_dir = process.env.UNZIP_DIR;
|
|
||||||
if (unzip_dir) {
|
|
||||||
console.log("Unzipping artifact to directory: %s", unzip_dir);
|
|
||||||
require('child_process').execSync(`unzip -o ${artifactFilename} -d ${process.env.UNZIP_DIR}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
core.setFailed("No artifact found.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user