#!/usr/bin/env python3 # SPDX-License-Identifier: BSD-2-Clause # # SPDX-FileCopyrightText: 2023 KDE e.V. # SPDX-FileCopyrightText: 2023 Ben Cooksley # SPDX-FileContributor: Ingo Klöcker import logging import os import sys import tarfile from pathlib import Path from tempfile import NamedTemporaryFile from sftpnotary import config, projects, util from sftpnotary.buildpublisher import PublishBuildJob from sftpnotary.exceptions import Error log = logging.getLogger("publishbuild") def parseCommandLine(): import argparse parser = argparse.ArgumentParser(description="Publish build artifacts to a remote server (usually a web server)") # 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("--platform", required=True, help="the platform the artifacts correspond to") parser.add_argument("folderToPublish", help="the folder containing build artifacts to be published; 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 publishBuild(buildArchiveFilePath: Path, projectPath: str, branch: str, platform: str) -> bool: with util.connectToSftpServer() as sftp: job = PublishBuildJob( sftp, buildArchiveFilePath=buildArchiveFilePath, token=os.environ.get("CI_JOB_TOKEN", ""), projectPath=projectPath, branch=branch, platform=platform, ) try: job.start() job.waitForCompletion() except Exception as e: # or more specific exception log.error("Error: PublishBuildJob failed", exc_info=e) for logFile in job.logFiles: if logFile.name == "task.log": logTaskLog(logFile) if not job.success: log.error("Error: Publishing the build artifacts failed") return job.success def main() -> int: options = parseCommandLine() config.loadConfig(options.config) util.setUpClientLogging(log, options.verbose) projectSettingsLocation = config.settings.get("General", "ProjectSettings", "buildpublisher-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 the build projectPath = os.environ["CI_PROJECT_PATH"] branch = os.environ["CI_COMMIT_REF_NAME"] platform = options.platform 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 build files and trigger the publishing process with NamedTemporaryFile(delete=False, mode="wb") as artifactArchive: # Open our temporary file as a tarball archive = tarfile.open(fileobj=artifactArchive, 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 build artifact 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 # We have to use close() to ensure the file handle is fully closed otherwise 'sftp' on Windows will # not be allowed to open the file archive.close() artifactArchive.close() success = publishBuild(Path(artifactArchive.name), projectPath, branch, platform) # Cleanup os.remove(artifactArchive.name) 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)