[feat]: SDKs for ActionsArtifacts APIs

This commit is contained in:
Tom Longhurst
2023-09-25 20:16:41 +01:00
committed by GitHub
parent 03e7644f5e
commit 958bc5f1f8
20 changed files with 621 additions and 30 deletions

View File

@@ -28,9 +28,7 @@ namespace Octokit.Caching
ContentType = contentType;
}
/// <summary>
/// Raw response body. Typically a string, but when requesting images, it will be a byte array.
/// </summary>
/// <inheritdoc/>
public object Body { get; private set; }
/// <summary>
/// Information about the API.

View File

@@ -1,4 +1,7 @@
namespace Octokit
using System.IO;
using System.Threading.Tasks;
namespace Octokit
{
/// <summary>
/// A client for GitHub's Actions Artifacts API.
@@ -15,5 +18,60 @@
public ActionsArtifactsClient(IApiConnection apiConnection) : base(apiConnection)
{
}
/// <inheritdoc/>
[ManualRoute("GET", "/repos/{owner}/{repository}/actions/artifacts")]
public Task<ListArtifactsResponse> ListArtifacts(string owner, string repository, ListArtifactsRequest listArtifactsRequest = null)
{
Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner));
Ensure.ArgumentNotNullOrEmptyString(repository, nameof(repository));
return ApiConnection.Get<ListArtifactsResponse>(ApiUrls.ListArtifacts(owner, repository), listArtifactsRequest?.ToParametersDictionary());
}
/// <inheritdoc/>
[ManualRoute("GET", "/repos/{owner}/{repository}/actions/artifacts/{artifact_id}")]
public Task<Artifact> GetArtifact(string owner, string repository, long artifactId)
{
Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner));
Ensure.ArgumentNotNullOrEmptyString(repository, nameof(repository));
Ensure.ArgumentNotNullOrDefault(artifactId, nameof(artifactId));
return ApiConnection.Get<Artifact>(ApiUrls.Artifact(owner, repository, artifactId), null);
}
/// <inheritdoc/>
[ManualRoute("DELETE", "/repos/{owner}/{repository}/actions/artifacts/{artifact_id}")]
public Task DeleteArtifact(string owner, string repository, long artifactId)
{
Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner));
Ensure.ArgumentNotNullOrEmptyString(repository, nameof(repository));
Ensure.ArgumentNotNullOrDefault(artifactId, nameof(artifactId));
return ApiConnection.Delete(ApiUrls.Artifact(owner, repository, artifactId), null);
}
/// <inheritdoc/>
[ManualRoute("GET", "/repos/{owner}/{repository}/actions/artifacts/{artifact_id}/{archive_format}")]
public Task<Stream> DownloadArtifact(string owner, string repository, long artifactId, string archiveFormat)
{
Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner));
Ensure.ArgumentNotNullOrEmptyString(repository, nameof(repository));
Ensure.ArgumentNotNullOrDefault(artifactId, nameof(artifactId));
Ensure.ArgumentNotNullOrEmptyString(repository, nameof(archiveFormat));
return ApiConnection.GetRawStream(ApiUrls.DownloadArtifact(owner, repository, artifactId, archiveFormat), null);
}
/// <inheritdoc/>
[ManualRoute("GET", "/repos/{owner}/{repo}/actions/runs/{run_id}/artifacts")]
public Task<ListArtifactsResponse> ListWorkflowArtifacts(string owner, string repository, long runId, ListArtifactsRequest listArtifactsRequest = null)
{
Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner));
Ensure.ArgumentNotNullOrEmptyString(repository, nameof(repository));
Ensure.ArgumentNotNullOrDefault(runId, nameof(runId));
return ApiConnection.Get<ListArtifactsResponse>(ApiUrls.ListWorkflowArtifacts(owner, repository, runId), listArtifactsRequest?.ToParametersDictionary());
}
}
}

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
namespace Octokit
@@ -11,5 +11,49 @@ namespace Octokit
/// </remarks>
public interface IActionsArtifactsClient
{
/// <summary>
/// Lists artifacts for a repository
/// </summary>
/// <param name="owner"></param>
/// <param name="repository"></param>
/// <returns></returns>
Task<ListArtifactsResponse> ListArtifacts(string owner, string repository, ListArtifactsRequest listArtifactsRequest = null);
/// <summary>
/// Gets the specified artifact
/// </summary>
/// <param name="owner"></param>
/// <param name="repository"></param>
/// <param name="artifactId"></param>
/// <returns></returns>
Task<Artifact> GetArtifact(string owner, string repository, long artifactId);
/// <summary>
/// Deletes the specified artifact
/// </summary>
/// <param name="owner"></param>
/// <param name="repository"></param>
/// <param name="artifactId"></param>
/// <returns></returns>
Task DeleteArtifact(string owner, string repository, long artifactId);
/// <summary>
/// Downloads the specified artifact's contents
/// </summary>
/// <param name="owner"></param>
/// <param name="repository"></param>
/// <param name="artifactId"></param>
/// <param name="archiveFormat"></param>
/// <returns></returns>
Task<Stream> DownloadArtifact(string owner, string repository, long artifactId, string archiveFormat);
/// <summary>
/// Lists the artifacts for a specific workflow run
/// </summary>
/// <param name="owner"></param>
/// <param name="repository"></param>
/// <param name="runId"></param>
/// <returns></returns>
Task<ListArtifactsResponse> ListWorkflowArtifacts(string owner, string repository, long runId, ListArtifactsRequest listArtifactsRequest = null);
}
}

