GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Test Failed
Push — tim/optional-debug-tmp-dir ( 81c2ef )
by
unknown
03:12
created

DebugInfoCollector.create_tarball()   B

Complexity

Conditions 3

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
c 0
b 0
f 0
dl 0
loc 28
rs 8.8571
1
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
2
# contributor license agreements.  See the NOTICE file distributed with
3
# this work for additional information regarding copyright ownership.
4
# The ASF licenses this file to You under the Apache License, Version 2.0
5
# (the "License"); you may not use this file except in compliance with
6
# the License.  You may obtain a copy of the License at
7
#
8
#     http://www.apache.org/licenses/LICENSE-2.0
9
#
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
15
16
"""
17
This script submits information which helps StackStorm employees debug different
18
user problems and issues to StackStorm.
19
20
By default the following information is included:
21
22
- Logs from /var/log/st2
23
- StackStorm and mistral config file (/etc/st2/st2.conf, /etc/mistral/mistral.conf)
24
- All the content (integration packs).
25
- Information about your system and StackStorm installation (Operating system,
26
  Python version, StackStorm version, Mistral version)
27
28
Note: This script currently assumes it's running on Linux.
29
"""
30
31
import os
32
import sys
33
import shutil
34
import socket
35
import logging
36
import tarfile
37
import argparse
38
import platform
39
import tempfile
40
import httplib
41
42
import six
43
import yaml
44
import gnupg
45
import requests
46
from distutils.spawn import find_executable
47
48
import st2common
49
from st2common.content.utils import get_packs_base_paths
50
from st2common import __version__ as st2_version
51
from st2common import config
52
from st2common.util import date as date_utils
53
from st2common.util.shell import run_command
54
from st2debug.constants import GPG_KEY
55
from st2debug.constants import GPG_KEY_FINGERPRINT
56
from st2debug.constants import S3_BUCKET_URL
57
from st2debug.constants import COMPANY_NAME
58
from st2debug.constants import ARG_NAMES
59
from st2debug.utils.fs import copy_files
60
from st2debug.utils.fs import get_full_file_list
61
from st2debug.utils.fs import get_dirs_in_path
62
from st2debug.utils.fs import remove_file
63
from st2debug.utils.fs import remove_dir
64
from st2debug.utils.system_info import get_cpu_info
65
from st2debug.utils.system_info import get_memory_info
66
from st2debug.utils.system_info import get_package_list
67
from st2debug.utils.git_utils import get_repo_latest_revision_hash
68
from st2debug.processors import process_st2_config
69
from st2debug.processors import process_mistral_config
70
from st2debug.processors import process_content_pack_dir
71
72
LOG = logging.getLogger(__name__)
73
74
# Constants
75
GPG_INSTALLED = find_executable('gpg') is not None
76
77
LOG_FILE_PATHS = [
78
    '/var/log/st2/*.log',
79
    '/var/log/mistral*.log'
80
]
81
82
ST2_CONFIG_FILE_PATH = '/etc/st2/st2.conf'
83
MISTRAL_CONFIG_FILE_PATH = '/etc/mistral/mistral.conf'
84
85
SHELL_COMMANDS = []
86
87
# Directory structure inside tarball
88
DIRECTORY_STRUCTURE = [
89
    'configs/',
90
    'logs/',
91
    'content/',
92
    'commands/'
93
]
94
95
OUTPUT_PATHS = {
96
    'logs': 'logs/',
97
    'configs': 'configs/',
98
    'content': 'content/',
99
    'commands': 'commands/',
100
    'system_info': 'system_info.yaml',
101
    'user_info': 'user_info.yaml'
102
}
103
104
# Options which should be removed from the st2 config
105
ST2_CONF_OPTIONS_TO_REMOVE = {
106
    'database': ['username', 'password'],
107
    'messaging': ['url']
108
}
109
110
REMOVE_VALUE_NAME = '**removed**'
111
112
OUTPUT_FILENAME_TEMPLATE = 'st2-debug-output-%(hostname)s-%(date)s.tar.gz'
113
114
DATE_FORMAT = '%Y-%m-%d-%H%M%S'
115
116
try:
117
    config.parse_args(args=[])
