133 lines
4.7 KiB
Python
133 lines
4.7 KiB
Python
# 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 dataclasses
|
|
import logging
|
|
import os
|
|
import tarfile
|
|
from pathlib import Path
|
|
from tempfile import TemporaryDirectory
|
|
from typing import Optional
|
|
|
|
from sftpnotary import projects, util
|
|
from sftpnotary.core import Job, Request, Response, TaskProcessor, Worker
|
|
from sftpnotary.exceptions import Error
|
|
from sftpnotary.sftp import SFTPClient, SFTPError
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class PublishWebsiteRequest(Request):
|
|
websiteArchiveFileName: Optional[str] = None
|
|
|
|
def validate(self):
|
|
util.validateFileName(self.websiteArchiveFileName, strict=True)
|
|
|
|
|
|
PublishWebsiteResponse = Response
|
|
|
|
|
|
class PublishWebsiteJob(Job):
|
|
def __init__(
|
|
self,
|
|
sftp: SFTPClient,
|
|
websiteArchiveFilePath: Path,
|
|
token: str,
|
|
projectPath: str,
|
|
branch: str,
|
|
):
|
|
super().__init__(sftp)
|
|
|
|
self.websiteArchiveFilePath = websiteArchiveFilePath
|
|
self.request = PublishWebsiteRequest(
|
|
websiteArchiveFileName=None,
|
|
token=token,
|
|
projectPath=projectPath,
|
|
branch=branch,
|
|
)
|
|
self.resultPath = self.websiteArchiveFilePath.parent
|
|
|
|
def start(self):
|
|
websiteArchiveFileName = self.websiteArchiveFilePath.name
|
|
self.sftp.upload(self.websiteArchiveFilePath, self.task.path() / websiteArchiveFileName)
|
|
self.request.websiteArchiveFileName = websiteArchiveFileName
|
|
self.task.putRequest(self.request)
|
|
self.taskQueue.commitTask(self.task)
|
|
|
|
def waitForCompletion(self):
|
|
self.taskQueue.waitForCompletion(self.task)
|
|
log.debug(f"Fetching response of task '{self.task.id}' ...")
|
|
response = None
|
|
try:
|
|
response = self.task.fetchResponse(PublishWebsiteResponse)
|
|
except SFTPError as e:
|
|
log.error("Error: %s", e)
|
|
if response is None:
|
|
log.error("Error: Got no response. Task failed.")
|
|
else:
|
|
self.success = True
|
|
self.downloadLogFiles(self.resultPath)
|
|
self.taskQueue.removeTask(self.task)
|
|
|
|
|
|
class PublishWebsiteProcessor(TaskProcessor):
|
|
requestClass = PublishWebsiteRequest
|
|
|
|
def getProjectSetting(self, name, required=True):
|
|
value = projects.settings.get(self.request.projectPath, self.request.branch, name)
|
|
if required and not value:
|
|
raise Error(
|
|
f"Project setting '{name}' not set for branch {self.request.branch} "
|
|
f"of project {self.request.projectPath}"
|
|
)
|
|
return value
|
|
|
|
def doProcess(self):
|
|
# Get the details for where we will publish the website to
|
|
rsyncDestination = self.getProjectSetting("rsyncDestination")
|
|
websiteDomain = self.getProjectSetting("domain")
|
|
|
|
# Make sure the website domain we have been given is valid...
|
|
if not websiteDomain:
|
|
raise Error(f"Website domain has not been set for branch {self.request.branch} and is mandatory")
|
|
|
|
with TemporaryDirectory() as workPathName:
|
|
workPath = Path(workPathName)
|
|
|
|
localWebsiteArchiveFilePath = workPath / self.request.websiteArchiveFileName
|
|
self.sftp.download(self.task.path() / self.request.websiteArchiveFileName, localWebsiteArchiveFilePath)
|
|
|
|
# Setup a path for us to unpack the website archive into...
|
|
unpackedWebsitePath = workPath / "unpacked-site"
|
|
os.makedirs(unpackedWebsitePath)
|
|
|
|
# Unpack the website archive....
|
|
archive = tarfile.open(name=localWebsiteArchiveFilePath, mode="r")
|
|
# Set "nosec B202" because of https://github.com/PyCQA/bandit/issues/1038#issuecomment-1635863856
|
|
archive.extractall(filter="tar", path=unpackedWebsitePath) # nosec B202
|
|
archive.close()
|
|
|
|
# Determine the final rsync destination
|
|
# We allow for the website domain to be optionally added into this via use of %s
|
|
# This avoids having to repeat ourselves in the configuration file
|
|
finalRsyncDestination = rsyncDestination % websiteDomain
|
|
|
|
# Build up the rsync command to run then run it
|
|
# The entire contents of
|
|
rsyncCommand = [
|
|
"rsync",
|
|
"--archive",
|
|
"--checksum",
|
|
"--delete",
|
|
"--verbose",
|
|
str(unpackedWebsitePath) + "/",
|
|
finalRsyncDestination.rstrip("/") + "/",
|
|
]
|
|
util.runCommand(rsyncCommand, cwd=workPathName)
|
|
|
|
|
|
class PublishWebsiteWorker(Worker):
|
|
processorClass = PublishWebsiteProcessor
|