#!/usr/bin/env python3 # SPDX-License-Identifier: BSD-2-Clause # # SPDX-FileCopyrightText: 2024 KDE e.V. # SPDX-FileContributor: Ingo Klöcker import logging import os import sys from pathlib import Path from typing import List from sftpnotary import config, projects, util from sftpnotary.exceptions import Error from sftpnotary.windowsbinariessigner import SignWindowsBinariesJob log = logging.getLogger("signwindowsbinaries") def parseCommandLine(): import argparse parser = argparse.ArgumentParser(description="Sign Windows binaries with signing service") # 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( "--log-folder", dest="logFolder", default=Path.cwd(), type=Path, help="the location where the log files should be saved; defaults to the current folder", ) parser.add_argument( "--files-from", dest="filesFrom", metavar="FILE", help="read list of binaries to sign from FILE" ) parser.add_argument("files", nargs="*", metavar="FILE", help="the binaries to sign") options = parser.parse_args() if options.filesFrom is None and not options.files: raise Error("No binaries to sign given.") if options.filesFrom is not None and options.files: raise Error("Binaries to sign cannot be given on command line and via --files-from.") 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 signWindowsBinaries(binaries: List[Path], logFolder: Path, projectPath: str, branch: str) -> bool: with util.connectToSftpServer() as sftp: job = SignWindowsBinariesJob( sftp, filePaths=binaries, logFolder=logFolder, token=os.environ.get("CI_JOB_TOKEN"), projectPath=projectPath, branch=branch, ) try: job.start() job.waitForCompletion() except Exception as e: # or more specific exception log.error("Error: SignWindowsBinariesJob failed", exc_info=e) for logFile in job.logFiles: if logFile.name == "task.log": logTaskLog(logFile) if not job.success: log.error("Error: Signing Windows binaries failed") return job.success def main() -> int: options = parseCommandLine() config.loadConfig(options.config) util.setUpClientLogging(log, options.verbose) projectSettingsLocation = config.settings.get("General", "ProjectSettings", "windowsbinariessigner-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 signing. Skipping.") return 0 # Check if it makes sense to attempt signing the files projectPath = os.environ["CI_PROJECT_PATH"] branch = os.environ["CI_COMMIT_REF_NAME"] if not projects.settings.exists(projectPath, branch): log.info( f"""Branch '{branch}' of project '{projectPath}' is not cleared for signing. Skipping.\n You can clear it by adding the branch to {projectSettingsLocation}""" ) return 0 if options.files: binaries = [Path(s) for s in options.files] else: with open(options.filesFrom, encoding="utf-8") as f: filenames = [line.strip() for line in f] binaries = [Path(s) for s in filenames if s] success = signWindowsBinaries(binaries, options.logFolder.absolute(), projectPath, branch) 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)