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; + } +}