118
except Exception:
119
    pass
120
121
122
def setup_logging():
123
    root = LOG
124
    root.setLevel(logging.INFO)
125
126
    ch = logging.StreamHandler(sys.stdout)
127
    ch.setLevel(logging.DEBUG)
128
    formatter = logging.Formatter('%(asctime)s  %(levelname)s - %(message)s')
129
    ch.setFormatter(formatter)
130
    root.addHandler(ch)
131
132
133
class DebugInfoCollector(object):
134
    def __init__(self, include_logs, include_configs, include_content, include_system_info,
135
                 include_shell_commands=False, user_info=None, debug=False, config_file=None,
136
                 output_path=None):
137
        """
138
        Initialize a DebugInfoCollector object.
139
140
        :param include_logs: Include log files in generated archive.
141
        :type include_logs: ``bool``
142
        :param include_configs: Include config files in generated archive.
143
        :type include_configs: ``bool``
144
        :param include_content: Include pack contents in generated archive.
145
        :type include_content: ``bool``
146
        :param include_system_info: Include system information in generated archive.
147
        :type include_system_info: ``bool``
148
        :param include_shell_commands: Include shell command output in generated archive.
149
        :type include_shell_commands: ``bool``
150
        :param user_info: User info to be included in generated archive.
151
        :type user_info: ``dict``
152
        :param debug: Enable debug logging.
153
        :type debug: ``bool``
154
        :param config_file: Values from config file to override defaults.
155
        :type config_file: ``dict``
156
        :param output_path: Path to write output file to. (optional)
157
        :type output_path: ``str``
158
        """
159
        self.include_logs = include_logs
160
        self.include_configs = include_configs
161
        self.include_content = include_content
162
        self.include_system_info = include_system_info
163
        self.include_shell_commands = include_shell_commands
164
        self.user_info = user_info
165
        self.debug = debug
166
        self.output_path = output_path
167
168
        config_file = config_file or {}
169
        self.tmp_dir_prefix = config_file.get('st2_debug_tmp_path', None)
170
        self.st2_config_file_path = config_file.get('st2_config_file_path', ST2_CONFIG_FILE_PATH)
171
        self.mistral_config_file_path = config_file.get('mistral_config_file_path',
172
                                                        MISTRAL_CONFIG_FILE_PATH)
173
        self.log_file_paths = config_file.get('log_file_paths', LOG_FILE_PATHS[:])
174
        self.gpg_key = config_file.get('gpg_key', GPG_KEY)
175
        self.gpg_key_fingerprint = config_file.get('gpg_key_fingerprint', GPG_KEY_FINGERPRINT)
176
        self.s3_bucket_url = config_file.get('s3_bucket_url', S3_BUCKET_URL)
177
        self.company_name = config_file.get('company_name', COMPANY_NAME)
178
        self.shell_commands = config_file.get('shell_commands', SHELL_COMMANDS)
179
180
        self.st2_config_file_name = os.path.basename(self.st2_config_file_path)
181
        self.mistral_config_file_name = os.path.basename(self.mistral_config_file_path)
182
        self.config_file_paths = [
183
            self.st2_config_file_path,
184
            self.mistral_config_file_path
185
        ]
186
187
    def run(self, encrypt=False, upload=False, existing_file=None):
188
        """
189
        Run the specified steps.
190
191
        :param encrypt: If true, encrypt the archive file.
192
        :param encrypt: ``bool``
193
        :param upload: If true, upload the resulting file.
194
        :param upload: ``bool``
195
        :param existing_file: Path to an existing archive file. If not specified a new
196
        archive will be created.
197
        :param existing_file: ``str``
198
        """
