PEP 438 – PyPI에서 릴리스 파일 호스팅으로 전환
- Author:
- Holger Krekel <holger at merlinux.eu>, Carl Meyer <carl at oddbird.net>
- BDFL-Delegate:
- Richard Jones <richard at python.org>
- Discussions-To:
- Distutils-SIG list
- Status:
- Superseded
- Type:
- Process
- Topic:
- Packaging
- Created:
- 15-Mar-2013
- Post-History:
- 19-May-2013
- Superseded-By:
- 470
- Resolution:
- Distutils-SIG message
Table of Contents
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 pypi.python.org (PyPI) 패키지 색인에서의 설치를 더 빠르고 간단하며 안정적으로 만들기 위한 하위 호환 가능한 2단계 전환 프로세스를 제안합니다. 전환을 용이하게 하고 클라이언트 측의 마찰을 최소화하기 위해, 대부분의 기존 패키지에서 더 빠르고 신뢰할 수 있는 설치를 가능하게 하는 첫 번째 전환 단계의 혜택을 받는 데 distutils 또는 기존 설치 도구를 변경할 필요가 없습니다.
첫 번째 전환 단계에서는 패키지 유지 관리자가 현재 설치 도구에 제공할 릴리스 파일 링크를 쉽고 명시적으로 제어할 수단을 구현합니다. 첫 번째 단계에는 현재 패키지를 위한 분석 도구의 구현도 포함되며, 이를 통해 패키지 유지 관리자와 소통하고 릴리스 파일 링크를 제어하기 위한 기본 모드를 자동으로 설정할 수 있습니다. 또한 첫 번째 단계에서는 PyPI에 새로 등록된 프로젝트가 PyPI에 업로드된 릴리스 파일에 대한 링크만 제공하도록 기본 설정합니다.
두 번째 전환 단계는 최종 사용자 설치 도구와 관련되며, 이러한 도구는 PyPI에서 호스팅되는 릴리스 파일만 설치하도록 기본 설정하고 외부 릴리스 파일이 존재하는 경우 사용자에게 알리며 해당 외부 파일을 자동으로 사용할 수 있는 선택지를 제공합니다. 향후 외부 릴리스 파일은 체크섬 해시와 함께 등록되어 설치 도구가 최종 다운로드의 무결성을 확인할 수 있어야 합니다(PyPI에서 호스팅되는 릴리스 파일에는 항상 이러한 체크섬이 포함됩니다).
대체 PyPI 서버 구현은 전환 1단계의 새로운 단순 색인 제공 동작을 구현하여, 2단계에서 설치 도구가 해당 서버의 릴리스 링크를 외부 링크로 처리하지 않도록 해야 합니다.
근거
외부 호스팅의 역사와 동기
PyPI가 온라인에 등장했을 때, 릴리스 등록 기능은 제공했지만 릴리스 파일 자체를 호스팅할 기능은 없었습니다. 호스팅 기능이 추가되었을 때에도 자동 다운로드 도구는 아직 존재하지 않았습니다. Phillip Eby가 (setuptools를 통해) 자동 다운로드를 구현할 때, 사람들이 원하는 다운로드 호스트를 사용할 수 있도록 하는 방식을 선택했습니다. 외부 호스팅 패키지를 찾는 기능은 다음과 같이 구현되었습니다.
- 패키지에 대한 PyPI
simple/색인은 해당 패키지의 모든 릴리스에 대한 long_description 메타데이터에서 링크를 수집하여 찾은 모든 링크를 포함합니다. “Download-URL” 및 “Home-page” 메타데이터 필드의 링크에는 각각rel=download및rel=homepage속성이 부여됩니다. - 이러한 링크 중 대상이 설치 가능한 소스 또는 바이너리 배포판 형식으로 보이는 파일이며, 파일명이 “packagename-version.ARCHIVEEXT” 형식인 링크는 설치 도구에서 잠재적인 설치 후보로 간주합니다.
- 마찬가지로 “#egg=packagename-version” 프래그먼트가 접미사로 붙은 모든 링크도 설치 후보로 간주합니다.
- 또한 설치 도구는
rel=homepage및rel=download링크를 크롤링하며, 해당 링크가 HTML인 경우 자체적으로 위 형식의 릴리스 파일 링크를 수집합니다.
이 동작에 대한 전체 설명은 easy_install 문서를 참조하십시오. [1]
현재 PyPI에 색인된 대부분의 패키지는 릴리스 파일을 PyPI에서 호스팅합니다. PyPI의 전체 29,117개 프로젝트 중 2,581개(10% 미만)만 PyPI 외부에서만 사용할 수 있는 설치 가능한 파일에 대한 링크를 포함합니다. [2]
사람들이 외부 호스팅을 선택한 데에는 여러 가지 이유가 있습니다. [3] 몇 가지만 예로 들면 다음과 같습니다.
- 릴리스 프로세스와 스크립트가 이미 개발되어 있으며 외부 사이트에 업로드합니다
- 세계 일부 지역에서는 대용량 파일을 업로드하는 데 너무 오래 걸립니다
- 예를 들어 암호화 관련 소프트웨어에 대한 수출 제한
- 자체 사이트를 통해 오픈 소스 패키지를 제공하도록 요구하는 회사 정책
- 릴리스 프로세스에 PyPI 업로드를 통합하는 데 따르는 문제(릴리스 정책 때문)
- PyPI가 관리하는 통계와 다른 다운로드 통계를 원하는 경우
- PyPI의 신뢰성이 낮다는 인식
- PyPI가 파일 호스팅을 제공한다는 사실을 모름
이러한 이유가 현재에도 유효한지와 관계없이, 사람들이 파일을 외부에서 호스팅하기로 선택하는 데에는 분명한 역사적 배경이 있으며, 한동안은 그렇게 하는 것이 유일한 방법이기도 했습니다. 이 PEP는 오늘날에도 외부 호스팅을 선택할 타당한 이유가 일부 남아 있다는 입장입니다.
문제
오늘날 Python 패키지 설치 프로그램(pip, easy_install, buildout 및 기타 프로그램)은 외부에서 호스팅되는 파일이 없는 경우에도 비-PyPI URL을 여러 개 조회해야 하는 경우가 많습니다. 설치 프로그램은 pypi.python.org의 단순 색인 페이지를 조회할 뿐만 아니라, 패키지의 어떤 릴리스에서든 지정된 모든 홈페이지와 다운로드 페이지도 크롤링합니다. 설치 프로그램이 외부 사이트를 크롤링해야 하므로 설치 속도가 느려지고, 취약하고 신뢰할 수 없는 설치 프로세스가 됩니다. 또한 이러한 사이트와 패키지는 PEP 381 미러링 인프라에 참여하지 않으므로, 전 세계 자동 설치 프로세스의 신뢰성과 속도가 더욱 저하됩니다.
대부분의 패키지는 pypi.python.org [2]에서 직접 호스팅됩니다. 이러한 패키지도 홈페이지와 download-url이 지정되어 있으면 설치 프로그램이 여전히 이를 크롤링합니다. 많은 패키지 업로더는 패키지 메타데이터에 “homepage” 또는 “download-url”을 지정하면 모든 사용자의 설치 프로세스가 불필요하게 느려진다는 사실을 인지하지 못합니다.
제3자 사이트에 의존하면 자동 설치를 사용하는 사이트에 악성 패키지를 주입할 수 있는 공격 경로도 더 많이 열립니다. 간단한 공격은 이제 사용되지 않는 오래된 홈페이지 도메인을 확보하여 그곳에 악성 패키지를 배치하는 방식일 수 있습니다. 또한 설치 사이트와 다운로드 사이트 중 하나 사이에서 중간자 공격(Man-in-The-Middle, MITM)을 수행하면 설치 사이트에 악성 패키지를 주입할 수 있습니다. 많은 홈페이지와 다운로드 위치가 HTTPS가 아닌 HTTP를 사용하므로 이러한 공격은 실행하기 어렵지 않습니다. 설치 프로그램은 어차피 패키지의 홈페이지에 접속하므로, 파일을 외부에서 호스팅할 의도가 전혀 없었던 패키지에서도 이러한 MITM 공격이 쉽게 발생할 수 있습니다.
현재 패키지 관리자가 외부 링크 크롤링을 피할 방법은 모든 과거 릴리스의 홈페이지/download url 메타데이터를 제거하는 것뿐입니다. 이 작업을 수행하는 스크립트 [4]가 작성되었지만, PyPI 릴리스에서 유용한 메타데이터를 제거하므로 일반적인 해결책으로는 적합하지 않습니다.
“Homepage” 및 “Download-URL” 링크가 참조하는 사이트에서 추가 링크를 스크랩하지 않는다고 하더라도, 현재 시스템에서는 설치 도구가 해당 파일을 설치 후보로 자동 간주하지 않으면서 패키지 소유자가 long_description 메타데이터 필드의 설치 가능한 파일로 연결할 명확한 방법이 없습니다(/pypi/PKG에 패키지 문서로 표시되는 필드입니다). 반대로, 여러 외부 릴리스 파일을 메타데이터 필드에 넣지 않고 명시적으로 등록할 방법도 없습니다.
목표
이 PEP의 구현을 통해 달성해야 할 목표는 다음과 같습니다.
- 패키지 소유자는 PyPI가 설치 도구에 설치 후보로 제공하는 파일을 명시적으로 제어할 수 있어야 합니다. 패키지 소유자가 설치 파일로 명시적으로 지정하지 않은 링크를 광범위하고 불필요하게 크롤링하여 설치가 느려지거나 신뢰성이 떨어져서는 안 됩니다.
- 패키지 소유자가 PyPI 외부의 자체 호스팅 환경에서 릴리스 파일을 호스팅하도록 선택할 수 있어야 합니다. 사용자가 자동 설치 도구를 사용하여 이러한 릴리스의 설치를 요청하기 쉬워야 하며, 특히 외부 릴리스 파일이 체크섬 해시와 함께 등록된 경우에는 더욱 그래야 합니다.
- 자동 설치 도구는 외부에서 호스팅되는 패키지를 기본적으로 설치해서는 안 되며, 사용자가 명시적으로 권한을 부여하도록 요구해야 합니다. 도구가 이러한 패키지를 기본적으로 설치하지 않기로 거부할 때에는 설치 프로그램이 따라야 하는 외부 링크가 정확히 무엇인지와, 해당 링크를 따라가도록 도구에 권한을 부여하기 위해 사용자가 제공할 수 있는 옵션이 무엇인지 알려 주어야 합니다. PyPI는 설치 도구가 이를 단일 요청/응답 상호 작용 내에서 쉽게 구현하는 데 필요한 모든 메타데이터를 제공해야 합니다.
- 현재 상태에서 위의 사항으로 마이그레이션하는 과정은 점진적이어야 하며 호환성 손상을 최소화해야 합니다. 여기에는 기존 릴리스 프로세스에서 비-PyPI 호스팅에 업로드하는 패키지 소유자가 해당 릴리스 파일도 PyPI에 쉽게 업로드할 수 있도록 하는 도구가 포함됩니다.
해결책 / 두 전환 단계
첫 번째 전환 단계에서는 PyPI의 각 프로젝트에 “hosting-mode” 필드를 도입하여, 패키지 소유자가 기계 판독 가능한 simple/인덱스에서 현재 설치 도구에 제공할 릴리스 파일 링크를 명시적으로 제어할 수 있도록 합니다. 첫 번째 전환에서는 개별 얼리 어답터가 hosting-mode를 성공적으로 조작한 후, 자동화된 분석을 바탕으로 기존 패키지의 기본 hosting mode를 설정합니다. 이러한 자동 변경이 이루어지기 한 달 전에 유지 관리자에게 통지합니다. 첫 번째 전환 단계가 완료되면, 현재 존재하는 모든 릴리스 및 설치 과정과 도구가 계속 작동할 것으로 예상합니다. 남아 있는 오류나 문제는 개별 패키지의 설치와만 관련될 것으로 예상하며, 유지 관리자에게 연락할 수 없는 경우에도 패키지 유지 관리자나 PyPI 관리자가 쉽게 수정할 수 있습니다.
또한 첫 번째 단계에서는 simple/인덱스에서 제공되는 각 링크가 인덱스 자체에서 호스팅되는 경우(파일 제공에 CDN을 사용하는 인덱스에서는 별도의 도메인에 있을 수도 있음) rel="internal"로 명시적으로 표시합니다. 그렇게 표시되지 않은 모든 링크는 외부 링크로 간주합니다.
두 번째 전환 단계에서는 PyPI 클라이언트 설치 도구를 업데이트하여, 사용자가 외부 링크에서 설치하도록 허용하는 옵션을 지정하지 않는 한 기본적으로 rel="internal"패키지만 설치하도록 합니다. 설치 도구의 동작 방식에 대한 자세한 내용은 second transition phase에서 확인하십시오.
현재 PyPI가 아닌 사이트에서 릴리스 파일을 호스팅하는 패키지의 유지 관리자에게는 과거 및 향후 패키지 릴리스 파일을 쉽게 “재호스팅”할 수 있도록 지침과 도구를 제공합니다. 이 재호스팅 도구는 자동화된 hosting-mode 변경 사항을 패키지 유지 관리자에게 공지하기 전에 반드시 제공되어야 합니다.
구현
호스팅 모드
첫 번째 전환 단계의 기반은 패키지에 대한 PyPI 호스팅의 세 가지 “모드”를 도입하는 것이며, 이는 simple/인덱스에 생성되는 링크에 영향을 줍니다. 이러한 모드는 기계 판독 가능한 simple/인덱스를 생성하는 알고리즘을 변경함으로써, 설치 도구를 변경하지 않고 구현합니다.
모드는 다음과 같습니다.
pypi-scrape-crawl: history 에 설명된 것처럼 설치 도구를 위한 기계 판독 가능한 링크를 생성하는 현재 상황과 변경 사항이 없습니다.pypi-scrape: 이 모드의 패키지에서는simple/인덱스에 추가할 링크를 여전히 패키지 메타데이터에서 스크레이핑합니다. 그러나 “Home-page” 및 “Download-url” 링크에는rel=ext-homepage및rel=ext-download속성을 부여하며,rel=homepage및rel=download대신 사용합니다. 이로 인해(설치 도구를 변경할 필요 없이) 현재 설치 도구는 추가 후보 링크를 찾기 위해 이러한 링크를 따라가거나 스크레이핑하지 않습니다. PyPI에서 직접 호스팅되거나 PyPI 메타데이터에서 직접 연결된 설치 가능한 파일만 설치 대상으로 간주합니다. 설치 도구는 새로운 rel-attribution을 사용하여 외부 페이지를 크롤링하는 옵션을 제공하도록 발전할 수 있지만, 기본값으로 이를 사용해서는 안 됩니다.pypi-explicit: 이 모드의 패키지에서는 PyPI에 업로드된 릴리스 파일에 대한 링크와 패키지 소유자가 명시적으로 지정한 릴리스 파일에 대한 외부 링크만simple/인덱스에 추가합니다. PyPI는 패키지 소유자가 외부 릴리스 파일 URL을 제공할 수 있는 새로운 인터페이스를 제공합니다. 이러한 URL에는 “#hashtype=hashvalue” 형식의 URL 프래그먼트가 반드시 포함되어야 하며, 이는 외부로 연결된 파일의 해시를 지정합니다. 설치 도구는 이 해시를 사용하여 의도한 파일을 다운로드했는지 검증해야 합니다.
따라서 궁극적으로 PyPI의 모든 프로젝트를 pypi-explicit모드로 마이그레이션하면서도 설치 도구를 통해 외부에서 호스팅되는 릴리스 파일을 설치할 수 있기를 기대합니다. 호스팅 모드를 폐기하여 궁극적으로 pypi-explicit모드만 허용하는 것은 이 PEP에서 규정하지 않지만, 이 PEP에 설명된 전환 단계가 성공적으로 구현된 후 어느 시점에는 가능해질 것으로 예상합니다. 여전히 인기 있는 패키지의 유지 관리자에게 연락할 수 없어 패키지가 방치되는 경우에 대응하려면 새로운 처리 절차가 필요하므로, 폐기에는 이러한 절차가 필요할 것으로 예상합니다.
첫 번째 전환 단계(PyPI)
제안된 해결책은 여러 구현 및 커뮤니케이션 단계로 구성됩니다.
- 위에서 설명한 세 가지 모드를 PyPI에 구현하고, 패키지 소유자가 각 패키지의 모드를 선택하고 명시적인 외부 파일 URL을 등록할 수 있는 인터페이스를 제공합니다.
- 모든 모드의 패키지에 대해
simple/인덱스에서 인덱스가 호스팅하는 파일로 연결되는 링크를rel="internal"로 표시하여, 두 번째 단계에서 클라이언트 도구가 이러한 링크를 더 쉽게 구별할 수 있도록 합니다. - 모든
simple/인덱스 페이지에<meta name="api-version" value="2">HTML 태그를 추가하여, 클라이언트가rel="internal"메타데이터를 제공하는 인덱스와 이를 제공하지 않는 이전 인덱스를 구별할 수 있도록 합니다. - 새로 등록되는 모든 패키지는 기본적으로
pypi-explicit모드로 설정합니다(패키지 소유자는 원하는 경우 여전히 다른 모드로 전환할 수 있습니다). - (자동화된 분석 [2] 을 통해) 설치 가능한 모든 파일을 PyPI 자체에서 제공하는 패키지(A 그룹), 설치 가능한 모든 파일을 PyPI에서 제공하거나 PyPI 메타데이터에서 직접 링크하는 패키지(B 그룹), 그리고 외부 홈페이지/다운로드 HTML 페이지에서만 링크되는 설치 가능한 버전을 가진 패키지(C 그룹)를 판별합니다.
- A 그룹 프로젝트의 관리자에게는 한 달 후 해당 프로젝트가 자동으로
pypi-explicit모드로 설정될 것이라는 메일을 보내고, B 그룹 프로젝트의 관리자에게도 마찬가지로 해당 프로젝트가 자동으로pypi-scrape모드로 설정될 것이라는 메일을 보냅니다. 이 변경이 자신들의 프로젝트의 설치 가능성에는 전혀 영향을 미치지 않을 것으로 예상되지만, 사용자들에게는 더 빠르고 안전한 설치로 이어질 것이라는 점을 알립니다. 사용자에게 이익이 되도록 이 모드를 더 일찍 직접 설정하도록 권장합니다. - C 그룹 패키지의 관리자에게 해당 패키지의 호스팅 모드가
pypi-scrape-crawl이라는 사실을 알리는 메일을 보내고, 현재 크롤링되고 있는 URL 목록을 제시하며, 패키지를 PyPI에 직접 재호스팅하여pypi-explicit로 전환하거나, 최소한 PyPI 메타데이터에 릴리스 파일에 대한 직접 링크를 제공하여pypi-scrape로 전환할 것을 제안합니다. 이러한 전환을 돕기 위한 안내와 도구를 제공합니다.
두 번째 전환 단계(설치 도구)
두 번째 전환 단계에서는 설치 도구 관리자에게 두 차례의 업데이트를 릴리스하도록 요청합니다.
첫 번째 업데이트는 외부에 호스팅된 릴리스 파일(즉, 링크에 rel="internal"이 포함되지 않은 파일)이 다운로드 대상으로 선택될 경우, 정확히 어떤 프로젝트와 URL에서 이러한 일이 발생하는지에 대해 명확한 경고를 제공하고, 향후 버전에서는 외부 호스팅 다운로드가 기본적으로 비활성화될 것이라고 경고해야 합니다.
두 번째 업데이트는 기본 모드를 변경하여 rel="internal" 패키지 파일의 설치만 허용해야 하며, 외부에 호스팅된 패키지는 사용자가 옵션을 제공한 경우에만 설치를 허용해야 합니다.
설치 프로그램은 검증 가능한 외부 링크와 검증 불가능한 외부 링크를 구별해야 합니다. 검증 가능한 외부 링크는 다운로드한 파일의 무결성을 검증하는 데 사용할 수 있는 해시를 URL 프래그먼트(“#hashtype=hashvalue”)에 포함하는, PyPI simple/ 색인에서 설치 가능한 파일로 직접 연결되는 링크입니다. 검증 불가능한 외부 링크는 해시가 없거나, 외부 HTML에서 스크래핑되었거나, PyPI가 아닌 다른 출처(예: setuptools의 dependency_links 기능)를 통해 검색에 주입된 링크(설치 도구 사용자가 명시적으로 제공한 링크는 제외)를 말합니다.
설치 프로그램은 검증 가능한 외부 링크는 모두 설치할 수 있도록 허용하는 포괄적 옵션을 제공해야 합니다. 검증 불가능한 외부 링크는 사용자가 제공한 옵션이 어떤 외부 도메인을 사용할 수 있는지, 또는 어떤 특정 패키지 이름에 대해 외부 링크를 사용할 수 있는지를 정확히 지정한 경우에만 설치해야 합니다.
외부에서 호스팅되는 패키지의 다운로드가 기본 구성에서 허용되지 않는 경우, 사용자에게 설치를 성공시키는 방법에 대한 안내와 그 의미(패키지 색인의 일부가 아닌 사이트에서 파일이 다운로드된다는 점)에 대한 경고와 함께 알려야 합니다. 검증 불가능한 링크에 대해 주어지는 경고는 설치 프로그램이 다운로드한 파일의 무결성을 검증할 수 없다는 점을 명확히 명시해야 합니다. 검증 가능한 외부 링크에 대해 주어지는 경고는 파일이 외부 URL에서 다운로드되지만 파일 무결성은 체크섬으로 검증할 수 있다는 점만 언급하면 됩니다.
PyPI와 호환되는 대안 색인 구현체는 가능한 한 빨리 rel="internal" 메타데이터와 <meta name="api-version" value="2"> 태그를 제공하기 시작하도록 업그레이드해야 합니다. simple/ 페이지에 아직 메타 태그를 제공하지 않는 대안 색인의 경우, 설치 도구는 하위 호환되는 대체 동작을 제공해야 합니다(PEP 이전 시절처럼 링크를 내부 링크로 취급하고 경고를 제공).
외부 배포 URL 제출을 위한 API
새로운 배포 URL은 다음 URL로 HTTP POST를 수행하여 제출할 수 있습니다:
다음과 같은 폼 인코딩 데이터와 함께:
| Name | Value |
| :action | The string “urls” |
| name | The package name as a string |
| version | The release version as a string |
| new-url | The new URL to store |
| submit_new_url | The string “yes” |
POST는 PyPI에서 패키지를 관리할 권한이 있는 사용자의 사용자 이름과 비밀번호를 인코딩한 HTTP Basic Auth 헤더를 동반해야 합니다.
이 요청에 대한 HTTP 응답은 다음 중 하나입니다:
| Code | Meaning | URL submission implications |
| 200 | OK | Everything worked just fine |
| 400 | Bad request | Data provided for submission was malformed |
| 401 | Unauthorised | The username or password supplied were incorrect |
| 403 | Forbidden | User does not have permission to update the package information (not Owner or Maintainer) |
참고 문헌
감사의 말
서버 측 변경만으로 전환을 구현할 수 있는 정확한 정보와 기본 아이디어를 제공한 Phillip Eby에게 감사드립니다.
외부 호스팅에서 벗어나도록 밀어붙이고, 필요한 PyPI 변경 사항에 대한 Pull Request와 전환 1단계를 이끄는 분석 도구를 모두 구현하겠다고 나선 Donald Stufft에게 감사드립니다.
“외부 호스팅”을 없애는 것과 관련된 문제들을 깊이 고민해 준 Marc-Andre Lemburg, Alyssa Coghlan, 그리고 catalog-sig 전반에 감사드립니다.
Copyright
This document has been placed in the public domain.