Uploaded image for project: 'Project Quay'
  1. Project Quay
  2. PROJQUAY-10258

[API] Add Sparse Manifest Capability Detection Endpoint

XMLWordPrintable

    • Icon: Story Story
    • Resolution: Unresolved
    • Icon: Undefined Undefined
    • None
    • None
    • None
    • Security & Compliance
    • False
    • Hide

      None

      Show
      None
    • False
    • Not Selected

      [API] Add Sparse Manifest Capability Detection Endpoint

      Summary

      Create a new API endpoint that allows clients (oc-mirror, skopeo) to query whether the registry supports sparse manifests. This enables clients to detect sparse manifest support before attempting filtered multi-architecture mirroring.

      Acceptance Criteria

      • [ ] New API endpoint returns sparse manifest capability status
      • [ ] Response includes sparse_manifests_supported: true/false based on FEATURE_SPARSE_INDEX
      • [ ] Response includes list of required architectures if configured
      • [ ] Endpoint follows existing API patterns and authentication
      • [ ] Endpoint is documented in API schema
      • [ ] Clients can query without repository context (registry-level capability)

      Technical Requirements

      API Endpoint Design

      Endpoint: GET /api/v1/registry/capabilities

      Response:

      {
          "sparse_manifests": {
              "supported": true,
              "required_architectures": ["amd64", "arm64"],
              "optional_architectures_allowed": true
          },
          "oci_artifacts": {
              "supported": true
          }
      }
      

      Alternative OCI distribution-spec compliant approach:

      Endpoint: GET /v2/ (extend existing)

      Add to the existing v2 discovery response via headers or body:

      X-Sparse-Manifest-Support: true
      X-Required-Architectures: amd64,arm64
      

      Implementation

      File: endpoints/api/capabilities.py (new file)

      from endpoints.api import (
          resource,
          require_scope,
          path_param,
          define_json_response,
      )
      from auth.permissions import ReadRepositoryPermission
      from endpoints.exception import NotFound
      import features
      
      @resource("/v1/registry/capabilities")
      class RegistryCapabilities(ApiResource):
          """Resource for querying registry capabilities."""
      
          @define_json_response("RegistryCapabilities")
          def get(self):
              """
              Get registry capabilities.
      
              Returns information about supported registry features including
              sparse manifest support and required architectures.
              """
              sparse_enabled = features.SPARSE_INDEX
              required_archs = app.config.get("SPARSE_INDEX_REQUIRED_ARCHS", [])
      
              return {
                  "sparse_manifests": {
                      "supported": sparse_enabled,
                      "required_architectures": required_archs if sparse_enabled else [],
                      "optional_architectures_allowed": sparse_enabled and len(required_archs) > 0,
                  },
                  "oci_artifacts": {
                      "supported": features.OCI_ARTIFACTS if hasattr(features, 'OCI_ARTIFACTS') else False,
                  },
              }
      

      Register Endpoint

      File: endpoints/api/__init__.py

      Add import and registration for the new endpoint:

      from endpoints.api.capabilities import RegistryCapabilities
      

      OCI Distribution Spec Compliance

      File: endpoints/v2/__init__.py

      Also consider adding capability headers to the v2 base endpoint for OCI compliance:

      @v2_bp.route("/")
      def v2_base():
          """V2 API discovery endpoint."""
          response = make_response("", 200)
      
          # Add sparse manifest capability headers
          if features.SPARSE_INDEX:
              response.headers["X-Sparse-Manifest-Support"] = "true"
              required_archs = app.config.get("SPARSE_INDEX_REQUIRED_ARCHS", [])
              if required_archs:
                  response.headers["X-Required-Architectures"] = ",".join(required_archs)
      
          return response
      

      Implementation Notes

      Existing Patterns to Follow

      • API structure: See endpoints/api/mirror.py for resource patterns
      • Response format: See define_json_response() usage in other endpoints
      • Feature flag access: See features.REPO_MIRROR pattern

      Authentication

      This endpoint should be accessible:
      - Without authentication (public capability query)
      - Or with minimal read permissions

      @resource("/v1/registry/capabilities")
      class RegistryCapabilities(ApiResource):
          """Resource for querying registry capabilities."""
      
          schemas = {
              "RegistryCapabilities": {
                  "type": "object",
                  "properties": {
                      "sparse_manifests": {
                          "type": "object",
                          "properties": {
                              "supported": {"type": "boolean"},
                              "required_architectures": {
                                  "type": "array",
                                  "items": {"type": "string"}
                              },
                              "optional_architectures_allowed": {"type": "boolean"}
                          }
                      }
                  }
              }
          }
      
          # No authentication required for capability query
          def get(self):
              ...
      

      Response Headers for oc-mirror

      oc-mirror may expect specific headers. Coordinate with oc-mirror team on expected format:

      # Example headers oc-mirror might look for
      X-Sparse-Index-Supported: true
      X-Sparse-Index-Required-Archs: amd64,arm64
      

      Error Handling

      No special error handling needed - this endpoint should always return successfully:

      def get(self):
          try:
              sparse_enabled = features.SPARSE_INDEX
              required_archs = app.config.get("SPARSE_INDEX_REQUIRED_ARCHS", [])
          except Exception:
              # Default to disabled if configuration error
              sparse_enabled = False
              required_archs = []
      
          return {"sparse_manifests": {"supported": sparse_enabled, ...}}
      

      Dependencies

      • Story 02: Registry core sparse manifest support (configuration must be in place)

      Testing Requirements

      Unit Tests

      File: endpoints/api/test/test_capabilities.py (new file)

      def test_capabilities_sparse_disabled():
          """Test capabilities endpoint when sparse manifests disabled."""
          # FEATURE_SPARSE_INDEX = False
          response = client.get("/api/v1/registry/capabilities")
          assert response.status_code == 200
          assert response.json["sparse_manifests"]["supported"] == False
          assert response.json["sparse_manifests"]["required_architectures"] == []
      
      def test_capabilities_sparse_enabled():
          """Test capabilities endpoint when sparse manifests enabled."""
          # FEATURE_SPARSE_INDEX = True
          response = client.get("/api/v1/registry/capabilities")
          assert response.status_code == 200
          assert response.json["sparse_manifests"]["supported"] == True
      
      def test_capabilities_with_required_archs():
          """Test capabilities endpoint with required architectures configured."""
          # FEATURE_SPARSE_INDEX = True
          # SPARSE_INDEX_REQUIRED_ARCHS = ["amd64", "arm64"]
          response = client.get("/api/v1/registry/capabilities")
          assert response.json["sparse_manifests"]["required_architectures"] == ["amd64", "arm64"]
      
      def test_capabilities_no_auth_required():
          """Test capabilities endpoint accessible without authentication."""
          response = client.get("/api/v1/registry/capabilities")
          assert response.status_code == 200
      
      def test_v2_base_sparse_headers():
          """Test v2 base endpoint includes sparse capability headers."""
          response = client.get("/v2/")
          assert response.headers.get("X-Sparse-Manifest-Support") == "true"
      

      Integration Tests

      Test with actual client tools:
      1. Query capabilities endpoint
      2. Verify oc-mirror can detect sparse support
      3. Verify skopeo can detect sparse support

      Definition of Done

      • [ ] Code implemented and follows project conventions
      • [ ] All acceptance criteria met
      • [ ] Unit tests written and passing
      • [ ] API documentation updated
      • [ ] Endpoint accessible to clients (oc-mirror, skopeo)
      • [ ] Code reviewed and approved

              marckok Marcus Kok
              marckok Marcus Kok
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

                Created:
                Updated: