def write(self, build_docnames, updated_docnames, method='update'):
        docnames = self.env.all_docs
        if self.config.master_doc not in docnames:
            ConfluenceLogger.error('singleconfluence required master_doc')
            return

        root_doctitle = self._process_root_document()
        if not root_doctitle:
            ConfluenceLogger.error(
                'singleconfluence required title on master_doc')
            return

        with progress_message(__('assembling single confluence document')):
            self.env.toc_secnumbers = self.assemble_toc_secnumbers()
            self.env.toc_fignumbers = self.assemble_toc_fignumbers()

            # register title targets for references before assembling doc
            # re-works them into a single document
            for docname in docnames:
                doctree = self.env.get_doctree(docname)
                self._register_doctree_title_targets(docname, doctree)

            doctree = self.assemble_doctree()
            self._prepare_doctree_writing(self.config.master_doc, doctree)
            self.assets.processDocument(doctree, self.config.master_doc)

        with progress_message(__('writing single confluence document')):
            self.write_doc_serialized(self.config.master_doc, doctree)
            self.write_doc(self.config.master_doc, doctree)
Пример #2
0
def build_main(args_parser):
    """
    build mainline

    The mainline for the 'build' action.

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

    Returns:
        the exit code
    """

    args_parser.add_argument('-D', action='append', default=[], dest='define')
    args_parser.add_argument('--output-dir', '-o')

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

    defines = {}
    for val in args.define:
        try:
            key, val = val.split('=', 1)
            defines[key] = val
        except ValueError:
            logger.error('invalid define provided in command line')
            return 1

    work_dir = args.work_dir if args.work_dir else os.getcwd()
    if args.output_dir:
        output_dir = args.output_dir
    else:
        output_dir = os.path.join(work_dir, '_build', 'confluence')
    doctrees_dir = os.path.join(output_dir, '.doctrees')
    builder = args.action if args.action else DEFAULT_BUILDER

    verbosity = 0
    if args.verbose:
        try:
            verbosity = int(args.verbose)
        except ValueError:
            pass

    # run sphinx engine
    with docutils_namespace():
        app = Sphinx(
            work_dir,  # document sources
            work_dir,  # directory with configuration
            output_dir,  # output for generated documents
            doctrees_dir,  # output for doctree files
            builder,  # builder to execute
            confoverrides=defines,  # configuration overload
            freshenv=True,  # fresh environment
            verbosity=verbosity)  # verbosity
        app.build(force_all=True)

    return 0
Пример #3
0
    def write(self, build_docnames, updated_docnames, method='update'):
        docnames = self.env.all_docs
        if self.config.root_doc not in docnames:
            logger.error('singleconfluence requires root_doc')
            return

        root_doctitle = self._process_root_document()
        if not root_doctitle:
            logger.error('singleconfluence requires title on root_doc')
            return

        with progress_message(C('assembling single confluence document')):
            # assemble toc section/figure numbers
            #
            # Both the environment's `toc_secnumbers` and `toc_fignumbers`
            # are populated; however, they do not contain a complete list of
            # each document's section/figure numbers. The assembling process
            # will create a dictionary keys of '<docname>/<id>' which the writer
            # implementations can used to build desired references when invoked
            # with a `singleconfluence` builder. Unlike Sphinx's `singlehtml`
            # builder, this builder will update the existing number dictionaries
            # to hold the original mappings (for other post-transforms,
            # extensions, etc.) and the newer mappings for reference building.
            assembled_toc_secnumbers = self.assemble_toc_secnumbers()
            assembled_toc_fignumbers = self.assemble_toc_fignumbers()
            self.env.toc_secnumbers.setdefault(
                self.config.root_doc,
                {}).update(assembled_toc_secnumbers[self.config.root_doc])
            self.env.toc_fignumbers.setdefault(
                self.config.root_doc,
                {}).update(assembled_toc_fignumbers[self.config.root_doc])

            # register title targets for references before assembling doc
            # re-works them into a single document
            for docname in docnames:
                doctree = self.env.get_doctree(docname)
                self._register_doctree_title_targets(docname, doctree)

            doctree = self.assemble_doctree()
            self._prepare_doctree_writing(self.config.root_doc, doctree)
            self.assets.process_document(doctree, self.config.root_doc)

        with progress_message(C('writing single confluence document')):
            self.write_doc_serialized(self.config.root_doc, doctree)
            self.write_doc(self.config.root_doc, doctree)
Пример #4
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
Пример #5
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