Esempio n. 1
0
def wipe_main(args_parser):
    """
    wipe mainline

    The mainline for the 'wipe' action.

    Args:
        args_parser: the argument parser to use for argument processing

    Returns:
        the exit code
    """

    args_parser.add_argument('--danger', action='store_true')

    known_args = sys.argv[1:]
    args, unknown_args = args_parser.parse_known_args(known_args)
    if unknown_args:
        logger.warn('unknown arguments: {}'.format(' '.join(unknown_args)))

    work_dir = args.work_dir if args.work_dir else os.getcwd()

    # protection warning
    if not args.danger:
        print('')
        sys.stdout.flush()
        logger.warn('!!! DANGER DANGER DANGER !!!')
        print("""
A request has been made to attempt to wipe the pages from a configured
Confluence instance. This is a helper utility call to assist a user in cleaning
out a space since removing a bulk set of data may not be trivial for a user.

Note that this action is not reversible with this tool and may require
assistance from an administrator from a Confluence instance to recover pages.
Only use this action if you know what you are doing.

To use this action, the argument '--danger' must be set.
            """)
        sys.stdout.flush()
        logger.warn('!!! DANGER DANGER DANGER !!!')
        print('')
        return 1

    # check configuration and prepare publisher
    publisher = None
    with TemporaryDirectory() as tmp_dir:
        with docutils_namespace():
            app = Sphinx(
                work_dir,  # document sources
                work_dir,  # directory with configuration
                tmp_dir,  # output for generated documents
                tmp_dir,  # output for doctree files
                'confluence')  # builder to execute

            aggressive_search = app.config.confluence_adv_aggressive_search
            server_url = app.config.confluence_server_url
            space_name = app.config.confluence_space_name

            # initialize the publisher (if permitted)
            if app.config.confluence_publish:
                process_ask_configs(app.config)

                publisher = ConfluencePublisher()
                publisher.init(app.config)

    if not publisher:
        print('(error) publishing not configured in sphinx configuration')
        return 1

    # reminder warning
    print('')
    sys.stdout.flush()
    logger.warn('!!! DANGER DANGER DANGER !!!')
    print("""
A request has been made to attempt to wipe the pages from a configured
Confluence instance.  This action is not reversible with this tool and may
require assistance from an administrator from a Confluence instance to recover
pages. Only use this action if you know what you are doing.
        """)
    sys.stdout.flush()

    logger.warn('!!! DANGER DANGER DANGER !!!')
    print('')

    if not ask_question('Are you sure you want to continue?'):
        return 0
    print('')

    # user has confirmed; start an attempt to wipe
    publisher.connect()

    if aggressive_search:
        legacy_pages = publisher.getDescendantsCompat(None)
    else:
        legacy_pages = publisher.getDescendants(None)
    if not legacy_pages:
        print('No pages are published on this space. Exiting...')
        return 0

    print('         URL:', server_url)
    print('       Space:', space_name)
    logger.note('       Pages: All Pages')
    print(' Total pages:', len(legacy_pages))
    print('')
    if not ask_question('Are you sure you want to REMOVE these pages?'):
        return 0
    print('')

    logger.info('Removing pages...', nonl=True)
    for page_id in legacy_pages:
        publisher.removePage(page_id)
        logger.info('.', nonl=True)
    logger.info(' done\n')

    return 0
