Skip to content
Snippets Groups Projects
context.py 6.47 KiB
#
# Copyright (C) 2021 Associated Universities, Inc. Washington DC, USA.
#
# This file is part of NRAO Workspaces.
#
# Workspaces is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Workspaces is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Workspaces.  If not, see <https://www.gnu.org/licenses/>.
# -------------------------------------------------------------------------
#
#        D E L I V E R Y   C O N T E X T
#
# -------------------------------------------------------------------------
import argparse
import pathlib
import secrets

from .deliverer import DeliveryContextIF, Destination, DestinationBuilder
from .finder import ProductFinder, JsonProductFinder


class DeliveryContext(DeliveryContextIF):
    """
    The delivery context provides access to some environmental functions that
    are not really the responsibility of any particular component, but which
    many components need to share information about, including:

    - Creating and removing temporary files
    - Creating and retaining tokens
    - Settings which change how delivery is performed
    """

    def __init__(
        self,
        source: pathlib.Path,
        tar=False,
        local_destination=None,
        use_piperesults=True,
        rawdata=False,
        prefix="",
        username="anonymous",
    ):
        self.source = source
        self.tar = tar
        self.local_destination = local_destination
        self.use_piperesults = use_piperesults
        self.rawdata = rawdata
        self.prefix = prefix
        self._username = username

        # the token is not something that gets created at construction time
        self._token = None

    def __exit__(self, exc_type, exc_val, exc_tb):
        # TODO: possibility: remove all the generated tempfiles here
        pass

    def __repr__(self):
        return f"<DeliveryContext {self.__dict__}>"

    def token(self) -> str:
        """
        If a delivery only requires one token, just use this property
        to get it. It will be created once and remain the same throughout
        the lifetime of this object.

        :return: the current token
        """
        if self._token is None:
            self._token = self.generate_token()
        return self._token

    @property
    def username(self) -> str:
        return self._username

    @staticmethod
    def generate_token() -> str:
        """
        Generates a random token suitable for use in paths and URLs.

        :return: a random token
        """
        return secrets.token_hex(16)

    def create_finder(self) -> ProductFinder:
        # Only one type of product finder for now; may change in the future
        return JsonProductFinder(self.source)

    def create_destination(self, finder: ProductFinder) -> Destination:
        builder = DestinationBuilder(self)

        # so the layer-cake has to be built in kind of reverse order because it's a stack
        # at the bottom we want the physical destination, which is either local or web delivery

        # first handle the local destination argument
        if self.local_destination:
            builder.local(self.local_destination, self.prefix)
        else:
            builder.web(self.prefix)

        # always do a checksum at the end
        builder.checksums()

        # make a CURL file before that, but only if we're doing a web delivery
        if not self.local_destination:
            builder.fetchfile()

        # tar goes here, so that we generate a checksum for the tar file
        # and not for all the files in the tar file
        if self.tar:
            builder.tar(finder.projects)

        return builder.build()

    @classmethod
    def parse_commandline(cls, args=None) -> "DeliveryContext":
        parser = argparse.ArgumentParser()

        parser.add_argument(
            "--prefix",
            dest="prefix",
            default="",
            action="store",
            help="Prefix for the destination (a request ID perhaps)",
        )

        # piperesults options
        group = parser.add_mutually_exclusive_group()
        group.add_argument(
            "-p",
            "--use-piperesults",
            dest="use_piperesults",
            default=True,
            action="store_true",
            help="Use the CASA piperesults file, if present",
        )
        group.add_argument(
            "-P",
            "--ignore-piperesults",
            dest="use_piperesults",
            action="store_false",
            help="Ignore the CASA piperesults file",
        )

        # destination options
        group = parser.add_argument_group("Destination options")
        group.add_argument(
            "-l",
            "--local-destination",
            type=str,
            default=None,
            help="Deliver to this local directory instead of the appropriate web root",
        )
        group.add_argument(
            "-t",
            "--tar",
            action="store_true",
            default=False,
            help="Archive the delivered items as a tar file",
        )

        # filtering options
        group = parser.add_argument_group("Product filtering options")
        group.add_argument(
            "-r",
            "--rawdata",
            default=False,
            action="store_true",
            help="Deliver the rawdata instead of the products",
        )
        parser.add_argument(
            "source",
            type=pathlib.Path,
            metavar="SOURCE_DIRECTORY",
            help="The directory where the products to be delivered are located",
        )

        parser.add_argument(
            "-u",
            "--username",
            default="anonymous",
            action="store",
            help="User on whose behalf we are delivering (defaults to anonymous)",
        )

        ns = parser.parse_args(args)

        return DeliveryContext(
            source=ns.source,
            tar=ns.tar,
            local_destination=pathlib.Path(ns.local_destination) if ns.local_destination else None,
            use_piperesults=ns.use_piperesults,
            rawdata=ns.rawdata,
            prefix=ns.prefix,
            username=ns.username,
        )