View File

@@ -5472,5 +5472,53 @@ namespace Octokit
{
return "user/codespaces/{0}/stop".FormatUri(codespaceName);
}
/// <summary>
/// Returns the <see cref="Uri"/> that lists the artifacts for a repository.
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="repository">The name of the repository</param>
/// <returns></returns>
public static Uri ListArtifacts(string owner, string repository)
{
return "repos/{0}/{1}/actions/artifacts".FormatUri(owner, repository);
}
/// <summary>
/// Returns the <see cref="Uri"/> for the specified artifact.
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="repository">The name of the repository</param>
/// <param name="artifactId">The id of the artifact</param>
/// <returns></returns>
public static Uri Artifact(string owner, string repository, long artifactId)
{
return "repos/{0}/{1}/actions/artifacts/{2}".FormatUri(owner, repository, artifactId);
}
/// <summary>
/// Returns the <see cref="Uri"/> to download the specified artifact.
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="repository">The name of the repository</param>
/// <param name="artifactId">The id of the artifact</param>
/// <param name="archiveFormat">The archive format e.g. zip</param>
/// <returns></returns>
public static Uri DownloadArtifact(string owner, string repository, long artifactId, string archiveFormat)
{
return "repos/{0}/{1}/actions/artifacts/{2}/{3}".FormatUri(owner, repository, artifactId, archiveFormat);
}
/// <summary>
/// Returns the <see cref="Uri"/> to list the artifacts for a workflow.
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="repository">The name of the repository</param>
/// <param name="runId">The id of the workflow run</param>
/// <returns></returns>
public static Uri ListWorkflowArtifacts(string owner, string repository, long runId)
{
return "repos/{0}/{1}/actions/runs/{2}/artifacts".FormatUri(owner, repository, runId);
}
}
}

View File