Esempio n. 2
0
def report_main(args_parser):
    """
    report mainline

    The mainline for the 'report' action.

    Args:
        args_parser: the argument parser to use for argument processing

    Returns:
        the exit code
    """

    args_parser.add_argument('--full-config', '-C', action='store_true')
    args_parser.add_argument('--no-sanitize', action='store_true')
    args_parser.add_argument('--offline', action='store_true')

    known_args = sys.argv[1:]
    args, unknown_args = args_parser.parse_known_args(known_args)
    if unknown_args:
        logger.warn('unknown arguments: {}'.format(' '.join(unknown_args)))

    rv = 0
    offline = args.offline
    work_dir = args.work_dir if args.work_dir else os.getcwd()

    # setup sphinx engine to extract configuration
    config = {}
    configuration_load_issue = None
    confluence_instance_info = None
    publisher = ConfluencePublisher()

    try:
        with temp_dir() as tmp_dir:
            with docutils_namespace():
                print('fetching configuration information...')
                builder = ConfluenceReportBuilder.name
                app = Sphinx(
                    work_dir,            # document sources
                    work_dir,            # directory with configuration
                    tmp_dir,             # output for built documents
                    tmp_dir,             # output for doctree files
                    builder,             # builder to execute
                    status=sys.stdout,   # sphinx status output
                    warning=sys.stderr)  # sphinx warning output

                if app.config.confluence_publish:
                    try:
                        process_ask_configs(app.config)
                    except ConfluenceConfigurationError:
                        offline = True
                # extract configuration information
                for k, v in app.config.values.items():
                    raw = getattr(app.config, k)
                    if raw is None:
                        continue

                    if callable(raw):
                        value = '(callable)'
                    else:
                        value = raw

                    prefixes = (
                        'confluence_',
                        'singleconfluence_',
                    )
                    if not args.full_config and not k.startswith(prefixes):
                        continue

                    # always extract some known builder configurations
                    if args.full_config and k.startswith(IGNORE_BUILDER_CONFS):
                        continue

                    config[k] = value

                # initialize the publisher (if needed later)
                publisher.init(app.config)

    except Exception:
        sys.stdout.flush()
        tb_msg = traceback.format_exc()
        logger.error(tb_msg)
        if os.path.isfile(os.path.join(work_dir, 'conf.py')):
            configuration_load_issue = 'unable to load configuration'
            configuration_load_issue += '\n\n' + tb_msg.strip()
        else:
            configuration_load_issue = 'no documentation/missing configuration'
        rv = 1

    # attempt to fetch confluence instance version
    confluence_publish = config.get('confluence_publish')
    confluence_server_url = config.get('confluence_server_url')
    if not offline and confluence_publish and confluence_server_url:
        base_url = ConfluenceUtil.normalize_base_url(confluence_server_url)
        info = ''

        session = None
        try:
            print('connecting to confluence instance...')
            sys.stdout.flush()

            publisher.connect()
            info += ' connected: yes\n'
            session = publisher.rest_client.session
        except Exception:
            sys.stdout.flush()
            logger.error(traceback.format_exc())
            info += ' connected: no\n'
            rv = 1

        if session:
            try:
                # fetch
                print('fetching confluence instance information...')
                manifest_url = base_url + MANIFEST_PATH
                rsp = session.get(manifest_url)

                if rsp.status_code == 200:
                    info += '   fetched: yes\n'

                    # extract
                    print('decoding information...')
                    rsp.encoding = 'utf-8'
                    raw_data = rsp.text
                    info += '   decoded: yes\n'

                    # parse
                    print('parsing information...')
                    xml_data = ElementTree.fromstring(raw_data)
                    info += '    parsed: yes\n'
                    root = ElementTree.ElementTree(xml_data)
                    for o in root.findall('typeId'):
                        info += '      type: ' + o.text + '\n'
                    for o in root.findall('version'):
                        info += '   version: ' + o.text + '\n'
                    for o in root.findall('buildNumber'):
                        info += '     build: ' + o.text + '\n'
                else:
                    logger.error('bad response from server ({})'.format(
                        rsp.status_code))
                    info += '   fetched: error ({})\n'.format(rsp.status_code)
                    rv = 1
            except Exception:
                sys.stdout.flush()
                logger.error(traceback.format_exc())
                info += 'failure to determine confluence data\n'
                rv = 1

        confluence_instance_info = info

    def sensitive_config(key):
        if key in config:
            if config[key]:
                config[key] = '(set)'
            else:
                config[key] = '(set; empty)'

    # always sanitize out sensitive information
    sensitive_config('confluence_client_cert_pass')
    sensitive_config('confluence_publish_headers')
    sensitive_config('confluence_publish_token')
    sensitive_config('confluence_server_pass')

    # optional sanitization
    if not args.no_sanitize:
        sensitive_config('author')
        sensitive_config('confluence_client_cert')
        sensitive_config('confluence_global_labels')
        sensitive_config('confluence_jira_servers')
        sensitive_config('confluence_mentions')
        sensitive_config('confluence_parent_page')
        sensitive_config('confluence_parent_page_id_check')
        sensitive_config('confluence_proxy')
        sensitive_config('confluence_publish_root')
        sensitive_config('confluence_server_auth')
        sensitive_config('confluence_server_cookies')
        sensitive_config('confluence_server_user')
        sensitive_config('project')

        # remove confluence instance (attempt to keep scheme)
        if 'confluence_server_url' in config:
            value = config['confluence_server_url']
            parsed = urlparse(value)

            if parsed.scheme:
                value = parsed.scheme + '://<removed>'
            else:
                value = '(set; no scheme)'

            if parsed.netloc and parsed.netloc.endswith('atlassian.net'):
                value += ' (cloud)'

            config['confluence_server_url'] = value

        # remove space key, but track casing
        space_cfgs = [
            'confluence_space_key',
            'confluence_space_name',  # deprecated
        ]
        for space_cfg in space_cfgs:
            if space_cfg not in config:
                continue

            value = config[space_cfg]
            if value.startswith('~'):
                value = '(set; user)'
            elif value.isupper():
                value = '(set; upper)'
            elif value.islower():
                value = '(set; lower)'
            else:
                value = '(set; mixed)'
            config[space_cfg] = value

    print('')
    print('Confluence builder report has been generated.')
    print('Please copy the following text for the GitHub issue:')
    print('')
    logger.note('------------[ cut here ]------------')
    print('```')
    print('(system)')
    print(' platform:', single_line_version(platform.platform()))
    print('   python:', single_line_version(sys.version))
    print('   sphinx:', single_line_version(sphinx_version))
    print(' requests:', single_line_version(requests_version))
    print('  builder:', single_line_version(scb_version))

    print('')
    print('(configuration)')
    if config:
        for k, v in OrderedDict(sorted(config.items())).items():
            print('{}: {}'.format(k, v))
    else:
        print('~default configuration~')

    if configuration_load_issue:
        print('')
        print('(error loading configuration)')
        print(configuration_load_issue)

    if confluence_instance_info:
        print('')
        print('(confluence instance)')
        print(confluence_instance_info.rstrip())

    print('```')
    logger.note('------------[ cut here ]------------')

    return rv