199
        temp_files = []
200
201
        try:
202
            if existing_file:
203
                working_file = existing_file
204
            else:
205
                # Create a new archive if an existing file hasn't been provided
206
                working_file = self.create_archive()
207
                if not encrypt and not upload:
208
                    LOG.info('Debug tarball successfully '
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
209
                             'generated and can be reviewed at: %s' % working_file)
210
                else:
211
                    temp_files.append(working_file)
212
213
            if encrypt:
214
                working_file = self.encrypt_archive(archive_file_path=working_file)
215
                if not upload:
216
                    LOG.info('Encrypted debug tarball successfully generated at: %s' %
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
217
                             working_file)
218
                else:
219
                    temp_files.append(working_file)
220
221
            if upload:
222
                self.upload_archive(archive_file_path=working_file)
223
                tarball_name = os.path.basename(working_file)
224
                LOG.info('Debug tarball successfully uploaded to %s (name=%s)' %
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
225
                         (self.company_name, tarball_name))
226
                LOG.info('When communicating with support, please let them know the '
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
227
                         'tarball name - %s' % tarball_name)
228
        finally:
229
            # Remove temp files
230
            for temp_file in temp_files:
231
                assert temp_file.startswith(self.tmp_dir_prefix)
232
                remove_file(file_path=temp_file)
233
234
    def create_archive(self):
235
        """
236
        Create an archive with debugging information.
237
238
        :return: Path to the generated archive.
239
        :rtype: ``str``
240
        """
241
242
        try:
243
            # 1. Create temporary directory with the final directory structure where we will move
244
            # files which will be processed and included in the tarball
245
            self._temp_dir_path = self.create_temp_directories()
246
247
            # Prepend temp_dir_path to OUTPUT_PATHS
248
            output_paths = {}
249
            for key, path in OUTPUT_PATHS.iteritems():
250
                output_paths[key] = os.path.join(self._temp_dir_path, path)
251
252
            # 2. Moves all the files to the temporary directory
253
            LOG.info('Collecting files...')
254
            if self.include_logs:
255
                self.collect_logs(output_paths['logs'])
256
            if self.include_configs:
257
                self.collect_config_files(output_paths['configs'])
258
            if self.include_content:
259
                self.collect_pack_content(output_paths['content'])
260
            if self.include_system_info:
261
                self.add_system_information(output_paths['system_info'])
262
            if self.user_info:
263
                self.add_user_info(output_paths['user_info'])
264
            if self.include_shell_commands:
265
                self.add_shell_command_output(output_paths['commands'])
266
267
            # 3. Create a tarball
268
            return self.create_tarball(self._temp_dir_path)
269
270
        except Exception as e:
271
            LOG.exception('Failed to generate tarball', exc_info=True)
272
            raise e
273
274
        finally:
275
            # Ensure temp files are removed regardless of success or failure
276
            assert self._temp_dir_path.startswith(self.tmp_dir_prefix)
277
            remove_dir(self._temp_dir_path)
278
279
    def encrypt_archive(self, archive_file_path):
280
        """
281
        Encrypt archive with debugging information using our public key.
282
283
        :param archive_file_path: Path to the non-encrypted tarball file.
284
        :type archive_file_path: ``str``
285
286
        :return: Path to the encrypted archive.
287
        :rtype: ``str``
288
        """
289
        try:
290
            assert archive_file_path.endswith('.tar.gz')
291
292
            LOG.info('Encrypting tarball...')
293
            gpg = gnupg.GPG(verbose=self.debug)
294
295
            # Import our public key
296
            import_result = gpg.import_keys(self.gpg_key)
297
            # pylint: disable=no-member
298
            assert import_result.count == 1
299
300
            encrypted_archive_output_file_name = os.path.basename(archive_file_path) + '.asc'
301
            encrypted_archive_output_file_path = os.path.join(self.tmp_dir_prefix,
302
                                                              encrypted_archive_output_file_name)