@@ -21,6 +21,18 @@ namespace Octokit
throw new ArgumentNullException(name);
}
/// <summary>
/// Checks an argument to ensure it isn't null or the default value.
/// </summary>
/// <param name = "value">The argument value to check</param>
/// <param name = "name">The name of the argument</param>
public static void ArgumentNotNullOrDefault<T>([ValidatedNotNull]T value, string name)
{
if (value != null && !EqualityComparer<T>.Default.Equals(value, default)) return;
throw new ArgumentNullException(name);
}
/// <summary>
/// Checks a string argument to ensure it isn't null or empty.

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
@@ -138,6 +139,15 @@ namespace Octokit
var response = await Connection.GetRaw(uri, parameters).ConfigureAwait(false);
return response.Body;
}
/// <inheritdoc/>
public async Task<Stream> GetRawStream(Uri uri, IDictionary<string, string> parameters)
{
Ensure.ArgumentNotNull(uri, nameof(uri));
var response = await Connection.GetRawStream(uri, parameters).ConfigureAwait(false);
return response.Body;
}
/// <summary>
/// Gets all API resources in the list at the specified URI.

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
@@ -241,6 +242,19 @@ namespace Octokit
Endpoint = uri.ApplyParameters(parameters)
});
}
/// <inheritdoc/>
public Task<IApiResponse<Stream>> GetRawStream(Uri uri, IDictionary<string, string> parameters)
{
Ensure.ArgumentNotNull(uri, nameof(uri));
return GetRawStream(new Request
{
Method = HttpMethod.Get,
BaseAddress = BaseAddress,
Endpoint = uri.ApplyParameters(parameters)
});
}
public Task<IApiResponse<T>> Patch<T>(Uri uri, object body)
{
@@ -686,7 +700,30 @@ namespace Octokit
{
request.Headers.Add("Accept", AcceptHeaders.RawContentMediaType);
var response = await RunRequest(request, CancellationToken.None).ConfigureAwait(false);
return new ApiResponse<byte[]>(response, response.Body as byte[]);
return new ApiResponse<byte[]>(response, await StreamToByteArray(response.Body as Stream));
}
async Task<IApiResponse<Stream>> GetRawStream(IRequest request)
{
request.Headers.Add("Accept", AcceptHeaders.RawContentMediaType);
var response = await RunRequest(request, CancellationToken.None).ConfigureAwait(false);
return new ApiResponse<Stream>(response, response.Body as Stream);
}
async Task<byte[]> StreamToByteArray(Stream stream)
{
if (stream is MemoryStream memoryStream)
{
return memoryStream.ToArray();
}
using (var ms = new MemoryStream())
{
await stream.CopyToAsync(ms);
return ms.ToArray();
}
}
async Task<IApiResponse<T>> Run<T>(IRequest request, CancellationToken cancellationToken, Func<object, object> preprocessResponseBody = null)

View File

@@ -83,25 +83,24 @@ namespace Octokit.Internal
"application/x-gzip" ,
"application/octet-stream"};
using (var content = responseMessage.Content)
var content = responseMessage.Content;
if (content != null)
{
if (content != null)
{
contentType = GetContentMediaType(responseMessage.Content);
contentType = GetContentMediaType(content);
if (contentType != null && (contentType.StartsWith("image/") || binaryContentTypes
if (contentType != null && (contentType.StartsWith("image/") || binaryContentTypes
.Any(item => item.Equals(contentType, StringComparison.OrdinalIgnoreCase))))
{
responseBody = await responseMessage.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
}
else
{
responseBody = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
}
if (!(preprocessResponseBody is null))
responseBody = preprocessResponseBody(responseBody);
{
responseBody = await content.ReadAsStreamAsync().ConfigureAwait(false);
}
else
{
responseBody = await content.ReadAsStringAsync().ConfigureAwait(false);
content.Dispose();
}
if (!(preprocessResponseBody is null))
responseBody = preprocessResponseBody(responseBody);
}
var responseHeaders = responseMessage.Headers.ToDictionary(h => h.Key, h => h.Value.First());

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
@@ -71,6 +72,15 @@ namespace Octokit
/// <exception cref="ApiException">Thrown when an API error occurs.</exception>
Task<byte[]> GetRaw(Uri uri, IDictionary<string, string> parameters);
/// <summary>
/// Gets the raw stream of the API resource at the specified URI.
/// </summary>
/// <param name="uri">URI of the API resource to get</param>
/// <param name="parameters">Parameters to add to the API request</param>
/// <returns>The API resource's raw stream or <c>null</c> if the <paramref name="uri"/> points to a directory.</returns>
/// <exception cref="ApiException">Thrown when an API error occurs.</exception>
Task<Stream> GetRawStream(Uri uri, IDictionary<string, string> parameters);
/// <summary>
/// Gets all API resources in the list at the specified URI.
/// </summary>

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
@@ -29,6 +30,15 @@ namespace Octokit
/// <returns><seealso cref="IResponse"/> representing the received HTTP response</returns>
/// <remarks>The <see cref="IResponse.Body"/> property will be <c>null</c> if the <paramref name="uri"/> points to a directory instead of a file</remarks>
Task<IApiResponse<byte[]>> GetRaw(Uri uri, IDictionary<string, string> parameters);
/// <summary>
/// Performs an asynchronous HTTP GET request that expects a <seealso cref="IResponse"/> containing raw data.
/// </summary>
/// <param name="uri">URI endpoint to send request to</param>
/// <param name="parameters">Querystring parameters for the request</param>
/// <returns><seealso cref="IResponse"/> representing the received HTTP response</returns>
/// <remarks>The <see cref="IResponse.Body"/> property will be <c>null</c> if the <paramref name="uri"/> points to a directory instead of a file</remarks>
Task<IApiResponse<Stream>> GetRawStream(Uri uri, IDictionary<string, string> parameters);
/// <summary>
/// Performs an asynchronous HTTP GET request.

View File

@@ -25,7 +25,7 @@ namespace Octokit
public interface IResponse
{
/// <summary>
/// Raw response body. Typically a string, but when requesting images, it will be a byte array.
/// Raw response body. Typically a string, but when requesting images, or binary files, it will be a Stream.
/// </summary>
object Body { get; }

View File

@@ -35,9 +35,7 @@ namespace Octokit.Internal
ContentType = contentType;
}
/// <summary>
/// Raw response body. Typically a string, but when requesting images, it will be a byte array.
/// </summary>
/// <inheritdoc />
public object Body { get; private set; }
/// <summary>
/// Information about the API.

View File

@@ -0,0 +1,32 @@
using System.Diagnostics;
using System.Globalization;
using Octokit.Internal;
namespace Octokit
{
/// <summary>
/// Used to filter requests for lists of artifacts.
/// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class ListArtifactsRequest : RequestParameters
{
/// <summary>
/// Filter artifacts by name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// How many results to return per page (maximum 100).
/// </summary>
[Parameter(Key = "per_page")]
public int PerPage { get; set; } = 30;
/// <summary>
/// What page to retrieve.
/// </summary>
[Parameter(Key = "page")]
public int Page { get; set; } = 1;
internal string DebuggerDisplay => string.Format(CultureInfo.InvariantCulture, "Page: {0}, PerPage: {1} ", Page, PerPage);
}
}

View File

@@ -0,0 +1,83 @@
using System;
using System.Diagnostics;
using System.Globalization;
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class Artifact
{
public Artifact()
{
}
public Artifact(long id, string nodeId, string name, int sizeInBytes, string url, string archiveDownloadUrl, bool expired, DateTime createdAt, DateTime expiresAt, DateTime updatedAt, ArtifactWorkflowRun workflowRun)
{
Id = id;
NodeId = nodeId;
Name = name;
SizeInBytes = sizeInBytes;
Url = url;
ArchiveDownloadUrl = archiveDownloadUrl;
Expired = expired;
CreatedAt = createdAt;
ExpiresAt = expiresAt;
UpdatedAt = updatedAt;
WorkflowRun = workflowRun;
}
/// <summary>
/// The artifact Id
/// </summary>
public long Id { get; private set; }
/// <summary>
/// The artifact node Id
/// </summary>
public string NodeId { get; private set; }
/// <summary>
/// The name of the artifact
/// </summary>
public string Name { get; private set; }
/// <summary>
/// The size of the artifact in bytes
/// </summary>
public int SizeInBytes { get; private set; }
/// <summary>
/// The url for retrieving the artifact information
/// </summary>
public string Url { get; private set; }
/// <summary>
/// The url for downloading the artifact contents
/// </summary>
public string ArchiveDownloadUrl { get; private set; }
/// <summary>
/// True if the artifact has expired
/// </summary>
public bool Expired { get; private set; }
/// <summary>
/// The date and time when the artifact was created
/// </summary>
public DateTime CreatedAt { get; private set; }
/// <summary>
/// The date and time when the artifact expires
/// </summary>
public DateTime ExpiresAt { get; private set; }
/// <summary>
/// The date and time when the artifact was last updated
/// </summary>
public DateTime UpdatedAt { get; private set; }
/// <summary>
/// The workflow from where the artifact was created
/// </summary>
public ArtifactWorkflowRun WorkflowRun { get; private set; }
internal string DebuggerDisplay => string.Format(CultureInfo.InvariantCulture, "Id: {0}", Id);
}

View File

@@ -0,0 +1,46 @@
using System.Diagnostics;
using System.Globalization;
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class ArtifactWorkflowRun
{
public ArtifactWorkflowRun()
{
}
public ArtifactWorkflowRun(long id, long repositoryId, long headRepositoryId, string headBranch, string headSha)
{
Id = id;
RepositoryId = repositoryId;
HeadRepositoryId = headRepositoryId;
HeadBranch = headBranch;
HeadSha = headSha;
}
/// <summary>
/// The workflow run Id
/// </summary>
public long Id { get; private set; }
/// <summary>
/// The repository Id
/// </summary>
public long RepositoryId { get; private set; }
/// <summary>
/// The head repository Id
/// </summary>
public long HeadRepositoryId { get; private set; }
/// <summary>
/// The head branch
/// </summary>
public string HeadBranch { get; private set; }
/// <summary>
/// The head Sha
/// </summary>
public string HeadSha { get; private set; }
internal string DebuggerDisplay => string.Format(CultureInfo.InvariantCulture, "Id: {0}", Id);
}

View File

@@ -0,0 +1,29 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class ListArtifactsResponse
{
public ListArtifactsResponse()
{
}
public ListArtifactsResponse(int totalCount, IReadOnlyList<Artifact> artifacts)
{
TotalCount = totalCount;
Artifacts = artifacts;
}
/// <summary>
/// The number of artifacts found
/// </summary>
public int TotalCount { get; private set; }
/// <summary>
/// The list of found artifacts
/// </summary>
public IReadOnlyList<Artifact> Artifacts { get; private set; } = new List<Artifact>();
internal string DebuggerDisplay => string.Format(CultureInfo.InvariantCulture, "Artifacts: {0}", TotalCount);
}