-
Notifications
You must be signed in to change notification settings - Fork 61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ability to upload to Generic Packages Repository #524
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
using System.Collections.Generic; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using NGitLab.Models; | ||
|
||
namespace NGitLab.Mock.Clients | ||
{ | ||
internal sealed class PackageClient : ClientBase, IPackageClient | ||
{ | ||
public PackageClient(ClientContext context) | ||
: base(context) | ||
{ | ||
} | ||
|
||
public Task<Package> PublishAsync(int projectId, PackagePublish packagePublish, CancellationToken cancellationToken = default) | ||
{ | ||
throw new System.NotImplementedException(); | ||
} | ||
|
||
public IEnumerable<PackageSearchResult> Get(int projectId, PackageQuery packageQuery) | ||
{ | ||
throw new System.NotImplementedException(); | ||
} | ||
|
||
public Task<PackageSearchResult> GetByIdAsync(int projectId, int packageId, CancellationToken cancellationToken = default) | ||
{ | ||
throw new System.NotImplementedException(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
using System.IO; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
using NGitLab.Models; | ||
using NGitLab.Tests.Docker; | ||
using NUnit.Framework; | ||
|
||
namespace NGitLab.Tests | ||
{ | ||
public class PackageTests | ||
{ | ||
[Test] | ||
[NGitLabRetry] | ||
public async Task Test_publish_package() | ||
{ | ||
using var context = await GitLabTestContext.CreateAsync(); | ||
var project = context.CreateProject(); | ||
var packagesClient = context.Client.Packages; | ||
|
||
var packagePublish = new PackagePublish | ||
{ | ||
FileName = "README.md", | ||
PackageName = "Packages", | ||
PackageVersion = "1.0.0", | ||
Status = "default", | ||
PackageStream = File.OpenRead("../../../../README.md"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You'll need to dispose the stream. Also, don't rely on the file system, you can use a |
||
}; | ||
|
||
var newGenericPackage = await packagesClient.PublishAsync(project.Id, packagePublish); | ||
|
||
var packageQuery = new PackageQuery { PackageType = PackageType.generic }; | ||
var genericPackages = packagesClient.Get(project.Id, packageQuery).ToList(); | ||
var singleGenericPackage = await packagesClient.GetByIdAsync(project.Id, newGenericPackage.PackageId); | ||
|
||
Assert.AreEqual(1, genericPackages.Count); | ||
Assert.AreEqual(newGenericPackage.PackageId, genericPackages[0].PackageId); | ||
Assert.AreEqual(singleGenericPackage.PackageId, newGenericPackage.PackageId); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
using System.Collections.Generic; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using NGitLab.Models; | ||
|
||
namespace NGitLab | ||
{ | ||
public interface IPackageClient | ||
{ | ||
/// <summary> | ||
/// Add a package file with the proposed information to the GitLab Generic Package Repository for the selected Project Id. | ||
/// </summary> | ||
/// <param name="packagePublish">The information about the package file to publish.</param> | ||
/// <returns>The package if it was created. Null if not.</returns> | ||
Task<Package> PublishAsync(int projectId, PackagePublish packagePublish, CancellationToken cancellationToken = default); | ||
|
||
/// <summary> | ||
/// Gets all project packages based on the provided query parameters. | ||
/// </summary> | ||
/// <param name="projectId">The project id to search for packages in.</param> | ||
/// <param name="packageQuery">The query parameters to be used for the search.</param> | ||
/// <returns></returns> | ||
IEnumerable<PackageSearchResult> Get(int projectId, PackageQuery packageQuery); | ||
|
||
/// <summary> | ||
/// Gets a single project package using the provided project and package ids. | ||
/// </summary> | ||
/// <param name="projectId">The project id that the package resides in.</param> | ||
/// <param name="packageId">The package id that is being selected.</param> | ||
/// <param name="cancellationToken">The cancellation token used to halt the request.</param> | ||
/// <returns></returns> | ||
Task<PackageSearchResult> GetByIdAsync(int projectId, int packageId, CancellationToken cancellationToken = default); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,72 @@ | ||||||
using System; | ||||||
using System.Collections.Generic; | ||||||
using System.Globalization; | ||||||
using System.Threading; | ||||||
using System.Threading.Tasks; | ||||||
using NGitLab.Models; | ||||||
|
||||||
namespace NGitLab.Impl | ||||||
{ | ||||||
public class PackageClient : IPackageClient | ||||||
{ | ||||||
private const string PublishPackageUrl = "/projects/{0}/packages/generic/{1}/{2}/{3}?status={4}&select=package_file"; | ||||||
private const string GetPackagesUrl = "/projects/{0}/packages"; | ||||||
private const string GetPackageUrl = "/projects/{0}/packages/{1}"; | ||||||
|
||||||
private readonly API _api; | ||||||
|
||||||
public PackageClient(API api) | ||||||
{ | ||||||
_api = api; | ||||||
} | ||||||
|
||||||
public Task<Package> PublishAsync(int projectId, PackagePublish packagePublish, CancellationToken cancellationToken = default) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Based on the url, I think the method name should be
Suggested change
|
||||||
{ | ||||||
var formData = new FileContent(packagePublish.PackageStream); | ||||||
|
||||||
return _api.Put().With(formData).ToAsync<Package>(string.Format(CultureInfo.InvariantCulture, | ||||||
PublishPackageUrl, projectId, Uri.EscapeDataString(packagePublish.PackageName), | ||||||
Uri.EscapeDataString(packagePublish.PackageVersion), Uri.EscapeDataString(packagePublish.FileName), | ||||||
Uri.EscapeDataString(packagePublish.Status)), cancellationToken); | ||||||
} | ||||||
|
||||||
public IEnumerable<PackageSearchResult> Get(int projectId, PackageQuery packageQuery) | ||||||
{ | ||||||
var url = CreateGetUrl(projectId, packageQuery); | ||||||
return _api.Get().GetAllAsync<PackageSearchResult>(url); | ||||||
} | ||||||
|
||||||
public Task<PackageSearchResult> GetByIdAsync(int projectId, int packageId, CancellationToken cancellationToken = default) | ||||||
{ | ||||||
return _api.Get().ToAsync<PackageSearchResult>(string.Format(CultureInfo.InvariantCulture, GetPackageUrl, projectId, packageId), cancellationToken); | ||||||
} | ||||||
|
||||||
private static string CreateGetUrl(int projectId, PackageQuery query) | ||||||
{ | ||||||
var url = string.Format(CultureInfo.InvariantCulture, GetPackagesUrl, projectId); | ||||||
|
||||||
url = Utils.AddParameter(url, "order_by", query.OrderBy); | ||||||
url = Utils.AddParameter(url, "sort", query.Sort); | ||||||
url = Utils.AddParameter(url, "status", query.Status); | ||||||
url = Utils.AddParameter(url, "page", query.Page); | ||||||
url = Utils.AddParameter(url, "per_page", query.PerPage); | ||||||
|
||||||
if (query.PackageType != PackageType.all) | ||||||
{ | ||||||
url = Utils.AddParameter(url, "package_type", query.PackageType); | ||||||
} | ||||||
|
||||||
if (!string.IsNullOrWhiteSpace(query.PackageName)) | ||||||
{ | ||||||
url = Utils.AddParameter(url, "package_name", query.PackageName); | ||||||
} | ||||||
|
||||||
if (query.IncludeVersionless) | ||||||
{ | ||||||
url = Utils.AddParameter(url, "include_versionless", true); | ||||||
} | ||||||
|
||||||
return url; | ||||||
} | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
using System.IO; | ||
|
||
namespace NGitLab.Models | ||
{ | ||
public sealed class FileContent | ||
{ | ||
public FileContent(Stream stream) | ||
{ | ||
Stream = stream; | ||
} | ||
|
||
/// <summary> | ||
/// The stream to be uploaded. | ||
/// </summary> | ||
public Stream Stream { get; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,74 @@ | ||||||
using System; | ||||||
using System.Text.Json.Serialization; | ||||||
using NGitLab.Impl.Json; | ||||||
|
||||||
namespace NGitLab.Models | ||||||
{ | ||||||
public class Package | ||||||
{ | ||||||
[JsonPropertyName("id")] | ||||||
public int Id { get; set; } | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New classes should use
Suggested change
|
||||||
|
||||||
[JsonPropertyName("package_id")] | ||||||
public int PackageId { get; set; } | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
[JsonPropertyName("created_at")] | ||||||
[JsonConverter(typeof(DateOnlyConverter))] | ||||||
public DateTime CreatedAt { get; set; } | ||||||
|
||||||
[JsonPropertyName("updated_at")] | ||||||
[JsonConverter(typeof(DateOnlyConverter))] | ||||||
public DateTime? UpdatedAt { get; set; } | ||||||
|
||||||
[JsonPropertyName("size")] | ||||||
public int Size { get; set; } | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. File size is a long. I'm not sure GitLab would allow such big files, but you never know
Suggested change
|
||||||
|
||||||
[JsonPropertyName("file_store")] | ||||||
public int FileStore { get; set; } | ||||||
|
||||||
[JsonPropertyName("file_md5")] | ||||||
public string FileMD5 { get; set; } | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
[JsonPropertyName("file_sha1")] | ||||||
public string FileSHA1 { get; set; } | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
[JsonPropertyName("file_sha256")] | ||||||
public string FileSHA256 { get; set; } | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
[JsonPropertyName("file_name")] | ||||||
public string FileName { get; set; } | ||||||
|
||||||
[JsonPropertyName("verification_retry_at")] | ||||||
[JsonConverter(typeof(DateOnlyConverter))] | ||||||
public DateTime? VerificationRetryAt { get; set; } | ||||||
|
||||||
[JsonPropertyName("verified_at")] | ||||||
[JsonConverter(typeof(DateOnlyConverter))] | ||||||
public DateTime? VerifiedAt { get; set; } | ||||||
|
||||||
[JsonPropertyName("verification_failure")] | ||||||
public string VerificationFailure { get; set; } | ||||||
|
||||||
[JsonPropertyName("verification_retry_count")] | ||||||
public string VerificationRetryCount { get; set; } | ||||||
|
||||||
[JsonPropertyName("verification_checksum")] | ||||||
public string VerificationChecksum { get; set; } | ||||||
|
||||||
[JsonPropertyName("verification_state")] | ||||||
public int VerificationState { get; set; } | ||||||
|
||||||
[JsonPropertyName("verification_started_at")] | ||||||
[JsonConverter(typeof(DateOnlyConverter))] | ||||||
public DateTime? VerificationStartedAt { get; set; } | ||||||
|
||||||
[JsonPropertyName("new_file_path")] | ||||||
public string NewFilePath { get; set; } | ||||||
|
||||||
[JsonPropertyName("status")] | ||||||
public string Status { get; set; } | ||||||
|
||||||
[JsonPropertyName("file")] | ||||||
public PackageFile File { get; set; } | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
using System.Text.Json.Serialization; | ||
|
||
namespace NGitLab.Models | ||
{ | ||
public class PackageFile | ||
{ | ||
[JsonPropertyName("url")] | ||
gep13 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
public string Url { get; set; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
using System.Text.Json.Serialization; | ||
|
||
namespace NGitLab.Models | ||
{ | ||
public class PackageLinks | ||
{ | ||
[JsonPropertyName("web_path")] | ||
public string WebPath { get; set; } | ||
|
||
[JsonPropertyName("delete_api_path")] | ||
public string DeleteApiPath { get; set; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
namespace NGitLab.Models | ||
{ | ||
public enum PackageOrderBy | ||
{ | ||
created_at, | ||
name, | ||
version, | ||
type, | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@meziantou I couldn't see any prior art for returning an
IEnumerable<T>
in any of the existing clients. Happy to make a change here, just wanted to make sure that we are on the same page before making any changes.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other implementations use
GitLabCollectionResponse<T>
. This supports sync and async enumeration!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ooo! Missed that one! Will get that fixed up just now!