303
            with open(archive_file_path, 'rb') as fp:
304
                gpg.encrypt_file(file=fp,
305
                                 recipients=self.gpg_key_fingerprint,
306
                                 always_trust=True,
307
                                 output=encrypted_archive_output_file_path)
308
            return encrypted_archive_output_file_path
309
        except Exception as e:
310
            LOG.exception('Failed to encrypt archive', exc_info=True)
311
            raise e
312
313
    def upload_archive(self, archive_file_path):
314
        """
315
        Upload the encrypted archive.
316
317
        :param archive_file_path: Path to the encrypted tarball file.
318
        :type archive_file_path: ``str``
319
        """
320
        try:
321
            assert archive_file_path.endswith('.asc')
322
323
            LOG.debug('Uploading tarball...')
324
            file_name = os.path.basename(archive_file_path)
325
            url = self.s3_bucket_url + file_name
326
            assert url.startswith('https://')
327
328
            with open(archive_file_path, 'rb') as fp:
329
                response = requests.put(url=url, files={'file': fp})
330
            assert response.status_code == httplib.OK
331
        except Exception as e:
332
            LOG.exception('Failed to upload tarball to %s' % self.company_name, exc_info=True)
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
333
            raise e
334
335
    def collect_logs(self, output_path):
336
        """
337
        Copy log files to the output path.
338
339
        :param output_path: Path where log files will be copied to.
340
        :type output_path: ``str``
341
        """
342
        LOG.debug('Including log files')
343
        for file_path_glob in self.log_file_paths:
344
            log_file_list = get_full_file_list(file_path_glob=file_path_glob)
345
            copy_files(file_paths=log_file_list, destination=output_path)
346
347
    def collect_config_files(self, output_path):
348
        """
349
        Copy config files to the output path.
350
351
        :param output_path: Path where config files will be copied to.
352
        :type output_path: ``str``
353
        """
354
        LOG.debug('Including config files')
355
        copy_files(file_paths=self.config_file_paths, destination=output_path)
356
357
        st2_config_path = os.path.join(output_path, self.st2_config_file_name)
358
        process_st2_config(config_path=st2_config_path, tmp_prefix=self.tmp_dir_prefix)
359
360
        mistral_config_path = os.path.join(output_path, self.mistral_config_file_name)
361
        process_mistral_config(config_path=mistral_config_path, tmp_prefix=self.tmp_dir_prefix)
362
363
    def collect_pack_content(self, output_path):
364
        """
365
        Copy pack contents to the output path.
366
367
        :param output_path: Path where pack contents will be copied to.
368
        :type output_path: ``str``
369
        """
370
        LOG.debug('Including content')
371
372
        packs_base_paths = get_packs_base_paths()
373
        for index, packs_base_path in enumerate(packs_base_paths, 1):
374
            dst = os.path.join(output_path, 'dir-%s' % index)
375
376
            try:
377
                shutil.copytree(src=packs_base_path, dst=dst)
378
            except IOError:
379
                continue
380
381
        base_pack_dirs = get_dirs_in_path(file_path=output_path)
382
383
        for base_pack_dir in base_pack_dirs:
384
            pack_dirs = get_dirs_in_path(file_path=base_pack_dir)
385
386
            for pack_dir in pack_dirs:
387
                process_content_pack_dir(pack_dir=pack_dir, tmp_prefix=self.tmp_dir_prefix)
388
389
    def add_system_information(self, output_path):
390
        """
391
        Collect and write system information to output path.
392
393
        :param output_path: Path where system information will be written to.
394
        :type output_path: ``str``
395
        """
396
        LOG.debug('Including system info')
397
398
        system_information = yaml.safe_dump(self.get_system_information(),
399
                                            default_flow_style=False)
400
401
        with open(output_path, 'w') as fp:
402
            fp.write(system_information)
403
404
    def add_user_info(self, output_path):
