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 서버 구현은 2단계에서 설치 도구가 자신의 릴리스 링크를 외부 링크로 취급하지 않도록 전환 1단계의 새로운 simple 색인 제공 방식을 구현해야 합니다.
근거
외부 호스팅의 역사와 동기
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개 중 PyPI 밖에서만 구할 수 있는 설치 가능 파일에 대한 링크를 포함한 프로젝트는 2,581개(10% 미만)에 불과합니다. [2]
사람들이 외부 호스팅을 선택한 데에는 많은 이유가 있습니다 [3]. 몇 가지만 들면 다음과 같습니다.
- 릴리스 프로세스와 스크립트가 이미 개발되어 있으며 외부 사이트에 업로드합니다
- 세계의 일부 지역에서는 큰 파일을 업로드하는 데 너무 오랜 시간이 걸립니다
- 예를 들어 암호화 관련 소프트웨어에 대한 수출 제한
- 오픈 소스 패키지를 자체 사이트를 통해 제공하도록 요구하는 회사 정책
- (릴리스 정책 때문에) PyPI 업로드를 자신의 릴리스 프로세스에 통합하기 어려운 문제
- PyPI가 관리하는 것과는 다른 다운로드 통계를 원하는 경우
- PyPI의 신뢰성이 낮다는 인식
- PyPI가 파일 호스팅을 제공한다는 사실을 모름
이러한 이유가 오늘날에도 타당한지와는 관계없이, 사람들이 파일을 외부에 호스팅하기로 선택하게 된 역사적 배경은 분명히 있으며, 한동안은 그것이 유일한 방법이기도 했습니다. 이 PEP는 오늘날에도 외부 호스팅에 타당한 이유가 일부 남아 있다는 입장을 취합니다.
문제
현재 Python 패키지 설치 프로그램(pip, easy_install, buildout 등)은 외부에 호스팅된 파일이 전혀 없더라도 PyPI가 아닌 여러 URL을 조회해야 하는 경우가 많습니다. 설치 프로그램은 pypi.python.org의 simple 색인 페이지를 조회할 뿐 아니라, 패키지의 모든 릴리스에서 한 번이라도 지정된 홈페이지와 다운로드 페이지도 전부 크롤링합니다. 설치 프로그램이 외부 사이트를 크롤링해야 하므로 설치가 느려지고 설치 과정이 취약하며 신뢰하기 어려워집니다. 또한 그러한 사이트와 패키지는 PEP 381 미러링 인프라에 참여하지 않으므로, 전 세계에서 이루어지는 자동화된 설치 과정의 신뢰성과 속도가 더욱 떨어집니다.
대부분의 패키지는 pypi.python.org [2]에 직접 호스팅됩니다. 이러한 패키지라도 홈페이지와 download-url이 지정되어 있으면 설치 프로그램은 여전히 이를 크롤링합니다. 많은 패키지 업로더는 패키지 메타데이터에 “homepage”나 “download-url”을 지정하면 모든 사용자의 설치 과정이 불필요하게 느려진다는 사실을 알지 못합니다.
또한 서드파티 사이트에 의존하면 자동 설치를 사용하는 사이트에 악성 패키지를 주입할 수 있는 공격 경로가 늘어납니다. 간단한 공격으로는 지금은 사용되지 않는 오래된 홈페이지 도메인을 확보한 뒤 그곳에 악성 패키지를 올려 두는 방법이 있을 수 있습니다. 게다가 설치 사이트와 다운로드 사이트 중 어느 하나 사이에서 중간자(Man-in-The-Middle, MITM) 공격을 수행하면 설치 사이트에 악성 패키지를 주입할 수 있습니다. 많은 홈페이지와 다운로드 위치가 HTTPS가 아닌 HTTP를 사용하므로 이러한 공격을 시도하기는 어렵지 않습니다. 설치 프로그램은 어차피 홈페이지에 접속하므로, 파일을 외부에 호스팅할 의도가 전혀 없었던 패키지에서도 이러한 MITM 공격이 쉽게 일어날 수 있습니다.
현재 패키지 유지 관리자가 외부 링크 크롤링을 피하는 방법은 과거의 모든 릴리스에서 홈페이지/다운로드 URL 메타데이터를 전부 제거하는 것뿐입니다. 이 작업을 수행하는 스크립트 [4]가 작성되어 있기는 하지만, PyPI 릴리스에서 유용한 메타데이터를 제거하므로 좋은 일반적 해결책은 아닙니다.
“Homepage”와 “Download-URL” 링크가 가리키는 사이트에서 추가 링크를 수집하지 않는다 하더라도, 현재 체계에서는 패키지 소유자가 long_description 메타데이터 필드(/pypi/PKG에 패키지 문서로 표시됨)에서 설치 가능한 파일로 링크를 걸 때 설치 도구가 그 파일을 자동으로 설치 후보로 간주하지 않게 할 뚜렷한 방법이 없습니다. 반대로, 여러 외부 릴리스 파일을 메타데이터 필드에 넣지 않고 명시적으로 등록할 방법도 없습니다.
목표
이 PEP를 구현하여 달성하려는 목표는 다음과 같습니다:
- 패키지 소유자는 PyPI가 설치 프로그램 도구에 설치 후보로 제시하는 파일을 명시적으로 제어할 수 있어야 합니다. 패키지 소유자가 설치 파일로 명시적으로 지정하지 않은 링크를 광범위하고 불필요하게 크롤링하느라 설치가 느려지거나 신뢰성이 떨어져서는 안 됩니다.
- 패키지 소유자가 PyPI 외부의 자체 호스팅에 릴리스 파일을 호스팅하는 방식을 선택할 수 있는 가능성은 계속 유지되어야 합니다. 사용자가 자동화된 설치 프로그램 도구를 사용하여 그러한 릴리스의 설치를 쉽게 요청할 수 있어야 하며, 특히 외부 릴리스 파일이 체크섬 해시와 함께 등록된 경우에는 더욱 그러해야 합니다.
- 자동화된 설치 프로그램 도구는 외부에서 호스팅되는 패키지를 기본적으로 설치해서는 안 되며, 그렇게 하려면 사용자의 명시적인 승인을 요구해야 합니다. 도구가 기본 설정 때문에 이러한 패키지의 설치를 거부할 때는, 설치 프로그램이 따라가야 하는 외부 링크가 정확히 무엇인지, 그리고 도구가 해당 링크를 따라가도록 승인하려면 사용자가 어떤 옵션을 지정하면 되는지 알려 주어야 합니다. PyPI는 설치 프로그램 도구가 이를 쉽게, 그리고 단일 요청/응답 상호작용 안에서 구현할 수 있도록 필요한 모든 메타데이터를 제공해야 합니다.
- 현재 상태에서 위의 사항으로의 이전은 점진적이어야 하며 호환성 문제를 최소화해야 합니다. 여기에는 기존 릴리스 과정에서 PyPI가 아닌 호스팅에 업로드하던 패키지 소유자가 해당 릴리스 파일을 PyPI에도 쉽게 업로드할 수 있게 해 주는 도구가 포함됩니다.
해결책 / 두 단계의 전환
첫 번째 전환 단계에서는 PyPI의 각 프로젝트에 “hosting-mode” 필드를 도입합니다. 이를 통해 패키지 소유자는 기계가 읽을 수 있는 simple/ 색인에서 현재의 설치 도구에 어떤 릴리스 파일 링크를 제공할지 명시적으로 제어할 수 있습니다. 첫 번째 전환에서는 개별 얼리 어답터들이 호스팅 모드를 성공적으로 변경해 본 뒤, 자동화된 분석을 바탕으로 기존 패키지의 기본 호스팅 모드를 설정합니다. 유지 관리자는 이러한 자동 변경이 있기 한 달 전에 통지를 받습니다. 첫 번째 전환 단계가 완료된 시점에도 현재 존재하는 모든 릴리스 및 설치 절차와 도구는 계속 동작할 것으로 예상됩니다. 남아 있는 오류나 문제는 개별 패키지의 설치에만 관련될 것으로 예상됩니다. 이러한 오류나 문제는 패키지 유지 관리자가 쉽게 바로잡을 수 있으며, 유지 관리자와 연락이 닿지 않는 경우에는 PyPI 관리자가 바로잡을 수 있습니다.
또한 첫 번째 단계에서는 simple/ 색인에서 제공되는 각 링크가 색인 자체에서 호스팅되는 경우, 해당 링크를 rel="internal"로 명시적으로 표시합니다. 색인이 파일 제공에 CDN을 사용하여 파일이 별도의 도메인에 있는 경우에도 마찬가지입니다. 이렇게 표시되지 않은 링크는 모두 외부 링크로 간주합니다.
두 번째 전환 단계에서는 PyPI 클라이언트 설치 도구를 갱신하여, 사용자가 외부 링크에서의 설치를 허용하는 옵션을 지정하지 않는 한 기본적으로 rel="internal" 패키지만 설치하도록 해야 합니다. 설치 프로그램의 동작 방식에 대한 자세한 내용은 second transition phase를 참조하십시오.
현재 PyPI가 아닌 사이트에 릴리스 파일을 호스팅하고 있는 패키지의 유지 관리자는 과거 및 향후 패키지 릴리스 파일을 쉽게 “재호스팅”할 수 있도록 안내와 도구를 제공받아야 합니다. 이 재호스팅 도구는 자동 호스팅 모드 변경을 패키지 유지 관리자에게 공지하기 전에 반드시(MUST) 제공되어야 합니다.
구현
호스팅 모드
첫 번째 전환 단계의 기반은 패키지에 대한 PyPI 호스팅의 세 가지 “모드”를 도입하는 것입니다. 이 모드는 simple/ 색인에 어떤 링크가 생성될지에 영향을 줍니다. 이 모드들은 기계가 읽을 수 있는 simple/ 색인을 생성하는 알고리즘을 변경하는 방식으로 구현되므로, 설치 도구를 변경할 필요가 없습니다.
모드는 다음과 같습니다.
pypi-scrape-crawl: history에 설명된 것처럼 설치 도구를 위해 기계가 읽을 수 있는 링크를 생성하는 현재 방식에서 달라지는 것이 없습니다.pypi-scrape: 이 모드의 패키지에서는simple/색인에 추가될 링크를 여전히 패키지 메타데이터에서 스크레이핑합니다. 다만 “Home-page”와 “Download-url” 링크에는rel=homepage및rel=download대신rel=ext-homepage및rel=ext-download속성이 부여됩니다. 그 결과, 설치 도구를 변경할 필요 없이 현재의 설치 도구는 이 링크들을 따라가서 추가 후보 링크를 스크레이핑하지 않게 됩니다. 즉, PyPI에서 직접 호스팅되거나 PyPI 메타데이터에서 직접 링크된 설치 가능한 파일만 설치 대상으로 고려됩니다. 설치 도구는 새로운 rel 속성을 사용해 외부 페이지를 크롤링하는 옵션을 제공하도록 발전할 수 있지만(MAY), 이를 기본값으로 해서는 안 됩니다(MUST NOT).pypi-explicit: 이 모드의 패키지에서는 PyPI에 업로드된 릴리스 파일에 대한 링크와, 패키지 소유자가 명시적으로 지정한 릴리스 파일에 대한 외부 링크만simple/색인에 추가됩니다. PyPI는 패키지 소유자가 외부 릴리스 파일 URL을 제공할 수 있는 새로운 인터페이스를 제공합니다. 이러한 URL에는 외부 링크된 파일의 해시를 지정하는 “#hashtype=hashvalue” 형태의 URL 프래그먼트가 반드시 포함되어야 하며(MUST), 설치 프로그램 도구는 의도한 파일을 내려받았는지 검증하는 데 이 해시를 반드시 사용해야 합니다(MUST).
따라서 설치 프로그램 도구를 통해 외부에서 호스팅되는 릴리스 파일을 설치할 수 있는 능력을 유지하면서도, 궁극적으로는 PyPI의 모든 프로젝트를 pypi-explicit 모드로 이전할 수 있기를 기대합니다. 궁극적으로 pypi-explicit 모드만 허용하도록 호스팅 모드를 폐지하는 것은 이 PEP에서 규정하지 않습니다(NOT REGULATED). 다만 이 PEP에서 설명하는 전환 단계들이 성공적으로 구현된 후 어느 시점에는 가능해질 것으로 예상됩니다. 여전히 인기 있는 패키지의 유지 관리자와 연락이 닿지 않는 경우가 있으므로, 폐지를 위해서는 버려진 패키지를 처리하기 위한 새로운 절차가 필요할 것으로 예상됩니다.
첫 번째 전환 단계 (PyPI)
제안하는 해결책은 여러 구현 및 소통 단계로 구성됩니다.
- 위에서 설명한 세 가지 모드를 PyPI에 구현하고, 패키지 소유자가 각 패키지의 모드를 선택하고 명시적인 외부 파일 URL을 등록할 수 있는 인터페이스를 제공합니다.
- 모든 모드의 패키지에 대해,
simple/색인에서 색인이 호스팅하는 파일에 대한 링크에rel="internal"레이블을 붙입니다. 이렇게 하면 두 번째 단계에서 클라이언트 도구가 이 링크들을 더 쉽게 구별할 수 있습니다. - 모든
simple/색인 페이지에 HTML 태그<meta name="api-version" value="2">를 추가합니다. 이렇게 하면 클라이언트가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" 패키지 파일의 설치만 허용하도록 변경하고, 외부에서 호스팅되는 패키지는 사용자가 옵션을 지정한 경우에만 설치를 허용해야 합니다.
설치 프로그램은 검증 가능한 외부 링크와 검증 불가능한 외부 링크를 구분해야 합니다. 검증 가능한 외부 링크란 PyPI simple/ 색인에서 설치 가능한 파일로 연결되는 직접 링크로서, 다운로드한 파일의 무결성을 검증하는 데 사용할 수 있는 해시를 URL 프래그먼트(“#hashtype=hashvalue”)에 포함하는 링크입니다. 검증 불가능한 외부 링크란 해시가 없거나, 외부 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 변경 사항에 대한 풀 리퀘스트와 전환 1단계를 이끌 분석 도구를 모두 구현하겠다고 제안해 준 Donald Stufft에게 감사드립니다.
“외부 호스팅”을 없애는 것과 관련된 문제들을 깊이 검토해 주신 Marc-Andre Lemburg, Alyssa Coghlan, 그리고 catalog-sig 전반에 감사드립니다.
Copyright
This document has been placed in the public domain.