OpenMiInk/third_party/ci-notary-service/publishonpypi.py
Hellings b8c8ff98e2 Hi
2026-10-16 09:05:56 -04:00

128 lines
4.3 KiB
Python

#!/usr/bin/env python3
# SPDX-License-Identifier: BSD-2-Clause
#
# SPDX-FileCopyrightText: 2023 KDE e.V.
# SPDX-FileCopyrightText: 2023 Ben Cooksley <bcooksley@kde.org>
# SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
import logging
import os
import sys
import tarfile
from pathlib import Path
from tempfile import NamedTemporaryFile
from sftpnotary import config, projects, util
from sftpnotary.exceptions import Error
from sftpnotary.pypipublisher import PublishPyPiJob
log = logging.getLogger("publishonpypi")
def parseCommandLine():
import argparse
parser = argparse.ArgumentParser(description="Publish Python packages to a PyPi repository")
# debug options
parser.add_argument("-v", "--verbose", action="count", default=0, help="increase the verbosity")
parser.add_argument("--config", required=True, help="the configuration to use; required")
parser.add_argument(
"--repository", required=True, help="the repository the package should be published to; required"
)
parser.add_argument(
"folderToPublish", help='the folder containing the package to be published (eg. the "dist" folder); required'
)
options = parser.parse_args()
return options
def logTaskLog(logFile: Path):
log.info("Logs of remote signing:")
with open(logFile, "r", encoding="utf-8") as f:
for line in f:
log.info(line.rstrip())
def publishPyPi(archiveFilePath: Path, projectPath: str, branch: str, repository: str, logDestination: Path) -> bool:
with util.connectToSftpServer() as sftp:
job = PublishPyPiJob(
sftp,
archiveFilePath=archiveFilePath,
token=os.environ.get("CI_JOB_TOKEN"),
projectPath=projectPath,
branch=branch,
repository=repository,
)
try:
job.start()
job.waitForCompletion(logDestination.parent)
except Exception as e: # or more specific exception
log.error("Error: PublishPyPiJob failed", exc_info=e)
for logFile in job.logFiles:
if logFile.name == "task.log":
logTaskLog(logFile)
if not job.success:
log.error("Error: Publishing to PyPi failed")
return job.success
def main() -> int:
options = parseCommandLine()
config.loadConfig(options.config)
util.setUpClientLogging(log, options.verbose)
projectSettingsLocation = config.settings.get("General", "ProjectSettings", "pypipublisher-projects.yaml")
util.loadProjectSettings(
projectSettingsLocation,
relativeTo=Path(options.config).absolute().parent,
)
# Ignore merge request pipelines
if os.environ.get("CI_PIPELINE_SOURCE") == "merge_request_event":
log.info("Merge request pipelines are not cleared for publishing. Skipping.")
return 0
# Check if it makes sense to attempt publishing to pypi
projectPath = os.environ["CI_PROJECT_PATH"]
branch = os.environ["CI_COMMIT_REF_NAME"]
repository = options.repository
if not projects.settings.exists(projectPath, branch):
log.info(
f"""Branch '{branch}' of project '{projectPath}' is not cleared for publishing. Skipping.\n
You can clear it by adding the branch to {projectSettingsLocation}"""
)
return 0
# Build a tarball of the wheel files and trigger the publishing process
with NamedTemporaryFile() as wheelArchive:
# Open our temporary file as a tarball
archive = tarfile.open(fileobj=wheelArchive, mode="w")
# Determine the path to be archived and the files to be included
pathToArchive = Path(options.folderToPublish)
filesToInclude = os.listdir(pathToArchive)
# Start populating it with the wheel files
for filename in filesToInclude:
fullPath = pathToArchive / filename
archive.add(fullPath, arcname=filename, recursive=True)
# Close it out to ensure everything is flushed to disk
archive.close()
wheelArchive.flush()
success = publishPyPi(Path(wheelArchive.name), projectPath, branch, repository, pathToArchive)
return 0 if success else 1
if __name__ == "__main__":
try:
sys.exit(main())
except Error as e:
print("Error: %s" % e, file=sys.stderr)
sys.exit(1)