405
        """
406
        Write user info to output path as YAML.
407
408
        :param output_path: Path where user info will be written.
409
        :type output_path: ``str``
410
        """
411
        LOG.debug('Including user info')
412
        user_info = yaml.safe_dump(self.user_info, default_flow_style=False)
413
414
        with open(output_path, 'w') as fp:
415
            fp.write(user_info)
416
417
    def add_shell_command_output(self, output_path):
418
        """"
419
        Get output of the required shell command and redirect the output to output path.
420
421
        :param output_path: Directory where output files will be written
422
        :param output_path: ``str``
423
        """
424
        LOG.debug('Including the required shell commands output files')
425
        for cmd in self.shell_commands:
426
            output_file = os.path.join(output_path, '%s.txt' % self.format_output_filename(cmd))
427
            exit_code, stdout, stderr = run_command(cmd=cmd, shell=True, cwd=output_path)
428
            with open(output_file, 'w') as fp:
429
                fp.write('[BEGIN STDOUT]\n')
430
                fp.write(stdout)
431
                fp.write('[END STDOUT]\n')
432
                fp.write('[BEGIN STDERR]\n')
433
                fp.write(stderr)
434
                fp.write('[END STDERR]')
435
436
    def create_tarball(self, temp_dir_path):
437
        """
438
        Create tarball with the contents of temp_dir_path.
439
440
        Tarball will be written to self.output_path, if set. Otherwise it will
441
        be written to self.tmp_dir_prefix with a name generated according to
442
        OUTPUT_FILENAME_TEMPLATE.
443
444
        :param temp_dir_path: Base directory to include in tarball.
445
        :type temp_dir_path: ``str``
446
447
        :return: Path to the created tarball.
448
        :rtype: ``str``
449
        """
450
        LOG.info('Creating tarball...')
451
        if self.output_path:
452
            output_file_path = self.output_path
453
        else:
454
            date = date_utils.get_datetime_utc_now().strftime(DATE_FORMAT)
455
            values = {'hostname': socket.gethostname(), 'date': date}
456
457
            output_file_name = OUTPUT_FILENAME_TEMPLATE % values
458
            output_file_path = os.path.join(self.tmp_dir_prefix, output_file_name)
459
460
        with tarfile.open(output_file_path, 'w:gz') as tar:
461
            tar.add(temp_dir_path, arcname='')
462
463
        return output_file_path
464
465
    def create_temp_directories(self):
466
        """
467
        Creates a new temp directory and creates the directory structure as defined
468
        by DIRECTORY_STRUCTURE.
469
470
        :return: Path to temp directory.
471
        :rtype: ``str``
472
        """
473
        temp_dir_path = tempfile.mkdtemp(dir=self.tmp_dir_prefix)
474
        if not self.tmp_dir_prefix:
475
            # set self.tmp_dir_prefix to the path selected by mkdtemp, it's used elsewhere for
476
            # asserts.
477
            self.tmp_dir_prefix, tmp_file = os.path.split(temp_dir_path)
478
        LOG.info('Using temp dir prefix: %s', temp_dir_path)
479
480
        for directory_name in DIRECTORY_STRUCTURE:
481
            full_path = os.path.join(temp_dir_path, directory_name)
482
            os.mkdir(full_path)
483
484
        return temp_dir_path
485
486
    @staticmethod
487
    def format_output_filename(cmd):
488
        """"
489
        Remove whitespace and special characters from a shell command.
490
491
        Used to create filename-safe representations of a shell command.
492
493
        :param cmd: Shell command.
494
        :type cmd: ``str``
495
        :return: Formatted filename.
496
        :rtype: ``str``
497
        """
498
        return cmd.translate(None, """ !@#$%^&*()[]{};:,./<>?\|`~=+"'""")
0 ignored issues
show
Bug introduced by
A suspicious escape sequence \| was found. Did you maybe forget to add an r prefix?

