# # 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, )