diff --git a/app/services/packages/nuget/odata_package_entry_service.rb b/app/services/packages/nuget/odata_package_entry_service.rb
index 0cdcc38de16965c2dc10d268b0aa6a0b11b26dce..679b01d6c4865fcb3da52391340ac2ae173fb977 100644
--- a/app/services/packages/nuget/odata_package_entry_service.rb
+++ b/app/services/packages/nuget/odata_package_entry_service.rb
@@ -5,9 +5,6 @@ module Nuget
class OdataPackageEntryService
include API::Helpers::RelatedResourcesHelpers
- SEMVER_LATEST_VERSION_PLACEHOLDER = '0.0.0-latest-version'
- LATEST_VERSION_FOR_V2_DOWNLOAD_ENDPOINT = 'latest'
-
def initialize(project, params)
@project = project
@params = params
@@ -29,42 +26,40 @@ def package_entry
#{params[:package_name]}
- #{package_version}
+ #{params[:package_version]}
XML
end
- def package_version
- params[:package_version] || SEMVER_LATEST_VERSION_PLACEHOLDER
- end
-
def id_url
expose_url "#{api_v4_projects_packages_nuget_v2_path(id: project.id)}" \
- "/Packages(Id='#{params[:package_name]}',Version='#{package_version}')"
+ "/Packages(Id='#{params[:package_name]}',Version='#{params[:package_version]}')"
end
- # TODO: use path helper when download endpoint is merged
def download_url
- expose_url "#{api_v4_projects_packages_nuget_v2_path(id: project.id)}" \
- "/download/#{params[:package_name]}/#{download_url_package_version}"
- end
-
- def download_url_package_version
- if latest_version?
- LATEST_VERSION_FOR_V2_DOWNLOAD_ENDPOINT
+ if params[:package_version].present?
+ expose_url api_v4_projects_packages_nuget_download_package_name_package_version_package_filename_path(
+ {
+ id: project.id,
+ package_name: params[:package_name],
+ package_version: params[:package_version],
+ package_filename: file_name
+ },
+ true
+ )
else
- params[:package_version]
+ xml_base
end
end
- def latest_version?
- params[:package_version].nil? || params[:package_version] == SEMVER_LATEST_VERSION_PLACEHOLDER
- end
-
def xml_base
expose_url api_v4_projects_packages_nuget_v2_path(id: project.id)
end
+
+ def file_name
+ "#{params[:package_name]}.#{params[:package_version]}.nupkg"
+ end
end
end
end
diff --git a/doc/api/packages/nuget.md b/doc/api/packages/nuget.md
index ee304ab28df3f394f3dde9d81e9a3aa29ed820da..e28f42fa931516d27b0f60556a355697890b9541 100644
--- a/doc/api/packages/nuget.md
+++ b/doc/api/packages/nuget.md
@@ -488,10 +488,29 @@ Example response:
| `GET projects/:id/packages/nuget/v2/FindPackagesById()?id=''` | Returns an OData XML document containing information about the package with the given name. |
| `GET projects/:id/packages/nuget/v2/Packages(Id='',Version='')` | Returns an OData XML document containing information about the package with the given name and version. |
+```shell
+curl "https://gitlab.example.com/api/v4/projects/1/packages/nuget/v2/Packages(Id='mynugetpkg',Version='1.0.0')"
+```
+
+Example response:
+
+```xml
+
+ https://gitlab.example.com/api/v4/projects/1/packages/nuget/v2/Packages(Id='mynugetpkg',Version='1.0.0')
+
+ mynugetpkg
+
+
+ 1.0.0
+
+
+```
+
NOTE:
-GitLab doesn't receive an authentication token for the `Packages()` and `FindPackagesByID()` endpoints.
-To not reveal the package version to unauthenticated users, the actual latest package version is not returned. Instead, a placeholder version is returned.
-The latest version is obtained in the subsequent download request where the authentication token is sent.
+GitLab doesn't receive an authentication token for the `Packages()` and
+`FindPackagesByID()` endpoints, so the latest version of the package
+cannot be returned. You must provide the version when you install
+or upgrade a package with the NuGet v2 feed.
```shell
curl "https://gitlab.example.com/api/v4/projects/1/packages/nuget/v2/Packages()?$filter=(tolower(Id) eq 'mynugetpkg')"
@@ -501,12 +520,12 @@ Example response:
```xml
- https://gitlab.example.com/api/v4/projects/1/packages/nuget/v2/Packages(Id='mynugetpkg',Version='0.0.0-latest-version')
+ https://gitlab.example.com/api/v4/projects/1/packages/nuget/v2/Packages(Id='mynugetpkg',Version='')
mynugetpkg
-
+
- 0.0.0-latest-version
+
```
diff --git a/doc/user/packages/nuget_repository/index.md b/doc/user/packages/nuget_repository/index.md
index 340df4a3c5fb3d5fb9e7b3e7d6dd18cf9b019f84..4f8bb56bda8521aadc203b4e2cd9b162606d1e3b 100644
--- a/doc/user/packages/nuget_repository/index.md
+++ b/doc/user/packages/nuget_repository/index.md
@@ -492,6 +492,55 @@ dotnet add package \
- `` is the package ID.
- `` is the package version. Optional.
+### Install a package using NuGet v2 feed
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/416405) in GitLab 16.5.
+
+Prerequisites:
+
+- The project-level Package Registry is a [v2 feed source](#add-a-source-with-chocolatey-cli) for Chocolatey.
+- A version must be provided when installing or upgrading a package using NuGet v2 feed.
+
+To install a package with the Chocolatey CLI:
+
+```shell
+choco install -Source -Version
+```
+
+In this command:
+
+- `` is the package ID.
+- `` is the URL or name of the NuGet v2 feed Package Registry.
+- `` is the package version.
+
+For example:
+
+```shell
+choco install MyPackage -Source gitlab -Version 1.0.2
+
+# or
+
+choco install MyPackage -Source "https://gitlab.example.com/api/v4/projects//packages/nuget/v2" -u -p -Version 1.0.2
+```
+
+To upgrade a package with the Chocolatey CLI:
+
+```shell
+choco upgrade -Source -Version
+```
+
+In this command:
+
+- `` is the package ID.
+- `` is the URL or name of the NuGet v2 feed Package Registry.
+- `` is the package version.
+
+For example:
+
+```shell
+choco upgrade MyPackage -Source gitlab -Version 1.0.3
+```
+
## Symbol packages
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/262081) in GitLab 14.1.
diff --git a/lib/api/nuget_project_packages.rb b/lib/api/nuget_project_packages.rb
index dbc789c68b6c7b6983557875dfe1ba34cfaa6898..3da05b4e7d928abbeb26cd1b3d035281e01e2c2c 100644
--- a/lib/api/nuget_project_packages.rb
+++ b/lib/api/nuget_project_packages.rb
@@ -113,9 +113,7 @@ def publish_package(symbol_package: false)
track_package_event(
symbol_package ? 'push_symbol_package' : 'push_package',
:nuget,
- **{ category: 'API::NugetPackages',
- project: package.project,
- namespace: package.project.namespace }.tap { |args| args[:feed] = 'v2' if request.path.include?('nuget/v2') }
+ **track_package_event_attrs(package.project)
)
end
rescue ObjectStorage::RemoteStoreError => e
@@ -148,6 +146,16 @@ def present_odata_entry
present odata_entry
end
+
+ def track_package_event_attrs(project)
+ attrs = {
+ category: 'API::NugetPackages',
+ project: project,
+ namespace: project.namespace
+ }
+ attrs[:feed] = 'v2' if request.path.include?('nuget/v2')
+ attrs
+ end
end
params do
@@ -216,9 +224,7 @@ def present_odata_entry
track_package_event(
params[:format] == 'snupkg' ? 'pull_symbol_package' : 'pull_package',
:nuget,
- category: 'API::NugetPackages',
- project: package_file.project,
- namespace: package_file.project.namespace
+ **track_package_event_attrs(package.project)
)
# nuget and dotnet don't support 302 Moved status codes, supports_direct_download has to be set to false
diff --git a/spec/requests/api/nuget_project_packages_spec.rb b/spec/requests/api/nuget_project_packages_spec.rb
index b55d992c1e44c508f27ba61f3b43b120f53c81fe..1dcae4cf29503776b16cbaf9f499c756f86f237d 100644
--- a/spec/requests/api/nuget_project_packages_spec.rb
+++ b/spec/requests/api/nuget_project_packages_spec.rb
@@ -203,11 +203,12 @@ def snowplow_context(user_role: :developer)
describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/*package_version/*package_filename' do
let_it_be(:package) { create(:nuget_package, :with_symbol_package, :with_metadatum, project: project, name: package_name, version: '0.1') }
+ let_it_be(:package_version) { package.version }
let(:format) { 'nupkg' }
- let(:url) { "/projects/#{target.id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.name}.#{package.version}.#{format}" }
+ let(:url) { "/projects/#{target.id}/packages/nuget/download/#{package.name}/#{package_version}/#{package.name}.#{package_version}.#{format}" }
- subject { get api(url) }
+ subject { get api(url), headers: headers }
context 'with valid target' do
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
@@ -236,8 +237,6 @@ def snowplow_context(user_role: :developer)
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
let(:snowplow_gitlab_standard_context) { snowplow_context(user_role: user_role) }
- subject { get api(url), headers: headers }
-
before do
update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false))
end
diff --git a/spec/services/packages/nuget/odata_package_entry_service_spec.rb b/spec/services/packages/nuget/odata_package_entry_service_spec.rb
index d4c47538ce24b894511064ae2f4bbec71b7f3569..b4a22fef32b99cdb691c1d12db31b1465fc42378 100644
--- a/spec/services/packages/nuget/odata_package_entry_service_spec.rb
+++ b/spec/services/packages/nuget/odata_package_entry_service_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Packages::Nuget::OdataPackageEntryService, feature_category: :package_registry do
+ include GrapePathHelpers::NamedRouteMatcher
+
let_it_be(:project) { build_stubbed(:project) }
let_it_be(:params) { { package_name: 'dummy', package_version: '1.0.0' } }
let(:doc) { Nokogiri::XML(subject.payload) }
@@ -10,7 +12,7 @@
subject { described_class.new(project, params).execute }
describe '#execute' do
- shared_examples 'returning a package entry with the correct attributes' do |pkg_version, content_url_pkg_version|
+ shared_examples 'returning a package entry with the correct attributes' do |pkg_version = ''|
it 'returns a package entry with the correct attributes' do
expect(doc.root.name).to eq('entry')
expect(doc_node('id').text).to include(
@@ -18,7 +20,7 @@
)
expect(doc_node('title').text).to eq(params[:package_name])
expect(doc_node('content').attr('src')).to include(
- content_url(project.id, params[:package_name], content_url_pkg_version)
+ content_url(project.id, params[:package_name], pkg_version)
)
expect(doc_node('Version').text).to eq(pkg_version)
end
@@ -29,29 +31,17 @@
expect(subject).to be_success
end
- it_behaves_like 'returning a package entry with the correct attributes', '1.0.0', '1.0.0'
+ it_behaves_like 'returning a package entry with the correct attributes', '1.0.0'
end
- context 'when package_version is nil' do
+ context 'when package_version is not present' do
let(:params) { { package_name: 'dummy', package_version: nil } }
it 'returns a success ServiceResponse' do
expect(subject).to be_success
end
- it_behaves_like 'returning a package entry with the correct attributes',
- described_class::SEMVER_LATEST_VERSION_PLACEHOLDER, described_class::LATEST_VERSION_FOR_V2_DOWNLOAD_ENDPOINT
- end
-
- context 'when package_version is 0.0.0-latest-version' do
- let(:params) { { package_name: 'dummy', package_version: described_class::SEMVER_LATEST_VERSION_PLACEHOLDER } }
-
- it 'returns a success ServiceResponse' do
- expect(subject).to be_success
- end
-
- it_behaves_like 'returning a package entry with the correct attributes',
- described_class::SEMVER_LATEST_VERSION_PLACEHOLDER, described_class::LATEST_VERSION_FOR_V2_DOWNLOAD_ENDPOINT
+ it_behaves_like 'returning a package entry with the correct attributes'
end
end
@@ -64,6 +54,13 @@ def id_url(id, package_name, package_version)
end
def content_url(id, package_name, package_version)
- "api/v4/projects/#{id}/packages/nuget/v2/download/#{package_name}/#{package_version}"
+ if package_version.present?
+ filename = "#{package_name}.#{package_version}.nupkg"
+ api_v4_projects_packages_nuget_download_package_name_package_version_package_filename_path(
+ { id: id, package_name: package_name, package_version: package_version, package_filename: filename }, true
+ )
+ else
+ api_v4_projects_packages_nuget_v2_path(id: id)
+ end
end
end
diff --git a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
index 1be99040ae5de76c14039855f9ae77d993c85bed..0b67f12fb8dc0dd988195850f772dc0987531a4b 100644
--- a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
@@ -357,12 +357,7 @@
end
context 'with normalized package version' do
- let(:normalized_version) { '0.1.0' }
- let(:url) { "/projects/#{target.id}/packages/nuget/download/#{package.name}/#{normalized_version}/#{package.name}.#{package.version}.#{format}" }
-
- before do
- package.nuget_metadatum.update_column(:normalized_version, normalized_version)
- end
+ let(:package_version) { '0.1.0' }
it_behaves_like 'returning response status', status