Escape sequences in Python are generally interpreted according to rules similar to standard C. Only if strings are prefixed with r or R are they interpreted as regular expressions.

The escape sequence that was used indicates that you might have intended to write a regular expression.

Learn more about the available escape sequences. in the Python documentation.

Loading history...
499
500
    @staticmethod
501
    def get_system_information():
502
        """
503
        Retrieve system information which is included in the report.
504
505
        :rtype: ``dict``
506
        """
507
        system_information = {
508
            'hostname': socket.gethostname(),
509
            'operating_system': {},
510
            'hardware': {
511
                'cpu': {},
512
                'memory': {}
513
            },
514
            'python': {},
515
            'stackstorm': {},
516
            'mistral': {}
517
        }
518
519
        # Operating system information
520
        system_information['operating_system']['system'] = platform.system()
521
        system_information['operating_system']['release'] = platform.release()
522
        system_information['operating_system']['operating_system'] = platform.platform()
523
        system_information['operating_system']['platform'] = platform.system()
524
        system_information['operating_system']['architecture'] = ' '.join(platform.architecture())
525
526
        if platform.system().lower() == 'linux':
527
            distribution = ' '.join(platform.linux_distribution())
528
            system_information['operating_system']['distribution'] = distribution
529
530
        system_information['python']['version'] = sys.version.split('\n')[0]
531
532
        # Hardware information
533
        cpu_info = get_cpu_info()
534
535
        if cpu_info:
536
            core_count = len(cpu_info)
537
            model = cpu_info[0]['model_name']
538
            system_information['hardware']['cpu'] = {
539
                'core_count': core_count,
540
                'model_name': model
541
            }
542
        else:
543
            # Unsupported platform
544
            system_information['hardware']['cpu'] = 'unsupported platform'
545
546
        memory_info = get_memory_info()
547
548
        if memory_info:
549
            total = memory_info['MemTotal'] / 1024
550
            free = memory_info['MemFree'] / 1024
551
            used = (total - free)
552
            system_information['hardware']['memory'] = {
553
                'total': total,
554
                'used': used,
555
                'free': free
556
            }
557
        else:
558
            # Unsupported platform
559
            system_information['hardware']['memory'] = 'unsupported platform'
560
561
        # StackStorm information
562
        system_information['stackstorm']['version'] = st2_version
563
564
        st2common_path = st2common.__file__
565
        st2common_path = os.path.dirname(st2common_path)
566
567
        if 'st2common/st2common' in st2common_path:
568
            # Assume we are running source install
569
            base_install_path = st2common_path.replace('/st2common/st2common', '')
570
571
            revision_hash = get_repo_latest_revision_hash(repo_path=base_install_path)
572
573
            system_information['stackstorm']['installation_method'] = 'source'
574
            system_information['stackstorm']['revision_hash'] = revision_hash
575
        else:
576
            package_list = get_package_list(name_startswith='st2')
577
578
            system_information['stackstorm']['installation_method'] = 'package'
579
            system_information['stackstorm']['packages'] = package_list
580
581
        # Mistral information
582
        repo_path = '/opt/openstack/mistral'
583
        revision_hash = get_repo_latest_revision_hash(repo_path=repo_path)
584
        system_information['mistral']['installation_method'] = 'source'
585
        system_information['mistral']['revision_hash'] = revision_hash
586
587
        return system_information
588
589
590
def main():
591
    parser = argparse.ArgumentParser(description='')
592
    parser.add_argument('--exclude-logs', action='store_true', default=False,
593
                        help='Don\'t include logs in the generated tarball')
594
    parser.add_argument('--exclude-configs', action='store_true', default=False,
595
                        help='Don\'t include configs in the generated tarball')
596
    parser.add_argument('--exclude-content', action='store_true', default=False,
597
                        help='Don\'t include content packs in the generated tarball')
598
    parser.add_argument('--exclude-system-info', action='store_true', default=False,
599
                        help='Don\'t include system information in the generated tarball')
