#!/usr/bin/env python3 # SPDX-License-Identifier: BSD-2-Clause # # SPDX-FileCopyrightText: 2023 KDE e.V. # SPDX-FileContributor: Ingo Klöcker # SPDX-FileCopyrightText: 2023 Julius Künzel import logging import os import sys import tarfile from pathlib import Path from tempfile import TemporaryDirectory from typing import Optional from sftpnotary import config, projects, util from sftpnotary.exceptions import Error from sftpnotary.macappnotarizer import NotarizeMacAppJob log = logging.getLogger("notarizemacapp") def parseCommandLine(): import argparse parser = argparse.ArgumentParser(description="Notarize macOS apps with signing service") # debug options parser.add_argument("-v", "--verbose", action="count", default=0, help="increase the verbosity") parser.add_argument("--config") parser.add_argument("appfile", help="the .dmg file or .app (inside tar archive) to notarize") 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 notarizeMacApp(app: Path, logFolder: Path, projectPath: Optional[str], branch: Optional[str]) -> bool: with util.connectToSftpServer() as sftp: job = NotarizeMacAppJob( sftp, macAppFilePath=app, 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: NotarizeMacAppJob failed", exc_info=e) for logFile in job.logFiles: if logFile.name == "task.log": logTaskLog(logFile) if not job.success: log.error("Error: Notarizing macOS app failed") return job.success def main() -> int: options = parseCommandLine() config.loadConfig(options.config) util.setUpClientLogging(log, options.verbose) projectSettingsLocation = config.settings.get("General", "ProjectSettings", "macappnotarizer-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 notarization. Skipping.") return 0 # Check if it makes sense to attempt notarizing the macOS file 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 notarization. Skipping.\n You can clear it by adding the branch to {projectSettingsLocation}""" ) return 0 appFilePath = Path(options.appfile) if appFilePath.suffix == ".app": # Build a tarball since .app files are basically special folders with TemporaryDirectory() as tempArchiveDir: appArchivePath = Path(tempArchiveDir) / "shuttleArchive" with tarfile.open(name=appArchivePath, mode="w") as archive: archive.add(appFilePath, arcname=appFilePath.name, recursive=True) success = notarizeMacApp(appArchivePath, appFilePath.parent, projectPath, branch) if success: # notarizeMacApp overwrites the archive with the unsigned content # so we can just extract the archive at the same location again util.extractTarArchive(appArchivePath, appFilePath.parent) else: success = notarizeMacApp(appFilePath, appFilePath.parent, 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)