Skip to content
Snippets Groups Projects
Commit 01feeb59 authored by Pedro Pombeiro's avatar Pedro Pombeiro
Browse files

Merge branch '416405-Nuget-v2-$metadata-endpoint' into 'master'

parents 26626dc5 ece3d3d2
No related branches found
No related tags found
1 merge request!128786NuGet v2 $metadata endpoint
Pipeline #969099084 failed
Pipeline: E2E Omnibus GitLab EE

#969163530

    Pipeline: E2E GDK

    #969123828

      Pipeline: GitLab

      #969101037

        +20
        # frozen_string_literal: true
        module Packages
        module Nuget
        module V2
        class MetadataIndexPresenter
        def xml
        Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
        xml['edmx'].Edmx('xmlns:edmx' => 'http://schemas.microsoft.com/ado/2007/06/edmx', Version: '1.0') do
        xml['edmx'].DataServices('xmlns:m' => 'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata',
        'm:DataServiceVersion' => '2.0', 'm:MaxDataServiceVersion' => '2.0') do
        xml.Schema(xmlns: 'http://schemas.microsoft.com/ado/2006/04/edm', Namespace: 'NuGetGallery.OData') do
        xml.EntityType(Name: 'V2FeedPackage', 'm:HasStream' => true) do
        xml.Key do
        xml.PropertyRef(Name: 'Id')
        xml.PropertyRef(Name: 'Version')
        end
        xml.Property(Name: 'Id', Type: 'Edm.String', Nullable: false)
        xml.Property(Name: 'Version', Type: 'Edm.String', Nullable: false)
        xml.Property(Name: 'Authors', Type: 'Edm.String')
        xml.Property(Name: 'Dependencies', Type: 'Edm.String')
        xml.Property(Name: 'Description', Type: 'Edm.String')
        xml.Property(Name: 'DownloadCount', Type: 'Edm.Int64', Nullable: false)
        xml.Property(Name: 'IconUrl', Type: 'Edm.String')
        xml.Property(Name: 'Published', Type: 'Edm.DateTime', Nullable: false)
        xml.Property(Name: 'ProjectUrl', Type: 'Edm.String')
        xml.Property(Name: 'Tags', Type: 'Edm.String')
        xml.Property(Name: 'Title', Type: 'Edm.String')
        xml.Property(Name: 'LicenseUrl', Type: 'Edm.String')
        end
        end
        xml.Schema(xmlns: 'http://schemas.microsoft.com/ado/2006/04/edm', Namespace: 'NuGetGallery') do
        xml.EntityContainer(Name: 'V2FeedContext', 'm:IsDefaultEntityContainer' => true) do
        xml.EntitySet(Name: 'Packages', EntityType: 'NuGetGallery.OData.V2FeedPackage')
        xml.FunctionImport(Name: 'FindPackagesById',
        ReturnType: 'Collection(NuGetGallery.OData.V2FeedPackage)', EntitySet: 'Packages') do
        xml.Parameter(Name: 'id', Type: 'Edm.String', FixedLength: 'false', Unicode: 'false')
        end
        end
        end
        end
        end
        end
        end
        end
        end
        end
        end
        ......@@ -424,3 +424,54 @@ Example response:
        ]
        }
        ```
        ## V2 Feed Metadata Endpoint
        > Introduced in GitLab 16.3.
        Authentication is not required. Returns metadata for a V2 feed available endpoints:
        ```plaintext
        GET <route-prefix>/v2/$metadata
        ```
        ```shell
        curl "https://gitlab.example.com/api/v4/projects/1/packages/nuget/v2/$metadata"
        ```
        Example response:
        ```xml
        <edmx:Edmx xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx" Version="1.0">
        <edmx:DataServices xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:DataServiceVersion="2.0" m:MaxDataServiceVersion="2.0">
        <Schema xmlns="http://schemas.microsoft.com/ado/2006/04/edm" Namespace="NuGetGallery.OData">
        <EntityType Name="V2FeedPackage" m:HasStream="true">
        <Key>
        <PropertyRef Name="Id"/>
        <PropertyRef Name="Version"/>
        </Key>
        <Property Name="Id" Type="Edm.String" Nullable="false"/>
        <Property Name="Version" Type="Edm.String" Nullable="false"/>
        <Property Name="Authors" Type="Edm.String"/>
        <Property Name="Dependencies" Type="Edm.String"/>
        <Property Name="Description" Type="Edm.String"/>
        <Property Name="DownloadCount" Type="Edm.Int64" Nullable="false"/>
        <Property Name="IconUrl" Type="Edm.String"/>
        <Property Name="Published" Type="Edm.DateTime" Nullable="false"/>
        <Property Name="ProjectUrl" Type="Edm.String"/>
        <Property Name="Tags" Type="Edm.String"/>
        <Property Name="Title" Type="Edm.String"/>
        <Property Name="LicenseUrl" Type="Edm.String"/>
        </EntityType>
        </Schema>
        <Schema xmlns="http://schemas.microsoft.com/ado/2006/04/edm" Namespace="NuGetGallery">
        <EntityContainer Name="V2FeedContext" m:IsDefaultEntityContainer="true">
        <EntitySet Name="Packages" EntityType="NuGetGallery.OData.V2FeedPackage"/>
        <FunctionImport Name="FindPackagesById" ReturnType="Collection(NuGetGallery.OData.V2FeedPackage)" EntitySet="Packages">
        <Parameter Name="id" Type="Edm.String" FixedLength="false" Unicode="false"/>
        </FunctionImport>
        </EntityContainer>
        </Schema>
        </edmx:DataServices>
        </edmx:Edmx>
        ```
        ......@@ -60,6 +60,22 @@ module PublicEndpoints
        .new(project_or_group_without_auth)
        .xml
        end
        # https://www.nuget.org/api/v2/$metadata
        desc 'The NuGet V2 Feed Package $metadata endpoint' do
        detail 'This feature was introduced in GitLab 16.3'
        success code: 200
        tags %w[nuget_packages]
        end
        get '$metadata', format: :xml, urgency: :low do
        env['api.format'] = :xml
        content_type 'application/xml; charset=utf-8'
        # needed to allow browser default inline styles in xml response
        header 'Content-Security-Policy', "nonce-#{SecureRandom.base64(16)}"
        present ::Packages::Nuget::V2::MetadataIndexPresenter.new.xml
        end
        end
        end
        end
        ......
        # frozen_string_literal: true
        require 'spec_helper'
        RSpec.describe Packages::Nuget::V2::MetadataIndexPresenter, feature_category: :package_registry do
        describe '#xml' do
        let(:presenter) { described_class.new }
        subject(:xml) { Nokogiri::XML(presenter.xml.to_xml) }
        specify { expect(xml.root.name).to eq('Edmx') }
        specify { expect(xml.at_xpath('//edmx:Edmx')).to be_present }
        specify { expect(xml.at_xpath('//edmx:Edmx/edmx:DataServices')).to be_present }
        specify do
        expect(xml.css('*').map(&:name)).to include(
        'Schema', 'EntityType', 'Key', 'PropertyRef', 'EntityContainer', 'EntitySet', 'FunctionImport', 'Parameter'
        )
        end
        specify do
        expect(xml.css('*').select { |el| el.name == 'Property' }.map { |el| el.attribute_nodes.first.value })
        .to match_array(
        %w[Id Version Authors Dependencies Description DownloadCount IconUrl Published ProjectUrl Tags Title
        LicenseUrl]
        )
        end
        specify { expect(xml.css('*').detect { |el| el.name == 'EntityContainer' }.attr('Name')).to eq('V2FeedContext') }
        specify { expect(xml.css('*').detect { |el| el.name == 'FunctionImport' }.attr('Name')).to eq('FindPackagesById') }
        end
        end
        ......@@ -50,6 +50,44 @@ def snowplow_context(user_role: :developer)
        it_behaves_like 'accept get request on private project with access to package registry for everyone'
        end
        describe 'GET /api/v4/projects/:id/packages/nuget/v2/$metadata' do
        let(:url) { "/projects/#{target.id}/packages/nuget/v2/$metadata" }
        subject(:api_request) { get api(url) }
        it { is_expected.to have_request_urgency(:low) }
        context 'with valid target' do
        using RSpec::Parameterized::TableSyntax
        where(:visibility_level, :user_role, :member, :expected_status) do
        'PUBLIC' | :developer | true | :success
        'PUBLIC' | :guest | true | :success
        'PUBLIC' | :developer | false | :success
        'PUBLIC' | :guest | false | :success
        'PUBLIC' | :anonymous | false | :success
        'PRIVATE' | :developer | true | :success
        'PRIVATE' | :guest | true | :success
        'PRIVATE' | :developer | false | :success
        'PRIVATE' | :guest | false | :success
        'PRIVATE' | :anonymous | false | :success
        'INTERNAL' | :developer | true | :success
        'INTERNAL' | :guest | true | :success
        'INTERNAL' | :developer | false | :success
        'INTERNAL' | :guest | false | :success
        'INTERNAL' | :anonymous | false | :success
        end
        with_them do
        before do
        update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false))
        end
        it_behaves_like 'process nuget v2 $metadata service request', params[:user_role], params[:expected_status], params[:member]
        end
        end
        end
        describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/index' do
        let(:url) { "/projects/#{target.id}/packages/nuget/metadata/#{package_name}/index.json" }
        ......
        ......@@ -51,6 +51,34 @@
        end
        end
        RSpec.shared_examples 'process nuget v2 $metadata service request' do |user_type, status, add_member = true|
        context "for user type #{user_type}" do
        before do
        target.send("add_#{user_type}", user) if add_member && user_type != :anonymous
        end
        it_behaves_like 'returning response status', status
        it 'returns a valid xml response' do
        api_request
        doc = Nokogiri::XML(body)
        expect(response.media_type).to eq('application/xml')
        expect(doc.at_xpath('//edmx:Edmx')).to be_present
        expect(doc.at_xpath('//edmx:Edmx/edmx:DataServices')).to be_present
        expect(doc.css('*').map(&:name)).to include(
        'Schema', 'EntityType', 'Key', 'PropertyRef', 'EntityContainer', 'EntitySet', 'FunctionImport', 'Parameter'
        )
        expect(doc.css('*').select { |el| el.name == 'Property' }.map { |el| el.attribute_nodes.first.value })
        .to match_array(%w[Id Version Authors Dependencies Description DownloadCount IconUrl Published ProjectUrl
        Tags Title LicenseUrl]
        )
        expect(doc.css('*').detect { |el| el.name == 'FunctionImport' }.attr('Name')).to eq('FindPackagesById')
        end
        end
        end
        RSpec.shared_examples 'returning nuget metadata json response with json schema' do |json_schema|
        it 'returns a valid json response' do
        subject
        ......
        0% Loading or .
        You are about to add 0 people to the discussion. Proceed with caution.
        Finish editing this message first!
        Please register or to comment