600
    parser.add_argument('--exclude-shell-commands', action='store_true', default=False,
601
                        help='Don\'t include shell commands output in the generated tarball')
602
    parser.add_argument('--yes', action='store_true', default=False,
603
                        help='Run in non-interactive mode and answer "yes" to all the questions')
604
    parser.add_argument('--review', action='store_true', default=False,
605
                        help='Generate the tarball, but don\'t encrypt and upload it')
606
    parser.add_argument('--debug', action='store_true', default=False,
607
                        help='Enable debug mode')
608
    parser.add_argument('--config', action='store', default=None,
609
                        help='Get required configurations from config file')
610
    parser.add_argument('--output', action='store', default=None,
611
                        help='Specify output file path')
612
    parser.add_argument('--existing-file', action='store', default=None,
613
                        help='Specify an existing file to operate on')
614
    args = parser.parse_args()
615
616
    setup_logging()
617
618
    # Ensure that not all options have been excluded
619
    abort = True
620
    for arg_name in ARG_NAMES:
621
        abort &= getattr(args, arg_name, False)
622
623
    if abort:
624
        print('Generated tarball would be empty. Aborting.')
625
        sys.exit(2)
626
627
    # Get setting overrides from yaml file if specified
628
    if args.config:
629
        try:
630
            with open(args.config, 'r') as yaml_file:
631
                config_file = yaml.safe_load(yaml_file)
632
        except Exception as e:
633
            LOG.error('Failed to parse config file: %s' % e)
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
634
            sys.exit(1)
635
636
        if not isinstance(config_file, dict):
637
            LOG.error('Unrecognized config file format')
638
            sys.exit(1)
639
    else:
640
        config_file = {}
641
642
    company_name = config_file.get('company_name', COMPANY_NAME)
643
644
    # Defaults
645
    encrypt = True
646
    upload = True
647
648
    if args.review:
649
        encrypt = False
650
        upload = False
651
652
    if encrypt:
653
        # When not running in review mode, GPG needs to be installed and
654
        # available
655
        if not GPG_INSTALLED:
656
            msg = ('"gpg" binary not found, can\'t proceed. Make sure "gpg" is installed '
657
                   'and available in PATH.')
658
            raise ValueError(msg)
659
660
    if not args.yes and not args.existing_file and upload:
661
        submitted_content = [name.replace('exclude_', '') for name in ARG_NAMES if
662
                             not getattr(args, name, False)]
663
        submitted_content = ', '.join(submitted_content)
664
        print('This will submit the following information to %s: %s' % (company_name,
665
                                                                        submitted_content))
666
        value = six.moves.input('Are you sure you want to proceed? [y/n] ')
667
        if value.strip().lower() not in ['y', 'yes']:
668
            print('Aborting')
669
            sys.exit(1)
670
671
    # Prompt user for optional additional context info
672
    user_info = {}
673
    if not args.yes and not args.existing_file:
674
        print('If you want us to get back to you via email, you can provide additional context '
675
              'such as your name, email and an optional comment')
676
        value = six.moves.input('Would you like to provide additional context? [y/n] ')
677
        if value.strip().lower() in ['y', 'yes']:
678
            user_info['name'] = six.moves.input('Name: ')
679
            user_info['email'] = six.moves.input('Email: ')
680
            user_info['comment'] = six.moves.input('Comment: ')
681
682
    debug_collector = DebugInfoCollector(include_logs=not args.exclude_logs,
683
                                         include_configs=not args.exclude_configs,
684
                                         include_content=not args.exclude_content,
685
                                         include_system_info=not args.exclude_system_info,
686
                                         include_shell_commands=not args.exclude_shell_commands,
687
                                         user_info=user_info,
688
                                         debug=args.debug,
689
                                         config_file=config_file,
690
                                         output_path=args.output)
691
692
    debug_collector.run(encrypt=encrypt, upload=upload, existing_file=args.existing_file)
693