diff --git a/NGitLab.Mock/Clients/GitLabClient.cs b/NGitLab.Mock/Clients/GitLabClient.cs
index b5363a237..9d5283d08 100644
--- a/NGitLab.Mock/Clients/GitLabClient.cs
+++ b/NGitLab.Mock/Clients/GitLabClient.cs
@@ -13,6 +13,8 @@ public GitLabClient(ClientContext context)
public IGroupsClient Groups => new GroupClient(Context);
+ public IPackageClient Packages => new PackageClient(Context);
+
public IUserClient Users => new UserClient(Context);
public IProjectClient Projects => new ProjectClient(Context);
diff --git a/NGitLab.Mock/Clients/PackageClient.cs b/NGitLab.Mock/Clients/PackageClient.cs
new file mode 100644
index 000000000..ee90da386
--- /dev/null
+++ b/NGitLab.Mock/Clients/PackageClient.cs
@@ -0,0 +1,17 @@
+using NGitLab.Models;
+
+namespace NGitLab.Mock.Clients
+{
+ internal sealed class PackageClient : ClientBase, IPackageClient
+ {
+ public PackageClient(ClientContext context)
+ : base(context)
+ {
+ }
+
+ public Package Publish(PackagePublish packagePublish)
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/NGitLab.Tests/PackageTests.cs b/NGitLab.Tests/PackageTests.cs
new file mode 100644
index 000000000..4a32eb3f0
--- /dev/null
+++ b/NGitLab.Tests/PackageTests.cs
@@ -0,0 +1,33 @@
+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
+ {
+ ProjectId = project.Id,
+ FileName = "../../../../README.md",
+ PackageName = "Packages",
+ PackageVersion = "1.0.0",
+ Status = "default",
+ };
+
+ var package1 = packagesClient.Publish(packagePublish);
+
+ // TODO: What assertions should be put here?
+ Assert.True(true);
+ }
+ }
+}
diff --git a/NGitLab/GitLabClient.cs b/NGitLab/GitLabClient.cs
index 9bb61b5e1..ea85a5e46 100644
--- a/NGitLab/GitLabClient.cs
+++ b/NGitLab/GitLabClient.cs
@@ -8,6 +8,8 @@ public class GitLabClient : IGitLabClient
{
private readonly API _api;
+ public IPackageClient Packages { get; }
+
public IUserClient Users { get; }
public IProjectClient Projects { get; }
@@ -73,6 +75,7 @@ public GitLabClient(string hostUrl, string userName, string password, RequestOpt
private GitLabClient(GitLabCredentials credentials, RequestOptions options)
{
_api = new API(credentials, options);
+ Packages = new PackageClient(_api);
Users = new UserClient(_api);
Projects = new ProjectClient(_api);
MergeRequests = new MergeRequestClient(_api);
diff --git a/NGitLab/IGitLabClient.cs b/NGitLab/IGitLabClient.cs
index fd60a0daa..b71b3fbcc 100644
--- a/NGitLab/IGitLabClient.cs
+++ b/NGitLab/IGitLabClient.cs
@@ -2,6 +2,8 @@
{
public interface IGitLabClient
{
+ IPackageClient Packages { get; }
+
IUserClient Users { get; }
IProjectClient Projects { get; }
diff --git a/NGitLab/IPackageClient.cs b/NGitLab/IPackageClient.cs
new file mode 100644
index 000000000..6377e44a5
--- /dev/null
+++ b/NGitLab/IPackageClient.cs
@@ -0,0 +1,14 @@
+using NGitLab.Models;
+
+namespace NGitLab
+{
+ public interface IPackageClient
+ {
+ ///
+ /// Add a package file with the proposed information to the GitLab Generic Package Repository for the selected Project Id.
+ ///
+ /// The information about the package file to publish.
+ /// The package if it was created. Null if not.
+ Package Publish(PackagePublish packagePublish);
+ }
+}
diff --git a/NGitLab/Impl/HttpRequestor.GitLabRequest.cs b/NGitLab/Impl/HttpRequestor.GitLabRequest.cs
index 549724268..d50a97c34 100644
--- a/NGitLab/Impl/HttpRequestor.GitLabRequest.cs
+++ b/NGitLab/Impl/HttpRequestor.GitLabRequest.cs
@@ -25,6 +25,8 @@ private sealed class GitLabRequest
public string JsonData { get; }
+ public FileContent FileContent { get; }
+
public FormDataContent FormData { get; }
private MethodType Method { get; }
@@ -60,7 +62,11 @@ public GitLabRequest(Uri url, MethodType method, object data, string apiToken, R
Headers.Add("User-Agent", options.UserAgent);
}
- if (data is FormDataContent formData)
+ if (data is FileContent fileContent)
+ {
+ FileContent = fileContent;
+ }
+ else if (data is FormDataContent formData)
{
FormData = formData;
}
@@ -165,7 +171,11 @@ private HttpWebRequest CreateRequest(RequestOptions options)
if (HasOutput)
{
- if (FormData != null)
+ if (FileContent != null)
+ {
+ AddFileContent(request, options);
+ }
+ else if (FormData != null)
{
AddFileData(request, options);
}
@@ -182,6 +192,11 @@ private HttpWebRequest CreateRequest(RequestOptions options)
return request;
}
+ private void AddFileContent(HttpWebRequest request, RequestOptions options)
+ {
+ FileContent.Stream.CopyTo(options.GetRequestStream(request));
+ }
+
private void AddJsonData(HttpWebRequest request, RequestOptions options)
{
request.ContentType = "application/json";
diff --git a/NGitLab/Impl/PackageClient.cs b/NGitLab/Impl/PackageClient.cs
new file mode 100644
index 000000000..2aad528c5
--- /dev/null
+++ b/NGitLab/Impl/PackageClient.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Globalization;
+using System.IO;
+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 readonly API _api;
+
+ public PackageClient(API api)
+ {
+ _api = api;
+ }
+
+ public Package Publish(PackagePublish packagePublish)
+ {
+ var fullFilePath = Path.GetFullPath(packagePublish.FileName);
+
+ if (!File.Exists(fullFilePath))
+ {
+ throw new InvalidOperationException("Can't find file!");
+ }
+
+ var formData = new FileContent(File.OpenRead(fullFilePath));
+ var packageFile = new FileInfo(fullFilePath);
+
+ return _api.Put().With(formData).To(string.Format(CultureInfo.InvariantCulture,
+ PublishPackageUrl, packagePublish.ProjectId, packagePublish.PackageName, packagePublish.PackageVersion,
+ packageFile.Name, packagePublish.Status));
+ }
+ }
+}
diff --git a/NGitLab/Models/FileContent.cs b/NGitLab/Models/FileContent.cs
new file mode 100644
index 000000000..d99c8feb2
--- /dev/null
+++ b/NGitLab/Models/FileContent.cs
@@ -0,0 +1,17 @@
+using System.IO;
+
+namespace NGitLab.Models
+{
+ public sealed class FileContent
+ {
+ public FileContent(Stream stream)
+ {
+ Stream = stream;
+ }
+
+ ///
+ /// The stream to be uploaded.
+ ///
+ public Stream Stream { get; }
+ }
+}
diff --git a/NGitLab/Models/Package.cs b/NGitLab/Models/Package.cs
new file mode 100644
index 000000000..224844902
--- /dev/null
+++ b/NGitLab/Models/Package.cs
@@ -0,0 +1,76 @@
+using System;
+using System.ComponentModel;
+using System.Text.Json.Serialization;
+using NGitLab.Impl.Json;
+
+namespace NGitLab.Models
+{
+ public class Package
+ {
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [JsonIgnore]
+ public int Id;
+
+ [JsonPropertyName("package_id")]
+ public int PackageId;
+
+ [JsonPropertyName("created_at")]
+ [JsonConverter(typeof(DateOnlyConverter))]
+ public DateTime CreatedAt;
+
+ [JsonPropertyName("updated_at")]
+ [JsonConverter(typeof(DateOnlyConverter))]
+ public DateTime? UpdatedAt;
+
+ [JsonPropertyName("size")]
+ public int Size;
+
+ [JsonPropertyName("file_store")]
+ public int FileStore;
+
+ [JsonPropertyName("file_md5")]
+ public string FileMD5;
+
+ [JsonPropertyName("file_sha1")]
+ public string FileSHA1;
+
+ [JsonPropertyName("file_sha256")]
+ public string FileSHA256;
+
+ [JsonPropertyName("file_name")]
+ public string FileName;
+
+ [JsonPropertyName("verification_retry_at")]
+ [JsonConverter(typeof(DateOnlyConverter))]
+ public DateTime? VerificationRetryAt;
+
+ [JsonPropertyName("verified_at")]
+ [JsonConverter(typeof(DateOnlyConverter))]
+ public DateTime? VerifiedAt;
+
+ [JsonPropertyName("verification_failure")]
+ public string VerificationFailure;
+
+ [JsonPropertyName("verification_retry_count")]
+ public string VerificationRetryCount;
+
+ [JsonPropertyName("verification_checksum")]
+ public string VerificationChecksum;
+
+ [JsonPropertyName("verification_state")]
+ public int VerificationState;
+
+ [JsonPropertyName("verification_started_at")]
+ [JsonConverter(typeof(DateOnlyConverter))]
+ public DateTime? VerificationStartedAt;
+
+ [JsonPropertyName("new_file_path")]
+ public string NewFilePath;
+
+ [JsonPropertyName("status")]
+ public string Status;
+
+ [JsonPropertyName("file")]
+ public PackageFile File;
+ }
+}
diff --git a/NGitLab/Models/PackageFile.cs b/NGitLab/Models/PackageFile.cs
new file mode 100644
index 000000000..5dc46930c
--- /dev/null
+++ b/NGitLab/Models/PackageFile.cs
@@ -0,0 +1,10 @@
+using System.Text.Json.Serialization;
+
+namespace NGitLab.Models
+{
+ public class PackageFile
+ {
+ [JsonPropertyName("url")]
+ public string Url;
+ }
+}
diff --git a/NGitLab/Models/PackagePublish.cs b/NGitLab/Models/PackagePublish.cs
new file mode 100644
index 000000000..b5f04e25e
--- /dev/null
+++ b/NGitLab/Models/PackagePublish.cs
@@ -0,0 +1,30 @@
+using System.ComponentModel.DataAnnotations;
+using System.Text.Json.Serialization;
+
+namespace NGitLab.Models
+{
+ public class PackagePublish
+ {
+ [Required]
+ [JsonPropertyName("id")]
+ public int ProjectId;
+
+ [Required]
+ [JsonPropertyName("package_name")]
+ public string PackageName;
+
+ [Required]
+ [JsonPropertyName("package_version")]
+ public string PackageVersion;
+
+ [Required]
+ [JsonPropertyName("file_name")]
+ public string FileName;
+
+ [JsonPropertyName("status")]
+ public string Status;
+
+ [JsonPropertyName("select")]
+ public string Select;
+ }
+}