# Copyright 2024 Red Hat, Inc. Jose Castillo <jcastillo@redhat.com>
# This file is part of the sos project: https://github.com/sosreport/sos
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# version 2 of the GNU General Public License.
#
# See the LICENSE file in the source distribution for further information.
import os
import sys
import logging
import inspect
from sos.component import SoSComponent
from sos import _sos as _
from sos import __version__
class SoSUpload(SoSComponent):
"""
This class is designed to upload files to a distribution
defined location. These files can be either sos reports,
sos collections, or other kind of files like: vmcores,
application cores, logs, etc.
"""
desc = """Upload a file to a user or policy defined remote location"""
arg_defaults = {
'upload_file': '',
'case_id': '',
'low_priority': False,
'upload_url': None,
'upload_directory': None,
'upload_user': None,
'upload_pass': None,
'upload_method': 'auto',
'upload_no_ssl_verify': False,
'upload_protocol': 'auto',
'upload_s3_endpoint': 'https://s3.amazonaws.com',
'upload_s3_region': None,
'upload_s3_bucket': None,
'upload_s3_access_key': None,
'upload_s3_secret_key': None,
'upload_s3_object_prefix': None,
'upload_target': None
}
def __init__(self, parser=None, args=None, cmdline=None, in_place=False,
hook_commons=None, archive=None):
if not in_place:
# we are running `sos upload` directly
super().__init__(parser, args, cmdline)
self.from_cmdline = True
else:
# we are being hooked by either SoSReport or SoSCollector, don't
# re-init everything as that will cause issues, but instead load
# the needed bits from the calling component
self.opts = hook_commons['options']
self.policy = hook_commons['policy']
self.manifest = hook_commons['manifest']
self.parser = parser
self.cmdline = cmdline
self.args = args
self._upload_file = archive
self.ui_log = logging.getLogger('sos_ui')
self.from_cmdline = False
self.archive = archive
self.upload_targets = self.load_upload_targets()
self.upload_target = None
@classmethod
def add_parser_options(cls, parser):
parser.usage = 'sos upload FILE [options]'
upload_grp = parser.add_argument_group(
'Upload Options',
'These options control how upload manages files'
)
upload_grp.add_argument("upload_file", metavar="FILE",
help="The file or archive to upload")
upload_grp.add_argument("--case-id", action="store", dest="case_id",
help="specify case identifier")
upload_grp.add_argument("--upload-url", default=None,
help="Upload the archive to specified server")
upload_grp.add_argument("--upload-user", default=None,
help="Username to authenticate with")
upload_grp.add_argument("--upload-pass", default=None,
help="Password to authenticate with")
upload_grp.add_argument("--upload-directory", action="store",
dest="upload_directory",
help="Specify upload directory for archive")
upload_grp.add_argument("--upload-s3-endpoint", default=None,
help="Endpoint to upload to for S3 bucket")
upload_grp.add_argument("--upload-s3-region", default=None,
help="Region to upload to for S3 bucket")
upload_grp.add_argument("--upload-s3-bucket", default=None,
help="Name of the S3 bucket to upload to")
upload_grp.add_argument("--upload-s3-access-key", default=None,
help="Access key for the S3 bucket")
upload_grp.add_argument("--upload-s3-secret-key", default=None,
help="Secret key for the S3 bucket")
upload_grp.add_argument("--upload-s3-object-prefix", default=None,
help="Prefix for the S3 object/key")
upload_grp.add_argument("--upload-method", default='auto',
choices=['auto', 'put', 'post'],
help="HTTP method to use for uploading")
upload_grp.add_argument("--upload-protocol", default='auto',
choices=['auto', 'https', 'ftp', 'sftp', 's3'],
help="Manually specify the upload protocol")
upload_grp.add_argument("--upload-no-ssl-verify", default=False,
action='store_true',
help="Disable SSL verification for upload url")
upload_grp.add_argument("--upload-target", default='local',
choices=[
'redhat',
'canonical',
'generic',
'local'],
help=("Manually specify vendor-specific "
"target for uploads. Supported "
"options are:\n"
"redhat, canonical, "
"generic, local"))
@classmethod
def display_help(cls, section):
section.set_title('SoS Upload Detailed Help')
section.add_text(
'The upload command is designed to upload already existing '
'sos reports, as well as other files like logs and vmcores '
'to a distribution specific location.'
)
def intro(self):
"""Print the intro message and prompts for a case ID if one is not
provided on the command line
"""
disclaimer = """\
This utility is used to upload files to a target location \
based either on a command line option or detecting the local \
distribution.
The archive to be uploaded may contain data considered sensitive \
and its content should be reviewed by the originating \
organization before being passed to any third party.
No configuration changes will be made to the system running \
this utility.
"""
self.ui_log.info(f"\nsos upload (version {__version__})")
intro_msg = self._fmt_msg(disclaimer)
self.ui_log.info(intro_msg)
prompt = "\nPress ENTER to continue, or CTRL-C to quit\n"
if not self.opts.batch:
try:
input(prompt)
self.ui_log.info("")
except KeyboardInterrupt:
self._exit("Exiting on user cancel", 130)
except Exception as e:
self._exit(e, 1)
def get_commons(self):
return {
'cmdlineopts': self.opts,
'policy': self.policy,
'case_id': self.opts.case_id,
'upload_directory': self.opts.upload_directory
}
def set_commons(self, commons):
"""Set common host data for the Upload targets
to reference
"""
self.commons = commons
def determine_upload_target(self):
"""This sets the upload target and loads that target's options.
If no upload target is matched and no target is provided by
the user, then we abort.
If an upload target is provided in the command line,
this will not run.
"""
checks = list(self.upload_targets.values())
for upload_target in self.upload_targets.values():
checks.remove(upload_target)
if upload_target.check_distribution():
cname = upload_target.__class__.__name__
self.ui_log.debug(f"Installation matches {cname}, checking for"
" upload targets")
self.upload_target = upload_target
self.upload_name = upload_target.name()
if not self.upload_target:
self.upload_target = self.upload_targets["generic"]
self.upload_name = self.upload_target.name()
self.ui_log.info(
f"Upload target set to {self.upload_name}")
def load_upload_targets(self):
"""Loads all upload targets supported by the local installation
"""
import sos.upload.targets
supported_upload_tgts = {}
for upload_target in self._load_modules(sos.upload.targets, 'targets'):
target_class = upload_target[1](
parser=self.parser,
args=self.args,
cmdline=self.cmdline
)
target_class.set_commons(self.get_commons())
supported_upload_tgts[target_class.get_target_id()] = target_class
return supported_upload_tgts
@classmethod
def _load_modules(cls, package, submod):
"""Helper to import upload targets"""
modules = []
for path in package.__path__:
if os.path.isdir(path):
modules.extend(cls._find_modules_in_path(path, submod))
return modules
@classmethod
def _find_modules_in_path(cls, path, modulename):
"""Given a path and a module name, find everything that can be imported
and then import it
path - the filesystem path of the package
modulename - the name of the module in the package
E.G. a path of 'targets', and a modulename of 'redhat' equates to
importing sos.upload.targets.redhat
"""
modules = []
if os.path.exists(path):
for pyfile in sorted(os.listdir(path)):
if not pyfile.endswith('.py'):
continue
if '__' in pyfile:
continue
fname, _ = os.path.splitext(pyfile)
modname = f'sos.upload.{modulename}.{fname}'
modules.extend(cls._import_modules(modname))
return modules
@classmethod
def _import_modules(cls, modname):
"""Import and return all found classes in a module"""
mod_short_name = modname.split('.')[2]
try:
module = __import__(modname, globals(), locals(), [mod_short_name])
except ImportError as e:
raise e
modules = inspect.getmembers(module, inspect.isclass)
for mod in modules.copy():
if mod[0] in (
'DeviceAuthorizationClass',
'Upload',
'RHELPolicy',
'UbuntuPolicy'):
modules.remove(mod)
return modules
def pre_work(self):
# This method will be called before upload begins
self.commons = self.get_commons()
cmdline_opts = self.commons['cmdlineopts']
if cmdline_opts.low_priority:
self.policy._configure_low_priority()
def execute(self):
self.pre_work()
if self.from_cmdline:
self.intro()
self.archive = self.opts.upload_file
self.caseid = self.policy.prompt_for_case_id(
cmdline_opts=self.opts
)
cmdline_target = self.opts.upload_target
if cmdline_target and cmdline_target != 'local':
self.upload_target = self.upload_targets[cmdline_target]
else:
self.determine_upload_target()
if not self.upload_target:
# There was no upload target set, so we'll throw
# an error here and exit
self.ui_log.error(
"No upload target set via command line options or"
" autodetected.\n"
"Please specify one using the option --upload-target.\n"
"Exiting."
)
sys.exit(1)
self.upload_target.pre_work(self.get_commons())
try:
if os.stat(self.archive).st_size > 0:
if os.path.isfile(self.archive):
try:
self.upload_target.upload_archive(self.archive)
self.ui_log.info(
_(f"File {self.archive} uploaded successfully")
)
except Exception as err:
self.ui_log.error(_(f"Upload attempt failed: {err}"))
sys.exit(1)
else:
self.ui_log.error(_(f"{self.archive} is not a file."))
else:
self.ui_log.error(_(f"File {self.archive} is empty."))
except Exception as e:
self.ui_log.error(_(f"Cannot upload {self.archive}: {e} "))
# vim: set et ts=4 sw=4 :
Anons79 File Manager Version 1.0, Coded By Anons79
Email: [email protected]