Esempio n. 3
0
def wipe_main(args_parser):
    """
    wipe mainline

    The mainline for the 'wipe' action.

    Args:
        args_parser: the argument parser to use for argument processing

    Returns:
        the exit code
    """

    args_parser.add_argument('--danger', action='store_true')
    args_parser.add_argument('--parent', '-P', action='store_true')

    known_args = sys.argv[1:]
    args, unknown_args = args_parser.parse_known_args(known_args)
    if unknown_args:
        logger.warn('unknown arguments: {}'.format(' '.join(unknown_args)))

    work_dir = args.work_dir if args.work_dir else os.getcwd()

    # protection warning
    if not args.danger:
        print('')
        sys.stdout.flush()
        logger.warn('!!! DANGER DANGER DANGER !!!')
        print("""
A request has been made to attempt to wipe the pages from a configured
Confluence instance. This is a helper utility call to assist a user in cleaning
out a space since removing a bulk set of data may not be trivial for a user.

Note that this action is not reversible with this tool and may require
assistance from an administrator from a Confluence instance to recover pages.
Only use this action if you know what you are doing.

To use this action, the argument '--danger' must be set.
            """)
        sys.stdout.flush()
        logger.warn('!!! DANGER DANGER DANGER !!!')
        return 1

    # check configuration and prepare publisher
    dryrun = False
    publisher = None

    try:
        with temp_dir() as tmp_dir:
            with docutils_namespace():
                app = Sphinx(
                    work_dir,  # document sources
                    work_dir,  # directory with configuration
                    tmp_dir,  # output for built documents
                    tmp_dir,  # output for doctree files
                    'confluence',  # builder to execute
                    status=sys.stdout,  # sphinx status output
                    warning=sys.stderr)  # sphinx warning output

                aggressive_search = app.config.confluence_adv_aggressive_search
                dryrun = app.config.confluence_publish_dryrun
                server_url = app.config.confluence_server_url
                space_key = app.config.confluence_space_key
                parent_ref = app.config.confluence_parent_page

                # initialize the publisher (if permitted)
                if app.config.confluence_publish:
                    process_ask_configs(app.config)

                    publisher = ConfluencePublisher()
                    publisher.init(app.config)

    except Exception:
        sys.stdout.flush()
        logger.error(traceback.format_exc())
        if os.path.isfile(os.path.join(work_dir, 'conf.py')):
            logger.error('unable to load configuration')
        else:
            logger.error('no documentation/missing configuration')
        return 1

    if not publisher:
        logger.error('publishing not configured in sphinx configuration')
        return 1

    if args.parent and not parent_ref:
        logger.error('parent option provided but no parent page is configured')
        return 1

    # reminder warning
    print('')
    sys.stdout.flush()
    logger.warn('!!! DANGER DANGER DANGER !!!')
    print("""
A request has been made to attempt to wipe the pages from a configured
Confluence instance. This action is not reversible with this tool and may
require assistance from an administrator from a Confluence instance to recover
pages. Only use this action if you know what you are doing.
        """)
    sys.stdout.flush()

    logger.warn('!!! DANGER DANGER DANGER !!!')
    print('')

    if not ask_question('Are you sure you want to continue?'):
        return 0
    print('')

    # user has confirmed; start an attempt to wipe
    publisher.connect()

    base_page_id = None
    if args.parent:
        base_page_id = publisher.get_base_page_id()

    if aggressive_search:
        legacy_pages = publisher.get_descendants_compat(base_page_id)
    else:
        legacy_pages = publisher.get_descendants(base_page_id)

    print('         URL:', server_url)
    print('       Space:', space_key)
    if base_page_id:
        logger.note('       Pages: Child pages of ' + parent_ref)
    else:
        logger.note('       Pages: All Pages')
    print(' Total pages:', len(legacy_pages))
    if dryrun:
        print('     Dry run:', 'Enabled (no pages will be removed)')

    if not legacy_pages:
        print('')
        print('No pages detected on this space. Exiting...')
        return 0

    if args.verbose:
        print('-------------------------')
        page_names = []
        for p in legacy_pages:
            page_names.append(publisher._name_cache[p])
        sorted(page_names)
        print('\n'.join(page_names))
        print('-------------------------')

    print('')
    if not ask_question('Are you sure you want to REMOVE these pages?'):
        return 0
    print('')

    logger.info('Removing pages...', nonl=True)
    if dryrun:
        logger.info('')
    for page_id in legacy_pages:
        publisher.remove_page(page_id)
        if not dryrun:
            logger.info('.', nonl=True)
    if not dryrun:
        logger.info(__('done'))

    return 0