diff --git a/Dockerfile b/Dockerfile index a308f4b..f519d85 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,6 +27,7 @@ WORKDIR /app COPY LICENSE.md . COPY twine-upload.sh . COPY print-hash.py . +COPY print-pkg-name.py . COPY oidc-exchange.py . COPY attestations.py . diff --git a/print-pkg-name.py b/print-pkg-name.py new file mode 100644 index 0000000..a975385 --- /dev/null +++ b/print-pkg-name.py @@ -0,0 +1,31 @@ +import pathlib +import sys + +from packaging import utils + + +def debug(msg: str): + print(f'::debug::{msg.title()}', file=sys.stderr) + + +packages_dir = pathlib.Path(sys.argv[1]).resolve().absolute() + +wheel_file_names = [ + f.name for f in packages_dir.iterdir() if f.suffix == '.whl' +] +sdist_file_names = [ + f.name for f in packages_dir.iterdir() if f.suffix == '.gz' +] + +# Parse the package name from the distribution files and print it. On error, +# don't print anything. +if wheel_file_names: + try: + print(utils.parse_wheel_filename(wheel_file_names[0])[0]) + except utils.InvalidWheelFilename: + debug(f'Invalid wheel filename: {wheel_file_names[0]}') +elif sdist_file_names: + try: + print(utils.parse_sdist_filename(sdist_file_names[0])[0]) + except utils.InvalidSdistFilename: + debug(f'Invalid sdist filename: {sdist_file_names[0]}') diff --git a/requirements/runtime.in b/requirements/runtime.in index 50f52b6..5158d5c 100644 --- a/requirements/runtime.in +++ b/requirements/runtime.in @@ -12,3 +12,6 @@ requests # NOTE: Used to generate attestations. pypi-attestations ~= 0.0.11 sigstore ~= 3.2.0 + +# NOTE: Used to detect the PyPI package name from the distribution files +packaging diff --git a/twine-upload.sh b/twine-upload.sh index 12b57b2..9fcfd91 100755 --- a/twine-upload.sh +++ b/twine-upload.sh @@ -41,6 +41,10 @@ INPUT_SKIP_EXISTING="$(get-normalized-input 'skip-existing')" INPUT_PRINT_HASH="$(get-normalized-input 'print-hash')" INPUT_ATTESTATIONS="$(get-normalized-input 'attestations')" +REPOSITORY_NAME="$(echo ${GITHUB_REPOSITORY} | cut -d'/' -f2)" +WORKFLOW_FILENAME="$(echo ${GITHUB_WORKFLOW_REF} | cut -d'/' -f5- | cut -d'@' -f1)" +PACKAGE_NAME="$(python /app/print-pkg-name.py ${INPUT_PACKAGES_DIR%%/})" + PASSWORD_DEPRECATION_NUDGE="::error title=Password-based uploads disabled::\ As of 2024, PyPI requires all users to enable Two-Factor \ Authentication. This consequently requires all users to switch \ @@ -64,6 +68,20 @@ The workflow was run with 'attestations: true' input, but the specified \ repository URL does not support PEP 740 attestations. As a result, the \ attestations input is ignored." +if [[ ! "${INPUT_REPOSITORY_URL}" =~ pypi\.org || -z "${PACKAGE_NAME}" ]] ; then + TRUSTED_PUBLISHING_MAGIC_LINK_NUDGE="" +else + if [[ "${INPUT_REPOSITORY_URL}" =~ test\.pypi\.org ]] ; then + INDEX_URL="https://test.pypi.org" + else + INDEX_URL="https://pypi.org" + fi + TRUSTED_PUBLISHING_MAGIC_LINK_NUDGE="::warning title=Create a Trusted Publisher::\ +A new Trusted Publisher for the currently running publishing workflow can be created \ +by accessing the following link while logged-in as a maintainer of the package: \ +${INDEX_URL}/manage/project/${PACKAGE_NAME}/settings/publishing/?provider=github&owner=${GITHUB_REPOSITORY_OWNER}&repository=${REPOSITORY_NAME}&workflow_filename=${WORKFLOW_FILENAME}" +fi + [[ "${INPUT_USER}" == "__token__" && -z "${INPUT_PASSWORD}" ]] \ && TRUSTED_PUBLISHING=true || TRUSTED_PUBLISHING=false @@ -96,6 +114,7 @@ elif [[ "${INPUT_USER}" == '__token__' ]]; then if [[ "${INPUT_REPOSITORY_URL}" =~ pypi\.org ]]; then echo "${TRUSTED_PUBLISHING_NUDGE}" + echo "${TRUSTED_PUBLISHING_MAGIC_LINK_NUDGE}" fi else echo \ @@ -105,6 +124,7 @@ else if [[ "${INPUT_REPOSITORY_URL}" =~ pypi\.org ]]; then echo "${PASSWORD_DEPRECATION_NUDGE}" echo "${TRUSTED_PUBLISHING_NUDGE}" + echo "${TRUSTED_PUBLISHING_MAGIC_LINK_NUDGE}" exit 1 fi fi