From 9f43626e2507b2a26548dbba0a8f2f3424ad098a Mon Sep 17 00:00:00 2001 From: Scion Date: Thu, 10 Jul 2025 13:08:06 -0700 Subject: [PATCH] Updated download previous artifacts script. --- .gitignore | 1 + .../download-previous-artifacts/action.yaml | 23 +-- .../appsettings.json | 9 ++ .../download-previous-artifacts.cs | 151 ++++++++++++++++++ .../download-previous-artifacts.cs | 86 ---------- .../download-previous-artifacts.js | 67 -------- 6 files changed, 173 insertions(+), 164 deletions(-) create mode 100644 .gitignore rename {github => gitea}/download-previous-artifacts/action.yaml (72%) create mode 100644 gitea/download-previous-artifacts/appsettings.json create mode 100644 gitea/download-previous-artifacts/download-previous-artifacts.cs delete mode 100644 github/download-previous-artifacts/download-previous-artifacts.cs delete mode 100644 github/download-previous-artifacts/download-previous-artifacts.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dda3f58 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.local.json diff --git a/github/download-previous-artifacts/action.yaml b/gitea/download-previous-artifacts/action.yaml similarity index 72% rename from github/download-previous-artifacts/action.yaml rename to gitea/download-previous-artifacts/action.yaml index 3a39f0d..69b79e6 100644 --- a/github/download-previous-artifacts/action.yaml +++ b/gitea/download-previous-artifacts/action.yaml @@ -8,20 +8,21 @@ inputs: username: description: "Gitea user to query with." required: true - default: "${{ github.repository_owner }}" + default: "${{ github.actor }}" password: description: "Credentials to use for Gitea. If not specified, the GitHub/Gitea token will be used." required: true default: "${{ github.token }}" - accessToken: - description: "Access token for access to other repositories." + repoFullName: + description: "The full name of the repository that ran the workflow to download from. Default: ${{ github.repository }}" required: true - default: "" + default: "${{ github.repository }}" 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 + default: "${{ github.event.repository.workflow }}" artifactPattern: - description: "Pattern of the artifacts to match." + description: "Pattern of the artifacts to match. Supports wildcard or RegEx. Default: *" required: true default: "*" artifactName: @@ -32,10 +33,14 @@ inputs: description: "Directory to unzip the artifacts into. If not specified, the artifacts will not be unzipped." required: false default: "" + deleteAfterUnzip: + description: "Whether to delete the artifacts after unzipping. Default: false" + required: false + default: "false" nugetSources: description: "List of additional NuGet sources to use." required: false - default: "https://gitea.studiowhy.net/api/packages/FORK/nuget/index.json" + default: "${{ github.api_url }}/packages/FORK/nuget/index.json" nugetUsernames: description: "List of additional NuGet usernames to use." required: false @@ -57,10 +62,6 @@ runs: - name: "Download artifacts." uses: act/common/dotnet/dotnet-10@master env: - WORKFLOW_FILENAME: ${{ inputs.workflowPattern }} - ARTIFACT_NAME: ${{ inputs.filePattern }} - ARTIFACT_FILENAME: ${{ inputs.artifactName }} - UNZIP_DIR: ${{ inputs.unzipDir }} INPUTS: ${{ toJSON(inputs) }} with: command: run "${{ github.action_path }}/download-previous-artifacts.cs" diff --git a/gitea/download-previous-artifacts/appsettings.json b/gitea/download-previous-artifacts/appsettings.json new file mode 100644 index 0000000..c569e79 --- /dev/null +++ b/gitea/download-previous-artifacts/appsettings.json @@ -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": "" +} \ No newline at end of file diff --git a/gitea/download-previous-artifacts/download-previous-artifacts.cs b/gitea/download-previous-artifacts/download-previous-artifacts.cs new file mode 100644 index 0000000..aaee96a --- /dev/null +++ b/gitea/download-previous-artifacts/download-previous-artifacts.cs @@ -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(); + +ServiceProvider serviceProvider = services.BuildServiceProvider(); +RepositoryApi repositoryApi = serviceProvider.GetRequiredService(); + +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 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); + } +} diff --git a/github/download-previous-artifacts/download-previous-artifacts.cs b/github/download-previous-artifacts/download-previous-artifacts.cs deleted file mode 100644 index d298b32..0000000 --- a/github/download-previous-artifacts/download-previous-artifacts.cs +++ /dev/null @@ -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(); -services.AddSingleton(); - -ServiceProvider serviceProvider = services.BuildServiceProvider(); -OrganizationApi organizationApi = serviceProvider.GetRequiredService(); -RepositoryApi repositoryApi = serviceProvider.GetRequiredService(); - -IOrgGetAllApiResponse response = await organizationApi.OrgGetAllAsync(); - -if (response.TryOk(out List 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}"); - } -} diff --git a/github/download-previous-artifacts/download-previous-artifacts.js b/github/download-previous-artifacts/download-previous-artifacts.js deleted file mode 100644 index a8645ec..0000000 --- a/github/download-previous-artifacts/download-previous-artifacts.js +++ /dev/null @@ -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."); - } -}