#!/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.WriteLine(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; 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); } Console.WriteLine($"Retrieving workflow runs for repository: {repoOwner}/{repoName} with pattern: {workflowPattern}"); 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)) { Console.WriteLine("Failed to retrieve artifacts."); return; } if (artifactsResponse.Artifacts.Count == 0) { Console.WriteLine("No artifacts found for the specified workflow run."); 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); } }