File python-bugzilla-3.2.0+git.1726768917.5eedea3.obscpio of Package python-bugzilla
07070100000000000041ED00000000000000000000000266EC671500000000000000000000000000000000000000000000003500000000python-bugzilla-3.2.0+git.1726768917.5eedea3/.github07070100000001000081A400000000000000000000000166EC6715000000B7000000000000000000000000000000000000004400000000python-bugzilla-3.2.0+git.1726768917.5eedea3/.github/dependabot.ymlversion: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
target-branch: "main"
commit-message:
prefix: "ci"
07070100000002000041ED00000000000000000000000266EC671500000000000000000000000000000000000000000000003F00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/.github/workflows07070100000003000081A400000000000000000000000166EC67150000100A000000000000000000000000000000000000004900000000python-bugzilla-3.2.0+git.1726768917.5eedea3/.github/workflows/build.yml# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
# Derived from stock python-package.yml
name: CI
on: [push, pull_request]
jobs:
linter:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.x"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt -r test-requirements.txt setuptools
- name: Lint
run: |
pylint --output-format colorized --rcfile .pylintrc \
bugzilla-cli setup.py bugzilla examples tests
build:
# We stick with 20.04 to get access to python 3.6
# https://github.com/actions/setup-python/issues/544
runs-on: ubuntu-20.04
strategy:
matrix:
# python 3.6 is for rhel/centos8 compat
python-version: ["3.6", "3.x"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest pytest-cov
pip install -r requirements.txt -r test-requirements.txt
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest and generate coverage report
run: |
pytest --cov --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
# Run functional tests
integrationRO:
runs-on: ubuntu-latest
services:
mariadb:
image: mariadb:latest
env:
MARIADB_USER: bugs
MARIADB_DATABASE: bugs
MARIADB_PASSWORD: secret
MARIADB_ROOT_PASSWORD: supersecret
ports:
- 3306:3306
bugzilla:
image: ghcr.io/crazyscientist/bugzilla:test
ports:
- 80:80
strategy:
matrix:
python-version: ["3.x"]
steps:
- uses: actions/checkout@v4
- name: Install MariaDB utils
run: sudo apt install --no-install-recommends -q -y mariadb-client
- name: Restore DB dump
run: mariadb -h 127.0.0.1 -P 3306 --password=secret -u bugs bugs < tests/services/bugs.sql
- name: Store API key
run: |
mkdir -p ~/.config/python-bugzilla/
cp tests/services/bugzillarc ~/.config/python-bugzilla/
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-cov
pip install -r requirements.txt -r test-requirements.txt
- name: Test with pytest
run: pytest --ro-integration
env:
BUGZILLA_URL: http://localhost
# Build and install on Windows
windows:
runs-on: windows-latest
strategy:
matrix:
python-version: ["3.x"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Build tarball & install
run: |
python setup.py sdist
pip install --find-links dist python-bugzilla
07070100000004000081A400000000000000000000000166EC671500000044000000000000000000000000000000000000003800000000python-bugzilla-3.2.0+git.1726768917.5eedea3/.gitignore*.pyc
*.swp
MANIFEST
dist
build
.cache
.coverage
.tox
.pytest_cache
07070100000005000081A400000000000000000000000166EC6715000000FD000000000000000000000000000000000000003600000000python-bugzilla-3.2.0+git.1726768917.5eedea3/.mailmap<wwoods@redhat.com> <wwoods@zebes.localdomain>
<wwoods@redhat.com> <wwoods@wills-mini.localdomain>
Will Woods <wwoods@redhat.com> <root@grinch.usersys.redhat.com>
<wwoods@redhat.com> <wwoods@metroid.rdu.redhat.com>
Luke Macken <lmacken@redhat.com>
07070100000006000081A400000000000000000000000166EC671500000133000000000000000000000000000000000000003900000000python-bugzilla-3.2.0+git.1726768917.5eedea3/.packit.yml---
upstream_project_url: https://github.com/python-bugzilla/python-bugzilla
jobs:
- job: copr_build
trigger: commit
metadata:
targets:
- fedora-all
- epel-8-x86_64
- job: tests
trigger: commit
metadata:
targets:
- fedora-all
- epel-8-x86_64
07070100000007000081A400000000000000000000000166EC6715000005D1000000000000000000000000000000000000003700000000python-bugzilla-3.2.0+git.1726768917.5eedea3/.pylintrc[MASTER]
persistent=no
[MESSAGES CONTROL]
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once).
disable=Design,Format,Similarities,invalid-name,missing-docstring,locally-disabled,unnecessary-lambda,fixme,global-statement,broad-except,bare-except,wrong-import-position,consider-using-ternary,len-as-condition,no-else-return,useless-object-inheritance,inconsistent-return-statements,consider-using-dict-comprehension,import-outside-toplevel,use-a-generator,consider-using-with,consider-using-f-string,unspecified-encoding,use-implicit-booleaness-not-comparison
enable=fixme
[REPORTS]
# Tells whether to display a full report or only the messages
reports=no
score=no
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=100
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO
[VARIABLES]
# A regular expression matching the beginning of the name of dummy variables
# (i.e. not used).
dummy-variables-rgx=dummy.*|ignore.*|.*_ignore
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=_
07070100000008000081A400000000000000000000000166EC67150000079C000000000000000000000000000000000000003D00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/CONTRIBUTING.md# Setting up the environment
If you already have system installed versions of python-bugzilla
dependencies, running the command line from git is as simple as doing:
cd python-bugzilla.git
./bugzilla-cli [arguments]
# Running tests
Our test suite uses pytest. If your system has dependencies already, the
quick unit test suite is invoked simply with:
pytest
## Read-Only Functional tests
There are more comprehensive, readonly functional tests that run against
several public bugzilla instances, but they are not run by default. No
login account is required. Run them with:
pytest --ro-functional
## Read/Write Functional Tests.
Read/Write functional tests use bugzilla.stage.redhat.com, which is a
bugzilla instance specifically for this type of testing. Data is occasionally
hard synced with regular bugzilla.redhat.com, and all local edits are
removed. Login accounts are also synced. If you want access to
bugzilla.stage.redhat.com, sign up for a regular bugzilla.redhat.com login
and wait for the next sync period.
Before running these tests, you'll need to cache login credentials.
Example:
./bugzilla-cli --bugzilla=bugzilla.stage.redhat.com --username=$USER login
pytest --rw-functional
## Testing across python versions
To test all supported python versions, run tox using any of the following.
tox
tox -- --ro-functional
tox -- --rw-functional
# pylint and pycodestyle
To test for pylint or pycodestyle violations, you can run:
./setup.py pylint
Note: This expects that you already have pylint and pycodestyle installed.
# Patch Submission
If you are submitting a patch, ensure the following:
[REQ] verify that no new pylint or pycodestyle violations
[REQ] run basic unit test suite across all python versions as described
above.
Running any of the functional tests is not a requirement for patch submission,
but please give them a go if you are interested.
07070100000009000081A400000000000000000000000166EC6715000046AC000000000000000000000000000000000000003500000000python-bugzilla-3.2.0+git.1726768917.5eedea3/COPYING GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program 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 2 of the License, or
(at your option) any later version.
This program 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 this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
0707010000000A000081A400000000000000000000000166EC6715000000F4000000000000000000000000000000000000003900000000python-bugzilla-3.2.0+git.1726768917.5eedea3/MANIFEST.ininclude COPYING CONTRIBUTING.md MANIFEST.in README.md NEWS.md
include xmlrpc-api-notes.txt
include python-bugzilla.spec
include *requirements.txt
include man/bugzilla.rst
recursive-include examples *.py
recursive-include tests *.py *.txt *.cfg
0707010000000B000081A400000000000000000000000166EC6715000015CF000000000000000000000000000000000000003500000000python-bugzilla-3.2.0+git.1726768917.5eedea3/NEWS.md# python-bugzilla release news
## Release 3.3.0 (June, 2024)
- Expose error codes from the REST API (Stanislav Levin)
- Fixed broken link in documentation (Danilo C. L. de Paula)
- Set `Bug.weburl` that is compatible with the REST API
- Do not convert 'blocks' or 'depends' to int in `Bugzilla.build_update` (Adam Williamson)
- Use proper REST API route for getting a single bug
- Avoid duplicate entries when one id is 0 (Ricardo Branco)
- Removed unused argument from `Bugzilla.add_dict`
- Fixed API key leak (Ricardo Branco)
- Automatically include alias in include_fields in `Bugzilla._getbugs`
- Added method `Bugzilla.query_return_extra`
- cli: Support --field and --field-json for bugzilla attach
## Release 3.2.0 (January 12, 2022)
- Use soon-to-be-required Authorization header for RH bugzilla
- Remove cookie auth support
## Release 3.1.0 (July 27, 2021)
- Detect bugzilla.stage.redhat.com as RHBugzilla
- Add limit as option to build_query (Ivan Lausuch)
## Release 3.0.2 (November 12, 2020)
- Fix API key leaking into requests exceptions
## Release 3.0.1 (October 07, 2020)
- Skip man page generation to fix build on Windows (Alexander Todorov)
## Release 3.0.0 (October 03, 2020)
- Drop python2 support
- New option `bugzilla modify --minor-update option`
- requests: use PYTHONBUGZILLA_REQUESTS_TIMEOUT env variable
- xmlrpc: Don't add api key to passed in user dictionary
## Release 2.5.0 (July 04, 2020)
- cli: Add query --extrafield, --includefield, --excludefield
- Revive bugzilla.rhbugzilla.RHBugzilla import path
## Release 2.4.0 (June 29, 2020)
- Bugzilla REST API support
- Add --json command line output option
- Add APIs for Bugzilla Groups (Pierre-Yves Chibon)
- Add `Bugzilla.get_requests_session()` API to access raw requests Session
- Add `Bugzilla.get_xmlrpc_proxy()` API to access raw ServerProxy
- Add `Bugzilla requests_session=` init parameter to pass in auth, etc.
- Add `bugzilla attach --ignore-obsolete` (Čestmír Kalina)
- Add `bugzilla login --api-key` for API key prompting (Danilo C. L. de
Paula)
- Add `bugzilla new --private`
## Release 2.3.0 (August 26, 2019)
- restrict-login suppot (Viliam Krizan)
- cli: Add support for private attachments (Brian 'Redbeard' Harrington)
- Fix python3 deprecation warnings
- Drop python 3.3 support, minimum python3 is python 3.4 now
## Release 2.2.0 (August 11, 2018)
- Port tests to pytest
- cli: --cert Client side certificate support (Tobias Wolter)
- cli: add ability to post comment while sending attachment (Jeff Mahoney)
- cli: Add --comment-tag option
- cli: Add info --active-components
- Add a raw Product.get wrapper API
## Release 2.1.0 (March 30, 2017)
- Support for bugzilla 5 API Keys (Dustin J. Mitchell)
- bugzillarc can be used to set default URL for the cli tool
- Revive update_flags wrapper
- Bug fixes and minor improvements
## Release 2.0.0 (Feb 08 2017)
This release contains several small to medium API breaks. I expect most users
won't notice any difference. I previously outlined the changes here:
https://lists.fedorahosted.org/archives/list/python-bugzilla@lists.fedorahosted.org/thread/WCYPOKJZFYOW7RRT44FCM5GQU26O56K4/
The major changes are:
- Several fixes for use with bugzilla 5
- Bugzilla.bug_autorefresh now defaults to False
- Credentials are now cached in ~/.cache/python-bugzilla/
- bin/bugzilla was converted to argparse
- bugzilla query --boolean_chart option is removed
- Unify command line flags across sub commands
## Release 1.2.2 (Sep 23 2015)
- Switch hosting to http://github.com/python-bugzilla/python-bugzilla
- Fix requests usage when ndg-httpsclient is installed (Arun Babu
Neelicattu)
- Add non-rhbz support for getting bug comments (AJ Lewis)
- Misc bugfixes and improvements
## Release 1.2.1 (May 22 2015)
- bin/bugzilla: Add --ensure-logged-in option
- Fix get_products with bugzilla.redhat.com
- A few other minor improvements
## Release 1.2.0 (Apr 08 2015)
- Add bugzilla new/query/modify --field flag (Arun Babu Neelicattu)
- API support for ExternalBugs (Arun Babu Neelicattu, Brian Bouterse)
- Add new/modify --alias support (Adam Williamson)
- Bugzilla.logged_in now returns live state (Arun Babu Neelicattu)
- Fix getbugs API with latest Bugzilla releases
## Release 1.1.0 (Jun 01 2014)
- Support for bugzilla tokens (Arun Babu Nelicattu)
- bugzilla: Add query/modify --tags
- bugzilla --login: Allow to login and run a command in one shot
- bugzilla --no-cache-credentials: Don't use or save cached credentials
when using the CLI
- Show bugzilla errors when login fails
- Don't pull down attachments in bug.refresh(), need to get
bug.attachments manually
- Add Bugzilla bug_autorefresh parameter.
## Release 1.0.0 (Mar 25 2014)
- Python 3 support (Arun Babu Neelicattu)
- Port to python-requests (Arun Babu Neelicattu)
- bugzilla: new: Add --keywords, --assigned_to, --qa_contact (Lon Hohberger)
- bugzilla: query: Add --quicksearch, --savedsearch
- bugzilla: query: Support saved searches with --from-url
- bugzilla: --sub-component support for all relevant commands
## Release 0.9.0 (Jun 19 2013)
- CVE-2013-2191: Switch to pycurl to get SSL host and cert validation
- bugzilla: modify: add --dependson (Don Zickus)
- bugzilla: new: add --groups option (Paul Frields)
- bugzilla: modify: Allow setting nearly every bug parameter
- NovellBugzilla implementation removed, can't get it to work
## Release 0.8.0 (Feb 16 2013)
- Replace usage of non-upstream Red Hat bugzilla APIs with upstream replacements
- Test suite improvements, nearly complete code coverage
- Fix all open bug reports and RFEs
0707010000000C000081A400000000000000000000000166EC671500000428000000000000000000000000000000000000003700000000python-bugzilla-3.2.0+git.1726768917.5eedea3/README.md[](https://github.com/python-bugzilla/python-bugzilla/actions?query=workflow%3ACI)
[](https://codecov.io/gh/python-bugzilla/python-bugzilla)
[](https://pypi.org/project/python-bugzilla/)
# python-bugzilla
This package provides two bits:
* `bugzilla` python module for talking to a [Bugzilla](https://www.bugzilla.org/) instance over XMLRPC or REST
* `/usr/bin/bugzilla` command line tool for performing actions from the command line: create or edit bugs, various queries, etc.
This was originally written specifically for [Red Hat's Bugzilla instance](https://bugzilla.redhat.com)
and is used heavily at Red Hat and in Fedora, but it should be
generically useful.
You can find some code examples in the [examples](examples) directory.
For questions about submitting patches, see [CONTRIBUTING.md](CONTRIBUTING.md)
0707010000000D000041ED00000000000000000000000266EC671500000000000000000000000000000000000000000000003600000000python-bugzilla-3.2.0+git.1726768917.5eedea3/bugzilla0707010000000E000081A400000000000000000000000166EC671500000CCD000000000000000000000000000000000000003E00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/bugzilla-api.txtThis document tracks upstream bugzilla API changes, and related info.
Upstream timeline
=================
Here's a timeline of the evolution of the upstream bugzilla XMLRPC/REST API:
Bugzilla 2.*:
No XMLRPC API that I can tell
Bugzilla 3.0:
http://www.bugzilla.org/docs/3.0/html/api/index.html
Bug.legal_values
Bug.get_bugs:
returns: id, alias, summary, creation_time, last_change_time
Bug.create
Bugzilla.version
Bugzilla.timezone
Product.get_selectable_products
Product.get_enterable_products
Product.get_accessible_products
Product.get_products
User.login
User.logout
User.offer_account_by_email
User.create
Bugzilla 3.2:
http://www.bugzilla.org/docs/3.2/en/html/api/
Bug: RENAME: get_bugs->get, get_bugs should still work
Bug.add_comment
Bugzilla.extensions
Product: RENAME: get_products->get, get_products should still work
Bugzilla 3.4:
http://www.bugzilla.org/docs/3.4/en/html/api/
Bug.comments
Bug.history
Bug.search
Bug.update_see_also
Bugzilla.time
Bugzilla: DEPRECATED: timezone, use time instead
User.get
Util.filter_fields
Util.validate
Bugzilla 3.6:
http://www.bugzilla.org/docs/3.6/en/html/api/
Bug.attachments
Bug.fields
Bug: DEPRECATED: legal_values
Bugzilla: timezone now always returns UTC+0000
Bugzilla 4.0:
http://www.bugzilla.org/docs/4.0/en/html/api/
Bug.add_attachment
Bug.update
Util.filter_wants
Bugzilla 4.2:
http://www.bugzilla.org/docs/4.2/en/html/api/
Group.create
Product.create
Bugzilla 4.4:
http://www.bugzilla.org/docs/4.4/en/html/api/
Bug.update_tags
Bugzilla.parameters
Bugzilla.last_audit_time
Classification.get
Group.update
Product.update
User.update
Util.translate
Util.params_to_objects
Bugzilla 5.0: (July 2015)
https://bugzilla.readthedocs.io/en/5.0/api/index.html
Bug.update_attachment
Bug.search/update_comment_tags
Bug.search:
search() now supports --from-url style, like rhbz before it
search() now supports quicksearch
Bug.update:
update() alias is now a hash of add/remove/set, but has back compat
update() can take 'flags' config now
Component (new, or newly documented?)
Component.create
User.valid_login
Bugzilla latest/tip:
https://bugzilla.readthedocs.io/en/latest/api/index.html
Misc info
=========
Bugzilla REST API code link
https://github.com/bugzilla/bugzilla/tree/5.2/Bugzilla/WebService/Server/REST/Resources
Redhat Bugzilla: 5.0 based with extensions
https://bugzilla.redhat.com/docs/en/html/api/
Bug.search has --from-url extension
Bug.update has more hashing support
extra_fields for fetching comments, attachments, etc at Bug.get time
ExternalBugs extension: https://bugzilla.redhat.com/docs/en/html/api/extensions/ExternalBugs/lib/WebService.html
Fedora infrastructure python-bugzilla consumers:
https://infrastructure.fedoraproject.org/cgit/ansible.git/tree/roles/distgit/pagure/templates/pagure-sync-bugzilla.py.j2
https://github.com/fedora-infra/bodhi/blob/develop/bodhi/server/bugs.py
https://github.com/fedora-infra/fas/blob/develop/tools/export-bugzilla.py
0707010000000F000081ED00000000000000000000000166EC6715000000A2000000000000000000000000000000000000003A00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/bugzilla-cli#!/usr/bin/env python3
# This is a small wrapper script to simplify running the 'bugzilla'
# cli tool from a git checkout
from bugzilla import _cli
_cli.main()
07070100000010000081A400000000000000000000000166EC67150000047F000000000000000000000000000000000000004200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/bugzilla/__init__.py# python-bugzilla - a Python interface to bugzilla using xmlrpclib.
#
# Copyright (C) 2007, 2008 Red Hat Inc.
# Author: Will Woods <wwoods@redhat.com>
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
from .apiversion import version, __version__
from .base import Bugzilla
from .exceptions import BugzillaError
from .oldclasses import (Bugzilla3, Bugzilla32, Bugzilla34, Bugzilla36,
Bugzilla4, Bugzilla42, Bugzilla44,
NovellBugzilla, RHBugzilla, RHBugzilla3, RHBugzilla4)
# This is the public API. If you are explicitly instantiating any other
# class, using some function, or poking into internal files, don't complain
# if things break on you.
__all__ = [
"Bugzilla3", "Bugzilla32", "Bugzilla34", "Bugzilla36",
"Bugzilla4", "Bugzilla42", "Bugzilla44",
"NovellBugzilla",
"RHBugzilla3", "RHBugzilla4", "RHBugzilla",
'BugzillaError',
'Bugzilla', "version",
]
# Clear all other locals() from the public API
for __sym in locals().copy():
if __sym.startswith("__") or __sym in __all__:
continue
locals().pop(__sym)
locals().pop("__sym")
07070100000011000081A400000000000000000000000166EC671500001459000000000000000000000000000000000000004400000000python-bugzilla-3.2.0+git.1726768917.5eedea3/bugzilla/_authfiles.py# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import configparser
import os
from logging import getLogger
import urllib.parse
from ._util import listify
log = getLogger(__name__)
def _parse_hostname(url):
# If http://example.com is passed, netloc=example.com path=""
# If just example.com is passed, netloc="" path=example.com
parsedbits = urllib.parse.urlparse(url)
return parsedbits.netloc or parsedbits.path
def _makedirs(path):
if os.path.exists(os.path.dirname(path)):
return
os.makedirs(os.path.dirname(path), 0o700)
def _default_cache_location(filename):
"""
Determine default location for passed xdg filename.
example: ~/.cache/python-bugzilla/bugzillarc
"""
return os.path.expanduser("~/.cache/python-bugzilla/%s" % filename)
class _BugzillaRCFile(object):
"""
Helper class for interacting with bugzillarc files
"""
@staticmethod
def get_default_configpaths():
paths = [
'/etc/bugzillarc',
'~/.bugzillarc',
'~/.config/python-bugzilla/bugzillarc',
]
return paths
def __init__(self):
self._cfg = None
self._configpaths = None
self.set_configpaths(None)
def set_configpaths(self, configpaths):
configpaths = [os.path.expanduser(p) for p in
listify(configpaths or [])]
cfg = configparser.ConfigParser()
read_files = cfg.read(configpaths)
if read_files:
log.info("Found bugzillarc files: %s", read_files)
self._cfg = cfg
self._configpaths = configpaths or []
def get_configpaths(self):
return self._configpaths[:]
def get_default_url(self):
"""
Grab a default URL from bugzillarc [DEFAULT] url=X
"""
cfgurl = self._cfg.defaults().get("url", None)
if cfgurl is not None:
log.debug("bugzillarc: found cli url=%s", cfgurl)
return cfgurl
def parse(self, url):
"""
Find the section for the passed URL domain, and return all the fields
"""
section = ""
log.debug("bugzillarc: Searching for config section matching %s", url)
urlhost = _parse_hostname(url)
for sectionhost in sorted(self._cfg.sections()):
# If the section is just a hostname, make it match
# If the section has a / in it, do a substring match
if "/" not in sectionhost:
if sectionhost == urlhost:
section = sectionhost
elif sectionhost in url:
section = sectionhost
if section:
log.debug("bugzillarc: Found matching section: %s", section)
break
if not section:
log.debug("bugzillarc: No section found")
return {}
return dict(self._cfg.items(section))
def save_api_key(self, url, api_key):
"""
Save the API_KEY in the config file. We use the last file
in the configpaths list, which is the one with the highest
precedence.
"""
configpaths = self.get_configpaths()
if not configpaths:
return None
config_filename = configpaths[-1]
section = _parse_hostname(url)
cfg = configparser.ConfigParser()
cfg.read(config_filename)
if section not in cfg.sections():
cfg.add_section(section)
cfg.set(section, 'api_key', api_key.strip())
_makedirs(config_filename)
with open(config_filename, 'w') as configfile:
cfg.write(configfile)
return config_filename
class _BugzillaTokenCache(object):
"""
Class for interacting with a .bugzillatoken cache file
"""
@staticmethod
def get_default_path():
return _default_cache_location("bugzillatoken")
def __init__(self):
self._filename = None
self._cfg = None
def _get_domain(self, url):
domain = urllib.parse.urlparse(url)[1]
if domain and domain not in self._cfg.sections():
self._cfg.add_section(domain)
return domain
def get_value(self, url):
domain = self._get_domain(url)
if domain and self._cfg.has_option(domain, 'token'):
return self._cfg.get(domain, 'token')
return None
def set_value(self, url, value):
if self.get_value(url) == value:
return
domain = self._get_domain(url)
if value is None:
self._cfg.remove_option(domain, 'token')
else:
self._cfg.set(domain, 'token', value)
if self._filename:
_makedirs(self._filename)
with open(self._filename, 'w') as _cfg:
log.debug("Saving to _cfg")
self._cfg.write(_cfg)
def get_filename(self):
return self._filename
def set_filename(self, filename):
log.debug("Using tokenfile=%s", filename)
cfg = configparser.ConfigParser()
if filename:
cfg.read(filename)
self._filename = filename
self._cfg = cfg
07070100000012000081A400000000000000000000000166EC67150000202B000000000000000000000000000000000000004600000000python-bugzilla-3.2.0+git.1726768917.5eedea3/bugzilla/_backendbase.py# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
from logging import getLogger
import requests
log = getLogger(__name__)
class _BackendBase(object):
"""
Backends are thin wrappers around the different bugzilla API paradigms
(XMLRPC, REST). This base class defines the public API for the rest of
the code, but this is all internal to the library.
"""
def __init__(self, url, bugzillasession):
self._url = url
self._bugzillasession = bugzillasession
@staticmethod
def probe(url):
try:
requests.head(url, timeout=10).raise_for_status()
return True # pragma: no cover
except Exception as e:
log.debug("Failed to probe url=%s : %s", url, str(e))
return False
#################
# Internal APIs #
#################
def get_xmlrpc_proxy(self):
"""
Provides the raw XMLRPC proxy to API users of Bugzilla._proxy
"""
raise NotImplementedError()
def is_rest(self):
"""
:returns: True if this is the REST backend
"""
return False
def is_xmlrpc(self):
"""
:returns: True if this is the XMLRPC backend
"""
return False
######################
# Bugzilla info APIs #
######################
def bugzilla_version(self):
"""
Fetch bugzilla version string
http://bugzilla.readthedocs.io/en/latest/api/core/v1/bugzilla.html#version
"""
raise NotImplementedError()
#######################
# Bug attachment APIs #
#######################
def bug_attachment_get(self, attachment_ids, paramdict):
"""
Fetch bug attachments IDs. One part of:
http://bugzilla.readthedocs.io/en/latest/api/core/v1/attachment.html#get-attachment
"""
raise NotImplementedError()
def bug_attachment_get_all(self, bug_ids, paramdict):
"""
Fetch all bug attachments IDs. One part of
http://bugzilla.readthedocs.io/en/latest/api/core/v1/attachment.html#get-attachment
"""
raise NotImplementedError()
def bug_attachment_create(self, bug_ids, data, paramdict):
"""
Create a bug attachment
http://bugzilla.readthedocs.io/en/latest/api/core/v1/attachment.html#create-attachment
:param data: raw Bytes data of the attachment to attach. API will
encode this correctly if you pass it in and 'data' is not in
paramdict.
"""
raise NotImplementedError()
def bug_attachment_update(self, attachment_ids, paramdict):
"""
Update a bug attachment
http://bugzilla.readthedocs.io/en/latest/api/core/v1/attachment.html#update-attachment
"""
raise NotImplementedError()
############
# bug APIs #
############
def bug_comments(self, bug_ids, paramdict):
"""
Fetch bug comments
http://bugzilla.readthedocs.io/en/latest/api/core/v1/comment.html#get-comments
"""
raise NotImplementedError()
def bug_create(self, paramdict):
"""
Create a new bug
http://bugzilla.readthedocs.io/en/latest/api/core/v1/bug.html#create-bug
"""
raise NotImplementedError()
def bug_fields(self, paramdict):
"""
Query available bug field values
http://bugzilla.readthedocs.io/en/latest/api/core/v1/field.html#fields
"""
raise NotImplementedError()
def bug_get(self, bug_ids, aliases, paramdict):
"""
Lookup bug data by ID
http://bugzilla.readthedocs.io/en/latest/api/core/v1/bug.html#get-bug
"""
raise NotImplementedError()
def bug_history(self, bug_ids, paramdict):
"""
Lookup bug history
http://bugzilla.readthedocs.io/en/latest/api/core/v1/bug.html#bug-history
"""
raise NotImplementedError()
def bug_search(self, paramdict):
"""
Search/query bugs
http://bugzilla.readthedocs.io/en/latest/api/core/v1/bug.html#search-bugs
"""
raise NotImplementedError()
def bug_update(self, bug_ids, paramdict):
"""
Update bugs
http://bugzilla.readthedocs.io/en/latest/api/core/v1/bug.html#update-bug
"""
raise NotImplementedError()
def bug_update_tags(self, bug_ids, paramdict):
"""
Update bug tags
https://www.bugzilla.org/docs/4.4/en/html/api/Bugzilla/WebService/Bug.html#update_tags
"""
raise NotImplementedError()
##################
# Component APIs #
##################
def component_create(self, paramdict):
"""
Create component
https://bugzilla.readthedocs.io/en/latest/api/core/v1/component.html#create-component
"""
raise NotImplementedError()
def component_update(self, paramdict):
"""
Update component
https://bugzilla.readthedocs.io/en/latest/api/core/v1/component.html#update-component
"""
raise NotImplementedError()
###############################
# ExternalBugs extension APIs #
###############################
def externalbugs_add(self, paramdict):
"""
https://bugzilla.redhat.com/docs/en/html/integrating/api/Bugzilla/Extension/ExternalBugs/WebService.html#add-external-bug
"""
raise NotImplementedError()
def externalbugs_update(self, paramdict):
"""
https://bugzilla.redhat.com/docs/en/html/integrating/api/Bugzilla/Extension/ExternalBugs/WebService.html#update-external-bug
"""
raise NotImplementedError()
def externalbugs_remove(self, paramdict):
"""
https://bugzilla.redhat.com/docs/en/html/integrating/api/Bugzilla/Extension/ExternalBugs/WebService.html#remove-external-bug
"""
raise NotImplementedError()
##############
# Group APIs #
##############
def group_get(self, paramdict):
"""
https://bugzilla.readthedocs.io/en/latest/api/core/v1/group.html#get-group
"""
raise NotImplementedError()
################
# Product APIs #
################
def product_get(self, paramdict):
"""
Fetch product details
http://bugzilla.readthedocs.io/en/latest/api/core/v1/product.html#get-product
"""
raise NotImplementedError()
def product_get_accessible(self):
"""
List accessible products
http://bugzilla.readthedocs.io/en/latest/api/core/v1/product.html#list-products
"""
raise NotImplementedError()
def product_get_enterable(self):
"""
List enterable products
http://bugzilla.readthedocs.io/en/latest/api/core/v1/product.html#list-products
"""
raise NotImplementedError()
def product_get_selectable(self):
"""
List selectable products
http://bugzilla.readthedocs.io/en/latest/api/core/v1/product.html#list-products
"""
raise NotImplementedError()
#############
# User APIs #
#############
def user_create(self, paramdict):
"""
Create user
http://bugzilla.readthedocs.io/en/latest/api/core/v1/user.html#create-user
"""
raise NotImplementedError()
def user_get(self, paramdict):
"""
Get user info
http://bugzilla.readthedocs.io/en/latest/api/core/v1/user.html#get-user
"""
raise NotImplementedError()
def user_login(self, paramdict):
"""
Log in to bugzilla
http://bugzilla.readthedocs.io/en/latest/api/core/v1/user.html#login
"""
raise NotImplementedError()
def user_logout(self):
"""
Log out of bugzilla
http://bugzilla.readthedocs.io/en/latest/api/core/v1/user.html#logout
"""
raise NotImplementedError()
def user_update(self, paramdict):
"""
Update user
http://bugzilla.readthedocs.io/en/latest/api/core/v1/user.html#update-user
"""
raise NotImplementedError()
07070100000013000081A400000000000000000000000166EC67150000200F000000000000000000000000000000000000004600000000python-bugzilla-3.2.0+git.1726768917.5eedea3/bugzilla/_backendrest.py# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import base64
import json
import logging
import os
from ._backendbase import _BackendBase
from .exceptions import BugzillaError, BugzillaHTTPError
from ._util import listify
log = logging.getLogger(__name__)
def _update_key(indict, updict, key):
if key not in indict:
indict[key] = {}
indict[key].update(updict.get(key, {}))
class _BackendREST(_BackendBase):
"""
Internal interface for direct calls to bugzilla's REST API
"""
def __init__(self, url, bugzillasession):
_BackendBase.__init__(self, url, bugzillasession)
self._bugzillasession.set_rest_defaults()
#########################
# Internal REST helpers #
#########################
def _handle_error(self, e):
response = getattr(e, "response", None)
if response is None:
raise e # pragma: no cover
if response.status_code in [400, 401, 404]:
self._handle_error_response(response.text)
raise e
def _handle_error_response(self, text):
try:
result = json.loads(text)
except json.JSONDecodeError:
return
if result.get("error"):
raise BugzillaError(result["message"], code=result["code"])
def _handle_response(self, text):
try:
ret = dict(json.loads(text))
except Exception: # pragma: no cover
log.debug("Failed to parse REST response. Output is:\n%s", text)
raise
if ret.get("error", False): # pragma: no cover
raise BugzillaError(ret["message"], code=ret["code"])
return ret
def _op(self, method, apiurl, paramdict=None):
fullurl = os.path.join(self._url, apiurl.lstrip("/"))
log.debug("Bugzilla REST %s %s params=%s", method, fullurl, paramdict)
data = None
authparams = self._bugzillasession.get_auth_params()
if method == "GET":
authparams.update(paramdict or {})
else:
data = json.dumps(paramdict or {})
try:
response = self._bugzillasession.request(
method, fullurl, data=data, params=authparams
)
except BugzillaHTTPError as e:
self._handle_error(e)
return self._handle_response(response.text)
def _get(self, *args, **kwargs):
return self._op("GET", *args, **kwargs)
def _put(self, *args, **kwargs):
return self._op("PUT", *args, **kwargs)
def _post(self, *args, **kwargs):
return self._op("POST", *args, **kwargs)
#######################
# API implementations #
#######################
def get_xmlrpc_proxy(self):
raise BugzillaError("You are using the bugzilla REST API, "
"so raw XMLRPC access is not provided.")
def is_rest(self):
return True
def bugzilla_version(self):
return self._get("/version")
def bug_create(self, paramdict):
return self._post("/bug", paramdict)
def bug_fields(self, paramdict):
return self._get("/field/bug", paramdict)
def bug_get(self, bug_ids, aliases, paramdict):
bug_list = listify(bug_ids)
alias_list = listify(aliases)
data = paramdict.copy()
# FYI: The high-level API expects the backends to raise an exception
# when retrieval of a single bug fails (default behavior of the XMLRPC
# API), but the REST API simply returns an empty search result set.
# To ensure compliant behavior, the REST backend needs to use the
# explicit URL to get a single bug.
if len(bug_list or []) + len(alias_list or []) == 1:
for id_list in (bug_list, alias_list):
if id_list:
return self._get("/bug/%s" % id_list[0], data)
data["id"] = bug_list
data["alias"] = alias_list
ret = self._get("/bug", data)
return ret
def bug_attachment_get(self, attachment_ids, paramdict):
# XMLRPC supported mutiple fetch at once, but not REST
ret = {}
for attid in listify(attachment_ids):
out = self._get("/bug/attachment/%s" % attid, paramdict)
_update_key(ret, out, "attachments")
_update_key(ret, out, "bugs")
return ret
def bug_attachment_get_all(self, bug_ids, paramdict):
# XMLRPC supported mutiple fetch at once, but not REST
ret = {}
for bugid in listify(bug_ids):
out = self._get("/bug/%s/attachment" % bugid, paramdict)
_update_key(ret, out, "attachments")
_update_key(ret, out, "bugs")
return ret
def bug_attachment_create(self, bug_ids, data, paramdict):
if data is not None and "data" not in paramdict:
paramdict["data"] = base64.b64encode(data).decode("utf-8")
paramdict["ids"] = listify(bug_ids)
return self._post("/bug/%s/attachment" % paramdict["ids"][0],
paramdict)
def bug_attachment_update(self, attachment_ids, paramdict):
paramdict["ids"] = listify(attachment_ids)
return self._put("/bug/attachment/%s" % paramdict["ids"][0], paramdict)
def bug_comments(self, bug_ids, paramdict):
# XMLRPC supported mutiple fetch at once, but not REST
ret = {}
for bugid in bug_ids:
out = self._get("/bug/%s/comment" % bugid, paramdict)
_update_key(ret, out, "bugs")
return ret
def bug_history(self, bug_ids, paramdict):
# XMLRPC supported mutiple fetch at once, but not REST
ret = {"bugs": []}
for bugid in bug_ids:
out = self._get("/bug/%s/history" % bugid, paramdict)
ret["bugs"].extend(out.get("bugs", []))
return ret
def bug_search(self, paramdict):
return self._get("/bug", paramdict)
def bug_update(self, bug_ids, paramdict):
data = paramdict.copy()
data["ids"] = listify(bug_ids)
return self._put("/bug/%s" % data["ids"][0], data)
def bug_update_tags(self, bug_ids, paramdict):
raise BugzillaError("No REST API available for bug_update_tags")
def component_create(self, paramdict):
return self._post("/component", paramdict)
def component_update(self, paramdict):
if "ids" in paramdict:
apiurl = str(listify(paramdict["ids"])[0]) # pragma: no cover
if "names" in paramdict:
apiurl = ("%(product)s/%(component)s" %
listify(paramdict["names"])[0])
return self._put("/component/%s" % apiurl, paramdict)
def externalbugs_add(self, paramdict): # pragma: no cover
raise BugzillaError(
"No REST API available yet for externalbugs_add")
def externalbugs_remove(self, paramdict): # pragma: no cover
raise BugzillaError(
"No REST API available yet for externalbugs_remove")
def externalbugs_update(self, paramdict): # pragma: no cover
raise BugzillaError(
"No REST API available yet for externalbugs_update")
def group_get(self, paramdict):
return self._get("/group", paramdict)
def product_get(self, paramdict):
return self._get("/product/get", paramdict)
def product_get_accessible(self):
return self._get("/product_accessible")
def product_get_enterable(self):
return self._get("/product_enterable")
def product_get_selectable(self):
return self._get("/product_selectable")
def user_create(self, paramdict):
return self._post("/user", paramdict)
def user_get(self, paramdict):
return self._get("/user", paramdict)
def user_login(self, paramdict):
return self._get("/login", paramdict)
def user_logout(self):
return self._get("/logout")
def user_update(self, paramdict):
urlid = None
if "ids" in paramdict:
urlid = listify(paramdict["ids"])[0] # pragma: no cover
if "names" in paramdict:
urlid = listify(paramdict["names"])[0]
return self._put("/user/%s" % urlid, paramdict)
07070100000014000081A400000000000000000000000166EC671500002051000000000000000000000000000000000000004800000000python-bugzilla-3.2.0+git.1726768917.5eedea3/bugzilla/_backendxmlrpc.py# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
from logging import getLogger
import sys
from xmlrpc.client import (Binary, Fault, ProtocolError,
ServerProxy, Transport)
from requests import RequestException
from ._backendbase import _BackendBase
from .exceptions import BugzillaError
from ._util import listify
log = getLogger(__name__)
class _BugzillaXMLRPCTransport(Transport):
def __init__(self, bugzillasession):
if hasattr(Transport, "__init__"):
Transport.__init__(self, use_datetime=False)
self.__bugzillasession = bugzillasession
self.__bugzillasession.set_xmlrpc_defaults()
self.__seen_valid_xml = False
# Override Transport.user_agent
self.user_agent = self.__bugzillasession.get_user_agent()
############################
# Bugzilla private helpers #
############################
def __request_helper(self, url, request_body):
"""
A helper method to assist in making a request and parsing the response.
"""
response = None
# pylint: disable=try-except-raise
# pylint: disable=raise-missing-from
try:
response = self.__bugzillasession.request(
"POST", url, data=request_body)
return self.parse_response(response)
except RequestException as e:
if not response:
raise
raise ProtocolError( # pragma: no cover
url, response.status_code, str(e), response.headers)
except Fault:
raise
except Exception:
msg = str(sys.exc_info()[1])
if not self.__seen_valid_xml:
msg += "\nThe URL may not be an XMLRPC URL: %s" % url
e = BugzillaError(msg)
# pylint: disable=attribute-defined-outside-init
e.__traceback__ = sys.exc_info()[2]
# pylint: enable=attribute-defined-outside-init
raise e
######################
# Tranport overrides #
######################
def parse_response(self, response):
"""
Override Transport.parse_response
"""
parser, unmarshaller = self.getparser()
msg = response.text.encode('utf-8')
try:
parser.feed(msg)
except Exception: # pragma: no cover
log.debug("Failed to parse this XMLRPC response:\n%s", msg)
raise
self.__seen_valid_xml = True
parser.close()
return unmarshaller.close()
def request(self, host, handler, request_body, verbose=0):
"""
Override Transport.request
"""
# Setting self.verbose here matches overrided request() behavior
# pylint: disable=attribute-defined-outside-init
self.verbose = verbose
url = "%s://%s%s" % (self.__bugzillasession.get_scheme(),
host, handler)
# xmlrpclib fails to escape \r
request_body = request_body.replace(b'\r', b'
')
return self.__request_helper(url, request_body)
class _BugzillaXMLRPCProxy(ServerProxy, object):
"""
Override of xmlrpc ServerProxy, to insert bugzilla API auth
into the XMLRPC request data
"""
def __init__(self, uri, bugzillasession, *args, **kwargs):
self.__bugzillasession = bugzillasession
transport = _BugzillaXMLRPCTransport(self.__bugzillasession)
ServerProxy.__init__(self, uri, transport, *args, **kwargs)
def _ServerProxy__request(self, methodname, params):
"""
Overrides ServerProxy _request method
"""
# params is a singleton tuple, enforced by xmlrpc.client.dumps
newparams = params and params[0].copy() or {}
log.debug("XMLRPC call: %s(%s)", methodname, newparams)
authparams = self.__bugzillasession.get_auth_params()
authparams.update(newparams)
# pylint: disable=no-member
ret = ServerProxy._ServerProxy__request(
self, methodname, (authparams,))
# pylint: enable=no-member
return ret
class _BackendXMLRPC(_BackendBase):
"""
Internal interface for direct calls to bugzilla's XMLRPC API
"""
def __init__(self, url, bugzillasession):
_BackendBase.__init__(self, url, bugzillasession)
self._xmlrpc_proxy = _BugzillaXMLRPCProxy(url, self._bugzillasession)
def get_xmlrpc_proxy(self):
return self._xmlrpc_proxy
def is_xmlrpc(self):
return True
def bugzilla_version(self):
return self._xmlrpc_proxy.Bugzilla.version()
def bug_attachment_get(self, attachment_ids, paramdict):
data = paramdict.copy()
data["attachment_ids"] = listify(attachment_ids)
return self._xmlrpc_proxy.Bug.attachments(data)
def bug_attachment_get_all(self, bug_ids, paramdict):
data = paramdict.copy()
data["ids"] = listify(bug_ids)
return self._xmlrpc_proxy.Bug.attachments(data)
def bug_attachment_create(self, bug_ids, data, paramdict):
pdata = paramdict.copy()
pdata["ids"] = listify(bug_ids)
if data is not None and "data" not in paramdict:
pdata["data"] = Binary(data)
return self._xmlrpc_proxy.Bug.add_attachment(pdata)
def bug_attachment_update(self, attachment_ids, paramdict):
data = paramdict.copy()
data["ids"] = listify(attachment_ids)
return self._xmlrpc_proxy.Bug.update_attachment(data)
def bug_comments(self, bug_ids, paramdict):
data = paramdict.copy()
data["ids"] = listify(bug_ids)
return self._xmlrpc_proxy.Bug.comments(data)
def bug_create(self, paramdict):
return self._xmlrpc_proxy.Bug.create(paramdict)
def bug_fields(self, paramdict):
return self._xmlrpc_proxy.Bug.fields(paramdict)
def bug_get(self, bug_ids, aliases, paramdict):
data = paramdict.copy()
data["ids"] = listify(bug_ids) or []
data["ids"] += listify(aliases) or []
return self._xmlrpc_proxy.Bug.get(data)
def bug_history(self, bug_ids, paramdict):
data = paramdict.copy()
data["ids"] = listify(bug_ids)
return self._xmlrpc_proxy.Bug.history(data)
def bug_search(self, paramdict):
return self._xmlrpc_proxy.Bug.search(paramdict)
def bug_update(self, bug_ids, paramdict):
data = paramdict.copy()
data["ids"] = listify(bug_ids)
return self._xmlrpc_proxy.Bug.update(data)
def bug_update_tags(self, bug_ids, paramdict):
data = paramdict.copy()
data["ids"] = listify(bug_ids)
return self._xmlrpc_proxy.Bug.update_tags(data)
def component_create(self, paramdict):
return self._xmlrpc_proxy.Component.create(paramdict)
def component_update(self, paramdict):
return self._xmlrpc_proxy.Component.update(paramdict)
def externalbugs_add(self, paramdict):
return self._xmlrpc_proxy.ExternalBugs.add_external_bug(paramdict)
def externalbugs_update(self, paramdict):
return self._xmlrpc_proxy.ExternalBugs.update_external_bug(paramdict)
def externalbugs_remove(self, paramdict):
return self._xmlrpc_proxy.ExternalBugs.remove_external_bug(paramdict)
def group_get(self, paramdict):
return self._xmlrpc_proxy.Group.get(paramdict)
def product_get(self, paramdict):
return self._xmlrpc_proxy.Product.get(paramdict)
def product_get_accessible(self):
return self._xmlrpc_proxy.Product.get_accessible_products()
def product_get_enterable(self):
return self._xmlrpc_proxy.Product.get_enterable_products()
def product_get_selectable(self):
return self._xmlrpc_proxy.Product.get_selectable_products()
def user_create(self, paramdict):
return self._xmlrpc_proxy.User.create(paramdict)
def user_get(self, paramdict):
return self._xmlrpc_proxy.User.get(paramdict)
def user_login(self, paramdict):
return self._xmlrpc_proxy.User.login(paramdict)
def user_logout(self):
return self._xmlrpc_proxy.User.logout()
def user_update(self, paramdict):
return self._xmlrpc_proxy.User.update(paramdict)
07070100000015000081ED00000000000000000000000166EC67150000BA75000000000000000000000000000000000000003E00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/bugzilla/_cli.py#!/usr/bin/env python3
#
# bugzilla - a commandline frontend for the python bugzilla module
#
# Copyright (C) 2007-2017 Red Hat Inc.
# Author: Will Woods <wwoods@redhat.com>
# Author: Cole Robinson <crobinso@redhat.com>
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import argparse
import base64
import datetime
import errno
import json
import locale
from logging import getLogger, DEBUG, INFO, WARN, StreamHandler, Formatter
import os
import re
import socket
import sys
import tempfile
import urllib.parse
import xmlrpc.client
import requests.exceptions
import bugzilla
DEFAULT_BZ = 'https://bugzilla.redhat.com'
format_field_re = re.compile("%{([a-z0-9_]+)(?::([^}]*))?}")
log = getLogger(bugzilla.__name__)
################
# Util helpers #
################
def _is_unittest_debug():
return bool(os.getenv("__BUGZILLA_UNITTEST_DEBUG"))
def open_without_clobber(name, *args):
"""
Try to open the given file with the given mode; if that filename exists,
try "name.1", "name.2", etc. until we find an unused filename.
"""
fd = None
count = 1
orig_name = name
while fd is None:
try:
fd = os.open(name, os.O_CREAT | os.O_EXCL, 0o666)
except OSError as err:
if err.errno == errno.EEXIST:
name = "%s.%i" % (orig_name, count)
count += 1
else: # pragma: no cover
raise IOError(err.errno, err.strerror, err.filename) from None
fobj = open(name, *args)
if fd != fobj.fileno():
os.close(fd)
return fobj
def setup_logging(debug, verbose):
handler = StreamHandler(sys.stderr)
handler.setFormatter(Formatter(
"[%(asctime)s] %(levelname)s (%(module)s:%(lineno)d) %(message)s",
"%H:%M:%S"))
log.addHandler(handler)
if debug:
log.setLevel(DEBUG)
elif verbose:
log.setLevel(INFO)
else:
log.setLevel(WARN)
if _is_unittest_debug():
log.setLevel(DEBUG) # pragma: no cover
##################
# Option parsing #
##################
def _setup_root_parser():
epilog = 'Try "bugzilla COMMAND --help" for command-specific help.'
p = argparse.ArgumentParser(epilog=epilog)
default_url = bugzilla.Bugzilla.get_rcfile_default_url()
if not default_url:
default_url = DEFAULT_BZ
# General bugzilla connection options
p.add_argument('--bugzilla', default=default_url,
help="bugzilla URI. default: %s" % default_url)
p.add_argument("--nosslverify", dest="sslverify",
action="store_false", default=True,
help="Don't error on invalid bugzilla SSL certificate")
p.add_argument('--cert',
help="client side certificate file needed by the webserver")
p.add_argument('--login', action="store_true",
help='Run interactive "login" before performing the '
'specified command.')
p.add_argument('--username', help="Log in with this username")
p.add_argument('--password', help="Log in with this password")
p.add_argument('--restrict-login', action="store_true",
help="The session (login token) will be restricted to "
"the current IP address.")
p.add_argument('--ensure-logged-in', action="store_true",
help="Raise an error if we aren't logged in to bugzilla. "
"Consider using this if you are depending on "
"cached credentials, to ensure that when they expire the "
"tool errors, rather than subtly change output.")
p.add_argument('--no-cache-credentials',
action='store_false', default=True, dest='cache_credentials',
help="Don't save any bugzilla cookies or tokens to disk, and "
"don't use any pre-existing credentials.")
p.add_argument('--cookiefile', default=None, help=argparse.SUPPRESS)
p.add_argument('--tokenfile', default=None,
help="token file to use for bugzilla authentication")
p.add_argument('--verbose', action='store_true',
help="give more info about what's going on")
p.add_argument('--debug', action='store_true',
help="output bunches of debugging info")
p.add_argument('--version', action='version',
version=bugzilla.__version__)
# Allow user to specify BZClass to initialize. Kinda weird for the
# CLI, I'd rather people file bugs about this so we can fix our detection.
# So hide it from the help output but keep it for back compat
p.add_argument('--bztype', default='auto', help=argparse.SUPPRESS)
return p
def _parser_add_output_options(p):
outg = p.add_argument_group("Output format options")
outg.add_argument('--full', action='store_const', dest='output',
const='full', default='normal',
help="output detailed bug info")
outg.add_argument('-i', '--ids', action='store_const', dest='output',
const='ids', help="output only bug IDs")
outg.add_argument('-e', '--extra', action='store_const',
dest='output', const='extra',
help="output additional bug information "
"(keywords, Whiteboards, etc.)")
outg.add_argument('--oneline', action='store_const', dest='output',
const='oneline',
help="one line summary of the bug (useful for scripts)")
outg.add_argument('--json', action='store_const', dest='output',
const='json', help="output contents in json format")
outg.add_argument("--includefield", action="append",
help="Pass the field name to bugzilla include_fields list. "
"Only the fields passed to include_fields are returned "
"by the bugzilla server. "
"This can be specified multiple times.")
outg.add_argument("--extrafield", action="append",
help="Pass the field name to bugzilla extra_fields list. "
"When used with --json this can be used to request "
"bugzilla to return values for non-default fields. "
"This can be specified multiple times.")
outg.add_argument("--excludefield", action="append",
help="Pass the field name to bugzilla exclude_fields list. "
"When used with --json this can be used to request "
"bugzilla to not return values for a field. "
"This can be specified multiple times.")
outg.add_argument('--raw', action='store_const', dest='output',
const='raw', help="raw output of the bugzilla contents. This "
"format is unstable and difficult to parse. Use --json instead.")
outg.add_argument('--outputformat',
help="Print output in the form given. "
"You can use RPM-style tags that match bug "
"fields, e.g.: '%%{id}: %%{summary}'. See the man page "
"section 'Output options' for more details.")
def _parser_add_field_passthrough_opts(p):
p.add_argument('--field',
metavar="FIELD=VALUE", action="append", dest="fields",
help="Manually specify a bugzilla API field. FIELD is "
"the raw name used by the bugzilla instance. For example, if your "
"bugzilla instance has a custom field cf_my_field, do:\n"
" --field cf_my_field=VALUE")
p.add_argument('--field-json',
metavar="JSONSTRING", action="append", dest="field_jsons",
help="Specify --field data as a JSON string. Example: --field-json "
'\'{"cf_my_field": "VALUE", "cf_array_field": [1, 2]}\'')
def _parser_add_bz_fields(rootp, command):
cmd_new = (command == "new")
cmd_query = (command == "query")
cmd_modify = (command == "modify")
if cmd_new:
comment_help = "Set initial bug comment/description"
elif cmd_query:
comment_help = "Search all bug comments"
else:
comment_help = "Add new bug comment"
p = rootp.add_argument_group("Standard bugzilla options")
p.add_argument('-p', '--product', help="Product name")
p.add_argument('-v', '--version', help="Product version")
p.add_argument('-c', '--component', help="Component name")
p.add_argument('-t', '--summary', '--short_desc', help="Bug summary")
p.add_argument('-l', '--comment', '--long_desc', help=comment_help)
if not cmd_query:
p.add_argument("--comment-tag", action="append",
help="Comment tag for the new comment")
p.add_argument("--sub-component", action="append",
help="RHBZ sub component field")
p.add_argument('-o', '--os', help="Operating system")
p.add_argument('--arch', help="Arch this bug occurs on")
p.add_argument('-x', '--severity', help="Bug severity")
p.add_argument('-z', '--priority', help="Bug priority")
p.add_argument('--alias', help='Bug alias (name)')
p.add_argument('-s', '--status', '--bug_status',
help='Bug status (NEW, ASSIGNED, etc.)')
p.add_argument('-u', '--url', help="URL field")
p.add_argument('-m', '--target_milestone', help="Target milestone")
p.add_argument('--target_release', help="RHBZ Target release")
p.add_argument('--blocked', action="append",
help="Bug IDs that this bug blocks")
p.add_argument('--dependson', action="append",
help="Bug IDs that this bug depends on")
p.add_argument('--keywords', action="append",
help="Bug keywords")
p.add_argument('--groups', action="append",
help="Which user groups can view this bug")
p.add_argument('--cc', action="append", help="CC list")
p.add_argument('-a', '--assigned_to', '--assignee', help="Bug assignee")
p.add_argument('-q', '--qa_contact', help='QA contact')
if cmd_modify:
p.add_argument("--minor-update", action="store_true",
help="Request bugzilla to not send any "
"email about this change")
if not cmd_new:
p.add_argument('-f', '--flag', action='append',
help="Bug flags state. Ex:\n"
" --flag needinfo?\n"
" --flag dev_ack+ \n"
" clear with --flag needinfoX")
p.add_argument("--tags", action="append",
help="Tags/Personal Tags field.")
p.add_argument('-w', "--whiteboard", '--status_whiteboard',
action="append", help='Whiteboard field')
p.add_argument("--devel_whiteboard", action="append",
help='RHBZ devel whiteboard field')
p.add_argument("--internal_whiteboard", action="append",
help='RHBZ internal whiteboard field')
p.add_argument("--qa_whiteboard", action="append",
help='RHBZ QA whiteboard field')
p.add_argument('-F', '--fixed_in',
help="RHBZ 'Fixed in version' field")
_parser_add_field_passthrough_opts(p)
if not cmd_modify:
_parser_add_output_options(rootp)
def _setup_action_new_parser(subparsers):
description = ("Create a new bug report. "
"--product, --component, --version, --summary, and --comment "
"must be specified. "
"Options that take multiple values accept comma separated lists, "
"including --cc, --blocks, --dependson, --groups, and --keywords.")
p = subparsers.add_parser("new", description=description)
_parser_add_bz_fields(p, "new")
g = p.add_argument_group("'new' specific options")
g.add_argument('--private', action='store_true', default=False,
help='Mark new comment as private')
def _setup_action_query_parser(subparsers):
description = ("List bug reports that match the given criteria. "
"Certain options can accept a comma separated list to query multiple "
"values, including --status, --component, --product, --version, --id.")
epilog = ("Note: querying via explicit command line options will only "
"get you so far. See the --from-url option for a way to use powerful "
"Web UI queries from the command line.")
p = subparsers.add_parser("query",
description=description, epilog=epilog)
_parser_add_bz_fields(p, "query")
g = p.add_argument_group("'query' specific options")
g.add_argument('-b', '--id', '--bug_id',
help="specify individual bugs by IDs, separated with commas")
g.add_argument('-r', '--reporter',
help="Email: search reporter email for given address")
g.add_argument('--quicksearch',
help="Search using bugzilla's quicksearch functionality.")
g.add_argument('--savedsearch',
help="Name of a bugzilla saved search. If you don't own this "
"saved search, you must passed --savedsearch_sharer_id.")
g.add_argument('--savedsearch-sharer-id',
help="Owner ID of the --savedsearch. You can get this ID from "
"the URL bugzilla generates when running the saved search "
"from the web UI.")
# Keep this at the end so it sticks out more
g.add_argument('--from-url', metavar="WEB_QUERY_URL",
help="Make a working query via bugzilla's 'Advanced search' web UI, "
"grab the url from your browser (the string with query.cgi or "
"buglist.cgi in it), and --from-url will run it via the "
"bugzilla API. Don't forget to quote the string! "
"This only works for Bugzilla 5 and Red Hat bugzilla")
# Deprecated options
p.add_argument('-E', '--emailtype', help=argparse.SUPPRESS)
p.add_argument('--components_file', help=argparse.SUPPRESS)
p.add_argument('-U', '--url_type',
help=argparse.SUPPRESS)
p.add_argument('-K', '--keywords_type',
help=argparse.SUPPRESS)
p.add_argument('-W', '--status_whiteboard_type',
help=argparse.SUPPRESS)
p.add_argument('--fixed_in_type', help=argparse.SUPPRESS)
def _setup_action_info_parser(subparsers):
description = ("List products or component information about the "
"bugzilla server.")
p = subparsers.add_parser("info", description=description)
x = p.add_mutually_exclusive_group(required=True)
x.add_argument('-p', '--products', action='store_true',
help='Get a list of products')
x.add_argument('-c', '--components', metavar="PRODUCT",
help='List the components in the given product')
x.add_argument('-o', '--component_owners', metavar="PRODUCT",
help='List components (and their owners)')
x.add_argument('-v', '--versions', metavar="PRODUCT",
help='List the versions for the given product')
p.add_argument('--active-components', action="store_true",
help='Only show active components. Combine with --components*')
def _setup_action_modify_parser(subparsers):
usage = ("bugzilla modify [options] BUGID [BUGID...]\n"
"Fields that take multiple values have a special input format.\n"
"Append: --cc=foo@example.com\n"
"Overwrite: --cc==foo@example.com\n"
"Remove: --cc=-foo@example.com\n"
"Options that accept this format: --cc, --blocked, --dependson,\n"
" --groups, --tags, whiteboard fields.")
p = subparsers.add_parser("modify", usage=usage)
_parser_add_bz_fields(p, "modify")
g = p.add_argument_group("'modify' specific options")
g.add_argument("ids", nargs="+", help="Bug IDs to modify")
g.add_argument('-k', '--close', metavar="RESOLUTION",
help='Close with the given resolution (WONTFIX, NOTABUG, etc.)')
g.add_argument('-d', '--dupeid', metavar="ORIGINAL",
help='ID of original bug. Implies --close DUPLICATE')
g.add_argument('--private', action='store_true', default=False,
help='Mark new comment as private')
g.add_argument('--reset-assignee', action="store_true",
help='Reset assignee to component default')
g.add_argument('--reset-qa-contact', action="store_true",
help='Reset QA contact to component default')
def _setup_action_attach_parser(subparsers):
usage = """
bugzilla attach --file=FILE --desc=DESC [--type=TYPE] BUGID [BUGID...]
bugzilla attach --get=ATTACHID --getall=BUGID [--ignore-obsolete] [...]
bugzilla attach --type=TYPE BUGID [BUGID...]"""
description = "Attach files or download attachments."
p = subparsers.add_parser("attach", description=description, usage=usage)
p.add_argument("ids", nargs="*", help="BUGID references")
p.add_argument('-f', '--file', metavar="FILENAME",
help='File to attach, or filename for data provided on stdin')
p.add_argument('-d', '--description', '--summary',
metavar="SUMMARY", dest='desc',
help="A short summary of the file being attached")
p.add_argument('-t', '--type', metavar="MIMETYPE",
help="Mime-type for the file being attached")
p.add_argument('-g', '--get', metavar="ATTACHID", action="append",
default=[], help="Download the attachment with the given ID")
p.add_argument("--getall", "--get-all", metavar="BUGID", action="append",
default=[], help="Download all attachments on the given bug")
p.add_argument('--ignore-obsolete', action="store_true",
help='Do not download attachments marked as obsolete.')
p.add_argument('-l', '--comment', '--long_desc',
help="Add comment with attachment")
p.add_argument('--private', action='store_true', default=False,
help='Mark new comment as private')
_parser_add_field_passthrough_opts(p)
def _setup_action_login_parser(subparsers):
usage = 'bugzilla login [--api-key] [username [password]]'
description = """Log into bugzilla and save a login cookie or token.
Note: These tokens are short-lived, and future Bugzilla versions will no
longer support token authentication at all. Please use a
~/.config/python-bugzilla/bugzillarc file with an API key instead, or
use 'bugzilla login --api-key' and we will save it for you."""
p = subparsers.add_parser("login", description=description, usage=usage)
p.add_argument('--api-key', action='store_true', default=False,
help='Prompt for and save an API key into bugzillarc, '
'rather than prompt for username and password.')
p.add_argument("pos_username", nargs="?", help="Optional username ",
metavar="username")
p.add_argument("pos_password", nargs="?", help="Optional password ",
metavar="password")
def setup_parser():
rootparser = _setup_root_parser()
subparsers = rootparser.add_subparsers(dest="command")
subparsers.required = True
_setup_action_new_parser(subparsers)
_setup_action_query_parser(subparsers)
_setup_action_info_parser(subparsers)
_setup_action_modify_parser(subparsers)
_setup_action_attach_parser(subparsers)
_setup_action_login_parser(subparsers)
return rootparser
####################
# Command routines #
####################
def _merge_field_opts(query, fields, field_jsons, parser):
values = {}
# Add any custom fields if specified
for f in (fields or []):
try:
f, v = f.split('=', 1)
values[f] = v
except Exception:
parser.error("Invalid field argument provided: %s" % (f))
for j in (field_jsons or []):
try:
jvalues = json.loads(j)
values.update(jvalues)
except Exception as e:
parser.error("Invalid field-json value=%s: %s" % (j, e))
if values:
log.debug("parsed --field* values: %s", values)
query.update(values)
def _do_query(bz, opt, parser):
q = {}
# Parse preconstructed queries.
u = opt.from_url
if u:
q = bz.url_to_query(u)
if opt.components_file:
# Components slurped in from file (one component per line)
# This can be made more robust
clist = []
f = open(opt.components_file, 'r')
for line in f.readlines():
line = line.rstrip("\n")
clist.append(line)
opt.component = clist
if opt.status:
val = opt.status
stat = val
if val == 'ALL':
# leaving this out should return bugs of any status
stat = None
elif val == 'DEV':
# Alias for all development bug statuses
stat = ['NEW', 'ASSIGNED', 'NEEDINFO', 'ON_DEV',
'MODIFIED', 'POST', 'REOPENED']
elif val == 'QE':
# Alias for all QE relevant bug statuses
stat = ['ASSIGNED', 'ON_QA', 'FAILS_QA', 'PASSES_QA']
elif val == 'EOL':
# Alias for EndOfLife bug statuses
stat = ['VERIFIED', 'RELEASE_PENDING', 'CLOSED']
elif val == 'OPEN':
# non-Closed statuses
stat = ['NEW', 'ASSIGNED', 'MODIFIED', 'ON_DEV', 'ON_QA',
'VERIFIED', 'RELEASE_PENDING', 'POST']
opt.status = stat
# Convert all comma separated list parameters to actual lists,
# which is what bugzilla wants
# According to bugzilla docs, any parameter can be a list, but
# let's only do this for options we explicitly mention can be
# comma separated.
for optname in ["severity", "id", "status", "component",
"priority", "product", "version"]:
val = getattr(opt, optname, None)
if not isinstance(val, str):
continue
setattr(opt, optname, val.split(","))
include_fields = None
if opt.output in ['raw', 'json']:
# 'raw' always does a getbug() call anyways, so just ask for ID back
include_fields = ['id']
elif opt.outputformat:
include_fields = []
for fieldname, rest in format_field_re.findall(opt.outputformat):
if fieldname == "whiteboard" and rest:
fieldname = rest + "_" + fieldname
elif fieldname == "flag":
fieldname = "flags"
elif fieldname == "cve":
fieldname = ["keywords", "blocks"]
elif fieldname == "__unicode__":
# Needs to be in sync with bug.__unicode__
fieldname = ["id", "status", "assigned_to", "summary"]
flist = isinstance(fieldname, list) and fieldname or [fieldname]
for f in flist:
if f not in include_fields:
include_fields.append(f)
if include_fields is not None:
include_fields.sort()
kwopts = {}
if opt.product:
kwopts["product"] = opt.product
if opt.component:
kwopts["component"] = opt.component
if opt.sub_component:
kwopts["sub_component"] = opt.sub_component
if opt.version:
kwopts["version"] = opt.version
if opt.reporter:
kwopts["reporter"] = opt.reporter
if opt.id:
kwopts["bug_id"] = opt.id
if opt.summary:
kwopts["short_desc"] = opt.summary
if opt.comment:
kwopts["long_desc"] = opt.comment
if opt.cc:
kwopts["cc"] = opt.cc
if opt.assigned_to:
kwopts["assigned_to"] = opt.assigned_to
if opt.qa_contact:
kwopts["qa_contact"] = opt.qa_contact
if opt.status:
kwopts["status"] = opt.status
if opt.blocked:
kwopts["blocked"] = opt.blocked
if opt.dependson:
kwopts["dependson"] = opt.dependson
if opt.keywords:
kwopts["keywords"] = opt.keywords
if opt.keywords_type:
kwopts["keywords_type"] = opt.keywords_type
if opt.url:
kwopts["url"] = opt.url
if opt.url_type:
kwopts["url_type"] = opt.url_type
if opt.whiteboard:
kwopts["status_whiteboard"] = opt.whiteboard
if opt.status_whiteboard_type:
kwopts["status_whiteboard_type"] = opt.status_whiteboard_type
if opt.fixed_in:
kwopts["fixed_in"] = opt.fixed_in
if opt.fixed_in_type:
kwopts["fixed_in_type"] = opt.fixed_in_type
if opt.flag:
kwopts["flag"] = opt.flag
if opt.alias:
kwopts["alias"] = opt.alias
if opt.qa_whiteboard:
kwopts["qa_whiteboard"] = opt.qa_whiteboard
if opt.devel_whiteboard:
kwopts["devel_whiteboard"] = opt.devel_whiteboard
if opt.severity:
kwopts["bug_severity"] = opt.severity
if opt.priority:
kwopts["priority"] = opt.priority
if opt.target_release:
kwopts["target_release"] = opt.target_release
if opt.target_milestone:
kwopts["target_milestone"] = opt.target_milestone
if opt.emailtype:
kwopts["emailtype"] = opt.emailtype
if include_fields:
kwopts["include_fields"] = include_fields
if opt.quicksearch:
kwopts["quicksearch"] = opt.quicksearch
if opt.savedsearch:
kwopts["savedsearch"] = opt.savedsearch
if opt.savedsearch_sharer_id:
kwopts["savedsearch_sharer_id"] = opt.savedsearch_sharer_id
if opt.tags:
kwopts["tags"] = opt.tags
built_query = bz.build_query(**kwopts)
_merge_field_opts(built_query, opt.fields, opt.field_jsons, parser)
built_query.update(q)
q = built_query
if not q: # pragma: no cover
parser.error("'query' command requires additional arguments")
return bz.query(q)
def _do_info(bz, opt):
"""
Handle the 'info' subcommand
"""
# All these commands call getproducts internally, so do it up front
# with minimal include_fields for speed
def _filter_components(compdetails):
ret = {}
for k, v in compdetails.items():
if v.get("is_active", True):
ret[k] = v
return ret
productname = (opt.components or opt.component_owners or opt.versions)
fastcomponents = (opt.components and not opt.active_components)
include_fields = ["name", "id"]
if opt.components or opt.component_owners:
include_fields += ["components.name"]
if opt.component_owners:
include_fields += ["components.default_assigned_to"]
if opt.active_components:
include_fields += ["components.is_active"]
if opt.versions:
include_fields += ["versions"]
bz.refresh_products(names=productname and [productname] or None,
include_fields=include_fields)
if opt.products:
for name in sorted([p["name"] for p in bz.getproducts()]):
print(name)
elif fastcomponents:
for name in sorted(bz.getcomponents(productname)):
print(name)
elif opt.components:
details = bz.getcomponentsdetails(productname)
for name in sorted(_filter_components(details)):
print(name)
elif opt.versions:
proddict = bz.getproducts()[0]
for v in proddict['versions']:
print(str(v["name"] or ''))
elif opt.component_owners:
details = bz.getcomponentsdetails(productname)
for c in sorted(_filter_components(details)):
print("%s: %s" % (c, details[c]['default_assigned_to']))
def _convert_to_outputformat(output):
fmt = ""
if output == "normal":
fmt = "%{__unicode__}"
elif output == "ids":
fmt = "%{id}"
elif output == 'full':
fmt += "%{__unicode__}\n"
fmt += "Component: %{component}\n"
fmt += "CC: %{cc}\n"
fmt += "Blocked: %{blocks}\n"
fmt += "Depends: %{depends_on}\n"
fmt += "%{comments}\n"
elif output == 'extra':
fmt += "%{__unicode__}\n"
fmt += " +Keywords: %{keywords}\n"
fmt += " +QA Whiteboard: %{qa_whiteboard}\n"
fmt += " +Status Whiteboard: %{status_whiteboard}\n"
fmt += " +Devel Whiteboard: %{devel_whiteboard}\n"
elif output == 'oneline':
fmt += "#%{bug_id} %{status} %{assigned_to} %{component}\t"
fmt += "[%{target_milestone}] %{flags} %{cve}"
else: # pragma: no cover
raise RuntimeError("Unknown output type '%s'" % output)
return fmt
def _xmlrpc_converter(obj):
if "DateTime" in str(obj.__class__):
# xmlrpc DateTime object. Convert to date format that
# bugzilla REST API outputs
dobj = datetime.datetime.strptime(str(obj), '%Y%m%dT%H:%M:%S')
return dobj.isoformat() + "Z"
if "Binary" in str(obj.__class__):
# xmlrpc Binary object. Convert to base64
return base64.b64encode(obj.data).decode("utf-8")
raise RuntimeError(
"Unexpected JSON conversion class=%s" % obj.__class__)
def _format_output_json(buglist):
out = {"bugs": [b.get_raw_data() for b in buglist]}
s = json.dumps(out, default=_xmlrpc_converter, indent=2, sort_keys=True)
print(s)
def _format_output_raw(buglist):
for b in buglist:
print("Bugzilla %s: " % b.bug_id)
SKIP_NAMES = ["bugzilla"]
for attrname in sorted(b.__dict__):
if attrname in SKIP_NAMES:
continue
if attrname.startswith("_"):
continue
print("ATTRIBUTE[%s]: %s" % (attrname, b.__dict__[attrname]))
print("\n\n")
def _bug_field_repl_cb(bz, b, matchobj):
# whiteboard and flag allow doing
# %{whiteboard:devel} and %{flag:needinfo}
# That's what 'rest' matches
(fieldname, rest) = matchobj.groups()
if fieldname == "whiteboard" and rest:
fieldname = rest + "_" + fieldname
if fieldname == "flag" and rest:
val = b.get_flag_status(rest)
elif fieldname in ["flags", "flags_requestee"]:
tmpstr = []
for f in getattr(b, "flags", []):
requestee = f.get('requestee', "")
if fieldname == "flags":
requestee = ""
if fieldname == "flags_requestee":
if requestee == "":
continue
tmpstr.append("%s" % requestee)
else:
tmpstr.append("%s%s%s" %
(f['name'], f['status'], requestee))
val = ",".join(tmpstr)
elif fieldname == "cve":
cves = []
for key in getattr(b, "keywords", []):
# grab CVE from keywords and blockers
if key.find("Security") == -1:
continue
for bl in b.blocks:
cvebug = bz.getbug(bl)
for cb in cvebug.alias:
if (cb.find("CVE") != -1 and
cb.strip() not in cves):
cves.append(cb)
val = ",".join(cves)
elif fieldname == "comments":
val = ""
for c in getattr(b, "comments", []):
val += ("\n* %s - %s:\n%s\n" % (c['time'],
c.get("creator", c.get("author", "")), c['text']))
elif fieldname == "external_bugs":
val = ""
for e in getattr(b, "external_bugs", []):
url = e["type"]["full_url"].replace("%id%", e["ext_bz_bug_id"])
if not val:
val += "\n"
val += "External bug: %s\n" % url
elif fieldname == "__unicode__":
val = b.__unicode__()
else:
val = getattr(b, fieldname, "")
vallist = isinstance(val, list) and val or [val]
val = ','.join([str(v or '') for v in vallist])
return val
def _format_output(bz, opt, buglist):
if opt.output in ['raw', 'json']:
include_fields = None
exclude_fields = None
extra_fields = None
if opt.includefield:
include_fields = opt.includefield
if opt.excludefield:
exclude_fields = opt.excludefield
if opt.extrafield:
extra_fields = opt.extrafield
buglist = bz.getbugs([b.bug_id for b in buglist],
include_fields=include_fields,
exclude_fields=exclude_fields,
extra_fields=extra_fields)
if opt.output == 'json':
_format_output_json(buglist)
if opt.output == 'raw':
_format_output_raw(buglist)
return
for b in buglist:
# pylint: disable=cell-var-from-loop
def cb(matchobj):
return _bug_field_repl_cb(bz, b, matchobj)
print(format_field_re.sub(cb, opt.outputformat))
def _parse_triset(vallist, checkplus=True, checkminus=True, checkequal=True,
splitcomma=False):
add_val = []
rm_val = []
set_val = None
def make_list(v):
if not v:
return []
if splitcomma:
return v.split(",")
return [v]
for val in isinstance(vallist, list) and vallist or [vallist]:
val = val or ""
if val.startswith("+") and checkplus:
add_val += make_list(val[1:])
elif val.startswith("-") and checkminus:
rm_val += make_list(val[1:])
elif val.startswith("=") and checkequal:
# Intentionally overwrite this
set_val = make_list(val[1:])
else:
add_val += make_list(val)
return add_val, rm_val, set_val
def _do_new(bz, opt, parser):
# Parse options that accept comma separated list
def parse_multi(val):
return _parse_triset(val, checkplus=False, checkminus=False,
checkequal=False, splitcomma=True)[0]
kwopts = {}
if opt.blocked:
kwopts["blocks"] = parse_multi(opt.blocked)
if opt.cc:
kwopts["cc"] = parse_multi(opt.cc)
if opt.component:
kwopts["component"] = opt.component
if opt.dependson:
kwopts["depends_on"] = parse_multi(opt.dependson)
if opt.comment:
kwopts["description"] = opt.comment
if opt.groups:
kwopts["groups"] = parse_multi(opt.groups)
if opt.keywords:
kwopts["keywords"] = parse_multi(opt.keywords)
if opt.os:
kwopts["op_sys"] = opt.os
if opt.arch:
kwopts["platform"] = opt.arch
if opt.priority:
kwopts["priority"] = opt.priority
if opt.product:
kwopts["product"] = opt.product
if opt.severity:
kwopts["severity"] = opt.severity
if opt.summary:
kwopts["summary"] = opt.summary
if opt.url:
kwopts["url"] = opt.url
if opt.version:
kwopts["version"] = opt.version
if opt.assigned_to:
kwopts["assigned_to"] = opt.assigned_to
if opt.qa_contact:
kwopts["qa_contact"] = opt.qa_contact
if opt.sub_component:
kwopts["sub_component"] = opt.sub_component
if opt.alias:
kwopts["alias"] = opt.alias
if opt.comment_tag:
kwopts["comment_tags"] = opt.comment_tag
if opt.private:
kwopts["comment_private"] = opt.private
ret = bz.build_createbug(**kwopts)
_merge_field_opts(ret, opt.fields, opt.field_jsons, parser)
b = bz.createbug(ret)
b.refresh()
return [b]
def _do_modify(bz, parser, opt):
bugid_list = [bugid for a in opt.ids for bugid in a.split(',')]
add_wb, rm_wb, set_wb = _parse_triset(opt.whiteboard)
add_devwb, rm_devwb, set_devwb = _parse_triset(opt.devel_whiteboard)
add_intwb, rm_intwb, set_intwb = _parse_triset(opt.internal_whiteboard)
add_qawb, rm_qawb, set_qawb = _parse_triset(opt.qa_whiteboard)
add_blk, rm_blk, set_blk = _parse_triset(opt.blocked, splitcomma=True)
add_deps, rm_deps, set_deps = _parse_triset(opt.dependson, splitcomma=True)
add_key, rm_key, set_key = _parse_triset(opt.keywords)
add_cc, rm_cc, ignore = _parse_triset(opt.cc,
checkplus=False,
checkequal=False)
add_groups, rm_groups, ignore = _parse_triset(opt.groups,
checkequal=False,
splitcomma=True)
add_tags, rm_tags, ignore = _parse_triset(opt.tags, checkequal=False)
status = opt.status or None
if opt.dupeid is not None:
opt.close = "DUPLICATE"
if opt.close:
status = "CLOSED"
flags = []
if opt.flag:
# Convert "foo+" to tuple ("foo", "+")
for f in opt.flag:
flags.append({"name": f[:-1], "status": f[-1]})
update_opts = {}
if opt.assigned_to:
update_opts["assigned_to"] = opt.assigned_to
if opt.comment:
update_opts["comment"] = opt.comment
if opt.private:
update_opts["comment_private"] = opt.private
if opt.component:
update_opts["component"] = opt.component
if opt.product:
update_opts["product"] = opt.product
if add_blk:
update_opts["blocks_add"] = add_blk
if rm_blk:
update_opts["blocks_remove"] = rm_blk
if set_blk is not None:
update_opts["blocks_set"] = set_blk
if opt.url:
update_opts["url"] = opt.url
if add_cc:
update_opts["cc_add"] = add_cc
if rm_cc:
update_opts["cc_remove"] = rm_cc
if add_deps:
update_opts["depends_on_add"] = add_deps
if rm_deps:
update_opts["depends_on_remove"] = rm_deps
if set_deps is not None:
update_opts["depends_on_set"] = set_deps
if add_groups:
update_opts["groups_add"] = add_groups
if rm_groups:
update_opts["groups_remove"] = rm_groups
if add_key:
update_opts["keywords_add"] = add_key
if rm_key:
update_opts["keywords_remove"] = rm_key
if set_key is not None:
update_opts["keywords_set"] = set_key
if opt.os:
update_opts["op_sys"] = opt.os
if opt.arch:
update_opts["platform"] = opt.arch
if opt.priority:
update_opts["priority"] = opt.priority
if opt.qa_contact:
update_opts["qa_contact"] = opt.qa_contact
if opt.severity:
update_opts["severity"] = opt.severity
if status:
update_opts["status"] = status
if opt.summary:
update_opts["summary"] = opt.summary
if opt.version:
update_opts["version"] = opt.version
if opt.reset_assignee:
update_opts["reset_assigned_to"] = opt.reset_assignee
if opt.reset_qa_contact:
update_opts["reset_qa_contact"] = opt.reset_qa_contact
if opt.close:
update_opts["resolution"] = opt.close
if opt.target_release:
update_opts["target_release"] = opt.target_release
if opt.target_milestone:
update_opts["target_milestone"] = opt.target_milestone
if opt.dupeid:
update_opts["dupe_of"] = opt.dupeid
if opt.fixed_in:
update_opts["fixed_in"] = opt.fixed_in
if set_wb and set_wb[0]:
update_opts["whiteboard"] = set_wb and set_wb[0]
if set_devwb and set_devwb[0]:
update_opts["devel_whiteboard"] = set_devwb and set_devwb[0]
if set_intwb and set_intwb[0]:
update_opts["internal_whiteboard"] = set_intwb and set_intwb[0]
if set_qawb and set_qawb[0]:
update_opts["qa_whiteboard"] = set_qawb and set_qawb[0]
if opt.sub_component:
update_opts["sub_component"] = opt.sub_component
if opt.alias:
update_opts["alias"] = opt.alias
if flags:
update_opts["flags"] = flags
if opt.comment_tag:
update_opts["comment_tags"] = opt.comment_tag
if opt.minor_update:
update_opts["minor_update"] = opt.minor_update
update = bz.build_update(**update_opts)
# We make this a little convoluted to facilitate unit testing
wbmap = {
"whiteboard": (add_wb, rm_wb),
"internal_whiteboard": (add_intwb, rm_intwb),
"qa_whiteboard": (add_qawb, rm_qawb),
"devel_whiteboard": (add_devwb, rm_devwb),
}
for k, v in wbmap.copy().items():
if not v[0] and not v[1]:
del wbmap[k]
_merge_field_opts(update, opt.fields, opt.field_jsons, parser)
log.debug("update bug dict=%s", update)
log.debug("update whiteboard dict=%s", wbmap)
if not any([update, wbmap, add_tags, rm_tags]):
parser.error("'modify' command requires additional arguments")
if add_tags or rm_tags:
ret = bz.update_tags(bugid_list,
tags_add=add_tags, tags_remove=rm_tags)
log.debug("bz.update_tags returned=%s", ret)
if update:
ret = bz.update_bugs(bugid_list, update)
log.debug("bz.update_bugs returned=%s", ret)
if not wbmap:
return
# Now for the things we can't blindly batch.
# Being able to prepend/append to whiteboards, which are just
# plain string values, is an old rhbz semantic that we try to maintain
# here. This is a bit weird for traditional bugzilla API
log.debug("Adjusting whiteboard fields one by one")
for bug in bz.getbugs(bugid_list):
update_kwargs = {}
for wbkey, (add_list, rm_list) in wbmap.items():
bugval = getattr(bug, wbkey) or ""
for tag in add_list:
if bugval:
bugval += " "
bugval += tag
for tag in rm_list:
bugsplit = bugval.split()
for t in bugsplit[:]:
if t == tag:
bugsplit.remove(t)
bugval = " ".join(bugsplit)
update_kwargs[wbkey] = bugval
bz.update_bugs([bug.id], bz.build_update(**update_kwargs))
def _do_get_attach(bz, opt):
data = {}
def _process_attachment_data(_attlist):
for _att in _attlist:
data[_att["id"]] = _att
if opt.getall:
for attlist in bz.get_attachments(opt.getall, None)["bugs"].values():
_process_attachment_data(attlist)
if opt.get:
_process_attachment_data(
bz.get_attachments(None, opt.get)["attachments"].values())
for attdata in data.values():
is_obsolete = attdata.get("is_obsolete", None) == 1
if opt.ignore_obsolete and is_obsolete:
continue
att = bz.openattachment_data(attdata)
outfile = open_without_clobber(att.name, "wb")
data = att.read(4096)
while data:
outfile.write(data)
data = att.read(4096)
print("Wrote %s" % outfile.name)
def _do_set_attach(bz, opt, parser):
if not opt.ids:
parser.error("Bug ID must be specified for setting attachments")
if sys.stdin.isatty():
if not opt.file:
parser.error("--file must be specified")
fileobj = open(opt.file, "rb")
else:
# piped input on stdin
if not opt.desc:
parser.error("--description must be specified if passing "
"file on stdin")
fileobj = tempfile.NamedTemporaryFile(prefix="bugzilla-attach.")
data = sys.stdin.read(4096)
while data:
fileobj.write(data.encode(locale.getpreferredencoding()))
data = sys.stdin.read(4096)
fileobj.seek(0)
kwargs = {}
if opt.file:
kwargs["filename"] = os.path.basename(opt.file)
if opt.type:
kwargs["contenttype"] = opt.type
if opt.type in ["text/x-patch"]:
kwargs["ispatch"] = True
if opt.comment:
kwargs["comment"] = opt.comment
if opt.private:
kwargs["is_private"] = True
desc = opt.desc or os.path.basename(fileobj.name)
_merge_field_opts(kwargs, opt.fields, opt.field_jsons, parser)
# Upload attachments
for bugid in opt.ids:
attid = bz.attachfile(bugid, fileobj, desc, **kwargs)
print("Created attachment %i on bug %s" % (attid, bugid))
#################
# Main handling #
#################
def _make_bz_instance(opt):
"""
Build the Bugzilla instance we will use
"""
if opt.bztype != 'auto':
log.info("Explicit --bztype is no longer supported, ignoring")
cookiefile = None
tokenfile = None
use_creds = False
if opt.cache_credentials:
cookiefile = opt.cookiefile or -1
tokenfile = opt.tokenfile or -1
use_creds = True
return bugzilla.Bugzilla(
url=opt.bugzilla,
cookiefile=cookiefile,
tokenfile=tokenfile,
sslverify=opt.sslverify,
use_creds=use_creds,
cert=opt.cert)
def _handle_login(opt, action, bz):
"""
Handle all login related bits
"""
is_login_command = (action == 'login')
do_interactive_login = (is_login_command or
opt.login or opt.username or opt.password)
username = getattr(opt, "pos_username", None) or opt.username
password = getattr(opt, "pos_password", None) or opt.password
use_key = getattr(opt, "api_key", False)
try:
if use_key:
bz.interactive_save_api_key()
elif do_interactive_login:
if bz.api_key:
print("You already have an API key configured for %s" % bz.url)
print("There is no need to cache a login token. Exiting.")
sys.exit(0)
print("Logging into %s" % urllib.parse.urlparse(bz.url)[1])
bz.interactive_login(username, password,
restrict_login=opt.restrict_login)
except bugzilla.BugzillaError as e:
print(str(e))
sys.exit(1)
if opt.ensure_logged_in and not bz.logged_in:
print("--ensure-logged-in passed but you aren't logged in to %s" %
bz.url)
sys.exit(1)
if is_login_command:
sys.exit(0)
def _main(unittest_bz_instance):
parser = setup_parser()
opt = parser.parse_args()
action = opt.command
setup_logging(opt.debug, opt.verbose)
log.debug("Launched with command line: %s", " ".join(sys.argv))
log.debug("Bugzilla module: %s", bugzilla)
if unittest_bz_instance:
bz = unittest_bz_instance
else:
bz = _make_bz_instance(opt)
# Handle login options
_handle_login(opt, action, bz)
###########################
# Run the actual commands #
###########################
if hasattr(opt, "outputformat"):
if not opt.outputformat and opt.output not in ['raw', 'json', None]:
opt.outputformat = _convert_to_outputformat(opt.output)
buglist = []
if action == 'info':
_do_info(bz, opt)
elif action == 'query':
buglist = _do_query(bz, opt, parser)
elif action == 'new':
buglist = _do_new(bz, opt, parser)
elif action == 'attach':
if opt.get or opt.getall:
if opt.ids:
parser.error("Bug IDs '%s' not used for "
"getting attachments" % opt.ids)
_do_get_attach(bz, opt)
else:
_do_set_attach(bz, opt, parser)
elif action == 'modify':
_do_modify(bz, parser, opt)
else: # pragma: no cover
raise RuntimeError("Unexpected action '%s'" % action)
# If we're doing new/query/modify, output our results
if action in ['new', 'query']:
_format_output(bz, opt, buglist)
def main(unittest_bz_instance=None):
try:
try:
return _main(unittest_bz_instance)
except (Exception, KeyboardInterrupt):
log.debug("", exc_info=True)
raise
except KeyboardInterrupt:
print("\nExited at user request.")
sys.exit(1)
except (xmlrpc.client.Fault, bugzilla.BugzillaError) as e:
print("\nServer error: %s" % str(e))
sys.exit(3)
except requests.exceptions.SSLError as e:
# Give SSL recommendations
print("SSL error: %s" % e)
print("\nIf you trust the remote server, you can work "
"around this error with:\n"
" bugzilla --nosslverify ...")
sys.exit(4)
except (socket.error,
requests.exceptions.HTTPError,
requests.exceptions.ConnectionError,
requests.exceptions.InvalidURL,
xmlrpc.client.ProtocolError) as e:
print("\nConnection lost/failed: %s" % str(e))
sys.exit(2)
def cli():
main()
07070100000016000081A400000000000000000000000166EC671500001103000000000000000000000000000000000000004700000000python-bugzilla-3.2.0+git.1726768917.5eedea3/bugzilla/_rhconverters.py# rhbugzilla.py - a Python interface to Red Hat Bugzilla using xmlrpclib.
#
# Copyright (C) 2008-2012 Red Hat Inc.
# Author: Will Woods <wwoods@redhat.com>
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
from logging import getLogger
from ._util import listify
log = getLogger(__name__)
class _RHBugzillaConverters(object):
"""
Static class that holds functional Red Hat back compat converters.
Called inline in Bugzilla
"""
@staticmethod
def convert_build_update(
component=None,
fixed_in=None,
qa_whiteboard=None,
devel_whiteboard=None,
internal_whiteboard=None,
sub_component=None):
adddict = {}
def get_alias():
# RHBZ has a custom extension to allow a bug to have multiple
# aliases, so the format of aliases is
# {"add": [...], "remove": [...]}
# But that means in order to approximate upstream, behavior
# which just overwrites the existing alias, we need to read
# the bug's state first to know what string to remove. Which
# we can't do, since we don't know the bug numbers at this point.
# So fail for now.
#
# The API should provide {"set": [...]}
# https://bugzilla.redhat.com/show_bug.cgi?id=1173114
#
# Implementation will go here when it's available
pass
if fixed_in is not None:
adddict["cf_fixed_in"] = fixed_in
if qa_whiteboard is not None:
adddict["cf_qa_whiteboard"] = qa_whiteboard
if devel_whiteboard is not None:
adddict["cf_devel_whiteboard"] = devel_whiteboard
if internal_whiteboard is not None:
adddict["cf_internal_whiteboard"] = internal_whiteboard
if sub_component:
if not isinstance(sub_component, dict):
component = listify(component)
if not component:
raise ValueError("component must be specified if "
"specifying sub_component")
sub_component = {component[0]: sub_component}
adddict["sub_components"] = sub_component
get_alias()
return adddict
#################
# Query methods #
#################
@staticmethod
def pre_translation(query):
"""
Translates the query for possible aliases
"""
old = query.copy()
def split_comma(_v):
if isinstance(_v, list):
return _v
return _v.split(",")
if 'bug_id' in query:
query['id'] = split_comma(query.pop('bug_id'))
if 'component' in query:
query['component'] = split_comma(query['component'])
if 'include_fields' not in query and 'column_list' in query:
query['include_fields'] = query.pop('column_list')
if old != query:
log.debug("RHBugzilla pretranslated query to: %s", query)
@staticmethod
def post_translation(query, bug):
"""
Convert the results of getbug back to the ancient RHBZ value
formats
"""
ignore = query
# RHBZ _still_ returns component and version as lists, which
# deviates from upstream. Copy the list values to components
# and versions respectively.
if 'component' in bug and "components" not in bug:
val = bug['component']
bug['components'] = isinstance(val, list) and val or [val]
bug['component'] = bug['components'][0]
if 'version' in bug and "versions" not in bug:
val = bug['version']
bug['versions'] = isinstance(val, list) and val or [val]
bug['version'] = bug['versions'][0]
# sub_components isn't too friendly of a format, add a simpler
# sub_component value
if 'sub_components' in bug and 'sub_component' not in bug:
val = bug['sub_components']
bug['sub_component'] = ""
if isinstance(val, dict):
values = []
for vallist in val.values():
values += vallist
bug['sub_component'] = " ".join(values)
07070100000017000081A400000000000000000000000166EC67150000100F000000000000000000000000000000000000004200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/bugzilla/_session.py# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
from logging import getLogger
import os
import sys
import urllib.parse
import requests
from .exceptions import BugzillaHTTPError
log = getLogger(__name__)
class _BugzillaSession(object):
"""
Class to handle the backend agnostic 'requests' setup
"""
def __init__(self, url, user_agent,
sslverify, cert, tokencache, api_key,
is_redhat_bugzilla,
requests_session=None):
self._url = url
self._user_agent = user_agent
self._scheme = urllib.parse.urlparse(url)[0]
self._tokencache = tokencache
self._api_key = api_key
self._is_xmlrpc = False
self._use_auth_bearer = False
if self._scheme not in ["http", "https"]:
raise ValueError("Invalid URL scheme: %s (%s)" % (
self._scheme, url))
self._session = requests_session
if not self._session:
self._session = requests.Session()
if cert:
self._session.cert = cert
if sslverify is False:
self._session.verify = False
self._session.headers["User-Agent"] = self._user_agent
if is_redhat_bugzilla and self._api_key:
self._use_auth_bearer = True
self._session.headers["Authorization"] = (
"Bearer %s" % self._api_key)
def _get_timeout(self):
# Default to 5 minutes. This is longer than bugzilla.redhat.com's
# apparent 3 minute timeout so shouldn't affect legitimate usage,
# but saves us from indefinite hangs
DEFAULT_TIMEOUT = 300
envtimeout = os.environ.get("PYTHONBUGZILLA_REQUESTS_TIMEOUT")
return float(envtimeout or DEFAULT_TIMEOUT)
def set_rest_defaults(self):
self._session.headers["Content-Type"] = "application/json"
def set_xmlrpc_defaults(self):
self._is_xmlrpc = True
self._session.headers["Content-Type"] = "text/xml"
def get_user_agent(self):
return self._user_agent
def get_scheme(self):
return self._scheme
def get_auth_params(self):
# bugzilla.redhat.com will error if there's auth bits in params
# when Authorization header is used
if self._use_auth_bearer:
return {}
# Don't add a token to the params list if an API key is set.
# Keeping API key solo means bugzilla will definitely fail
# if the key expires. Passing in a token could hide that
# fact, which could make it confusing to pinpoint the issue.
if self._api_key:
# Bugzilla 5.0 only supports api_key as a query parameter.
# Bugzilla 5.1+ takes it as a X-BUGZILLA-API-KEY header as well,
# with query param taking preference.
return {"Bugzilla_api_key": self._api_key}
token = self._tokencache.get_value(self._url)
if token:
return {"Bugzilla_token": token}
return {}
def get_requests_session(self):
return self._session
def request(self, *args, **kwargs):
timeout = self._get_timeout()
if "timeout" not in kwargs:
kwargs["timeout"] = timeout
try:
response = self._session.request(*args, **kwargs)
if self._is_xmlrpc:
# This still appears to matter for properly decoding unicode
# code points in bugzilla.redhat.com content
response.encoding = "UTF-8"
response.raise_for_status()
except Exception as e:
# Scrape the api key out of the returned exception string
message = str(e).replace(self._api_key or "", "")
if isinstance(e, requests.HTTPError):
response = getattr(e, "response", None)
raise BugzillaHTTPError(
message, response=response).with_traceback(
sys.exc_info()[2])
raise type(e)(message).with_traceback(sys.exc_info()[2])
return response
07070100000018000081A400000000000000000000000166EC67150000014E000000000000000000000000000000000000003F00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/bugzilla/_util.py# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
def listify(val):
"""Ensure that value is either None or a list, converting single values
into 1-element lists"""
if val is None:
return val
if isinstance(val, list):
return val
return [val]
07070100000019000081A400000000000000000000000166EC6715000000B8000000000000000000000000000000000000004400000000python-bugzilla-3.2.0+git.1726768917.5eedea3/bugzilla/apiversion.py#
# Copyright (C) 2014 Red Hat Inc.
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
version = "3.3.0"
__version__ = version
0707010000001A000081A400000000000000000000000166EC6715000130FB000000000000000000000000000000000000003E00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/bugzilla/base.py# base.py - the base classes etc. for a Python interface to bugzilla
#
# Copyright (C) 2007, 2008, 2009, 2010 Red Hat Inc.
# Author: Will Woods <wwoods@redhat.com>
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import collections
import getpass
import locale
from logging import getLogger
import mimetypes
import os
import sys
import urllib.parse
from io import BytesIO
from ._authfiles import _BugzillaRCFile, _BugzillaTokenCache
from .apiversion import __version__
from ._backendrest import _BackendREST
from ._backendxmlrpc import _BackendXMLRPC
from .bug import Bug, Group, User
from .exceptions import BugzillaError
from ._rhconverters import _RHBugzillaConverters
from ._session import _BugzillaSession
from ._util import listify
log = getLogger(__name__)
def _nested_update(d, u):
# Helper for nested dict update()
for k, v in list(u.items()):
if isinstance(v, collections.abc.Mapping):
d[k] = _nested_update(d.get(k, {}), v)
else:
d[k] = v
return d
class _FieldAlias(object):
"""
Track API attribute names that differ from what we expose in users.
For example, originally 'short_desc' was the name of the property that
maps to 'summary' on modern bugzilla. We want pre-existing API users
to be able to continue to use Bug.short_desc, and
query({"short_desc": "foo"}). This class tracks that mapping.
@oldname: The old attribute name
@newname: The modern attribute name
@is_api: If True, use this mapping for values sent to the xmlrpc API
(like the query example)
@is_bug: If True, use this mapping for Bug attribute names.
"""
def __init__(self, newname, oldname, is_api=True, is_bug=True):
self.newname = newname
self.oldname = oldname
self.is_api = is_api
self.is_bug = is_bug
class _BugzillaAPICache(object):
"""
Helper class that holds cached API results for things like products,
components, etc.
"""
def __init__(self):
self.products = []
self.component_names = {}
self.bugfields = []
self.version_raw = None
self.version_parsed = (0, 0)
class Bugzilla(object):
"""
The main API object. Connects to a bugzilla instance over XMLRPC, and
provides wrapper functions to simplify dealing with API calls.
The most common invocation here will just be with just a URL:
bzapi = Bugzilla("http://bugzilla.example.com")
If you have previously logged into that URL, and have cached login
tokens, you will automatically be logged in. Otherwise to
log in, you can either pass auth options to __init__, or call a login
helper like interactive_login().
If you are not logged in, you won't be able to access restricted data like
user email, or perform write actions like bug create/update. But simple
querys will work correctly.
If you are unsure if you are logged in, you can check the .logged_in
property.
Another way to specify auth credentials is via a 'bugzillarc' file.
See readconfig() documentation for details.
"""
@staticmethod
def url_to_query(url):
"""
Given a big huge bugzilla query URL, returns a query dict that can
be passed along to the Bugzilla.query() method.
"""
q = {}
# pylint: disable=unpacking-non-sequence
(ignore1, ignore2, path,
ignore, query, ignore3) = urllib.parse.urlparse(url)
base = os.path.basename(path)
if base not in ('buglist.cgi', 'query.cgi'):
return {}
for (k, v) in urllib.parse.parse_qsl(query):
if k not in q:
q[k] = v
elif isinstance(q[k], list):
q[k].append(v)
else:
oldv = q[k]
q[k] = [oldv, v]
# Handle saved searches
if base == "buglist.cgi" and "namedcmd" in q and "sharer_id" in q:
q = {
"sharer_id": q["sharer_id"],
"savedsearch": q["namedcmd"],
}
return q
@staticmethod
def fix_url(url, force_rest=False):
"""
Turn passed url into a bugzilla XMLRPC web url
:param force_rest: If True, generate a REST API url
"""
(scheme, netloc, path,
params, query, fragment) = urllib.parse.urlparse(url)
if not scheme:
scheme = 'https'
if path and not netloc:
netloc = path.split("/", 1)[0]
path = "/".join(path.split("/")[1:]) or None
if not path:
path = 'xmlrpc.cgi'
if force_rest:
path = "rest/"
if not path.startswith("/"):
path = "/" + path
newurl = urllib.parse.urlunparse(
(scheme, netloc, path, params, query, fragment))
return newurl
@staticmethod
def get_rcfile_default_url():
"""
Helper to check all the default bugzillarc file paths for
a [DEFAULT] url=X section, and if found, return it.
"""
configpaths = _BugzillaRCFile.get_default_configpaths()
rcfile = _BugzillaRCFile()
rcfile.set_configpaths(configpaths)
return rcfile.get_default_url()
def __init__(self, url=-1, user=None, password=None, cookiefile=-1,
sslverify=True, tokenfile=-1, use_creds=True, api_key=None,
cert=None, configpaths=-1,
force_rest=False, force_xmlrpc=False, requests_session=None):
"""
:param url: The bugzilla instance URL, which we will connect
to immediately. Most users will want to specify this at
__init__ time, but you can defer connecting by passing
url=None and calling connect(URL) manually
:param user: optional username to connect with
:param password: optional password for the connecting user
:param cert: optional certificate file for client side certificate
authentication
:param cookiefile: Deprecated, raises an error if not -1 or None
:param sslverify: Set this to False to skip SSL hostname and CA
validation checks, like out of date certificate
:param tokenfile: Location to cache the API login token so youi
don't have to keep specifying username/password.
If -1, use the default path. If None, don't use
or save any tokenfile.
:param use_creds: If False, this disables tokenfile
and configpaths by default. This is a convenience option to
unset those values at init time. If those values are later
changed, they may be used for future operations.
:param sslverify: Maps to 'requests' sslverify parameter. Set to
False to disable SSL verification, but it can also be a path
to file or directory for custom certs.
:param api_key: A bugzilla5+ API key
:param configpaths: A list of possible bugzillarc locations.
:param force_rest: Force use of the REST API
:param force_xmlrpc: Force use of the XMLRPC API. If neither force_X
parameter are specified, heuristics will be used to determine
which API to use, with XMLRPC preferred for back compatability.
:param requests_session: An optional requests.Session object the
API will use to contact the remote bugzilla instance. This
way the API user can set up whatever auth bits they may need.
"""
if url == -1:
raise TypeError("Specify a valid bugzilla url, or pass url=None")
# Settings the user might want to tweak
self.user = user or ''
self.password = password or ''
self.api_key = api_key
self.cert = cert or None
self.url = ''
self._backend = None
self._session = None
self._user_requests_session = requests_session
self._sslverify = sslverify
self._cache = _BugzillaAPICache()
self._bug_autorefresh = False
self._is_redhat_bugzilla = False
self._rcfile = _BugzillaRCFile()
self._tokencache = _BugzillaTokenCache()
self._force_rest = force_rest
self._force_xmlrpc = force_xmlrpc
if cookiefile not in [None, -1]:
raise TypeError("cookiefile is deprecated, don't pass any value.")
if not use_creds:
tokenfile = None
configpaths = []
if tokenfile == -1:
tokenfile = self._tokencache.get_default_path()
if configpaths == -1:
configpaths = _BugzillaRCFile.get_default_configpaths()
self._settokenfile(tokenfile)
self._setconfigpath(configpaths)
if url:
self.connect(url)
def _detect_is_redhat_bugzilla(self):
if self._is_redhat_bugzilla:
return True
match = ".redhat.com"
if match in self.url:
log.info("Using RHBugzilla for URL containing %s", match)
return True
return False
def _init_class_from_url(self):
"""
Detect if we should use RHBugzilla class, and if so, set it
"""
from .oldclasses import RHBugzilla # pylint: disable=cyclic-import
if not self._detect_is_redhat_bugzilla():
return
self._is_redhat_bugzilla = True
if self.__class__ == Bugzilla:
# Overriding the class doesn't have any functional effect,
# but we continue to do it for API back compat incase anyone
# is doing any class comparison. We should drop this in the future
self.__class__ = RHBugzilla
def _get_field_aliases(self):
# List of field aliases. Maps old style RHBZ parameter
# names to actual upstream values. Used for createbug() and
# query include_fields at least.
ret = []
def _add(*args, **kwargs):
ret.append(_FieldAlias(*args, **kwargs))
def _add_both(newname, origname):
_add(newname, origname, is_api=False)
_add(origname, newname, is_bug=False)
_add('summary', 'short_desc')
_add('description', 'comment')
_add('platform', 'rep_platform')
_add('severity', 'bug_severity')
_add('status', 'bug_status')
_add('id', 'bug_id')
_add('blocks', 'blockedby')
_add('blocks', 'blocked')
_add('depends_on', 'dependson')
_add('creator', 'reporter')
_add('url', 'bug_file_loc')
_add('dupe_of', 'dupe_id')
_add('dupe_of', 'dup_id')
_add('comments', 'longdescs')
_add('creation_time', 'opendate')
_add('creation_time', 'creation_ts')
_add('whiteboard', 'status_whiteboard')
_add('last_change_time', 'delta_ts')
if self._is_redhat_bugzilla:
_add_both('fixed_in', 'cf_fixed_in')
_add_both('qa_whiteboard', 'cf_qa_whiteboard')
_add_both('devel_whiteboard', 'cf_devel_whiteboard')
_add_both('internal_whiteboard', 'cf_internal_whiteboard')
_add('component', 'components', is_bug=False)
_add('version', 'versions', is_bug=False)
# Yes, sub_components is the field name the API expects
_add('sub_components', 'sub_component', is_bug=False)
# flags format isn't exactly the same but it's the closest approx
_add('flags', 'flag_types')
return ret
def _get_user_agent(self):
return 'python-bugzilla/%s' % __version__
user_agent = property(_get_user_agent)
@property
def bz_ver_major(self):
return self._cache.version_parsed[0]
@property
def bz_ver_minor(self):
return self._cache.version_parsed[1]
###################
# Private helpers #
###################
def _get_version(self):
"""
Return version number as a float
"""
return float("%d.%d" % (self.bz_ver_major, self.bz_ver_minor))
def _get_bug_aliases(self):
return [(f.newname, f.oldname)
for f in self._get_field_aliases() if f.is_bug]
def _get_api_aliases(self):
return [(f.newname, f.oldname)
for f in self._get_field_aliases() if f.is_api]
#################
# Auth handling #
#################
def _getcookiefile(self):
return None
cookiefile = property(_getcookiefile)
def _gettokenfile(self):
return self._tokencache.get_filename()
def _settokenfile(self, filename):
self._tokencache.set_filename(filename)
def _deltokenfile(self):
self._settokenfile(None)
tokenfile = property(_gettokenfile, _settokenfile, _deltokenfile)
def _getconfigpath(self):
return self._rcfile.get_configpaths()
def _setconfigpath(self, configpaths):
return self._rcfile.set_configpaths(configpaths)
def _delconfigpath(self):
return self._rcfile.set_configpaths(None)
configpath = property(_getconfigpath, _setconfigpath, _delconfigpath)
#############################
# Login/connection handling #
#############################
def readconfig(self, configpath=None, overwrite=True):
"""
:param configpath: Optional bugzillarc path to read, instead of
the default list.
This function is called automatically from Bugzilla connect(), which
is called at __init__ if a URL is passed. Calling it manually is
just for passing in a non-standard configpath.
The locations for the bugzillarc file are preferred in this order:
~/.config/python-bugzilla/bugzillarc
~/.bugzillarc
/etc/bugzillarc
It has content like:
[bugzilla.yoursite.com]
user = username
password = password
Or
[bugzilla.yoursite.com]
api_key = key
The file can have multiple sections for different bugzilla instances.
A 'url' field in the [DEFAULT] section can be used to set a default
URL for the bugzilla command line tool.
Be sure to set appropriate permissions on bugzillarc if you choose to
store your password in it!
:param overwrite: If True, bugzillarc will clobber any already
set self.user/password/api_key/cert value.
"""
if configpath:
self._setconfigpath(configpath)
data = self._rcfile.parse(self.url)
for key, val in data.items():
if key == "api_key" and (overwrite or not self.api_key):
log.debug("bugzillarc: setting api_key")
self.api_key = val
elif key == "user" and (overwrite or not self.user):
log.debug("bugzillarc: setting user=%s", val)
self.user = val
elif key == "password" and (overwrite or not self.password):
log.debug("bugzillarc: setting password")
self.password = val
elif key == "cert" and (overwrite or not self.cert):
log.debug("bugzillarc: setting cert")
self.cert = val
else:
log.debug("bugzillarc: unknown key=%s", key)
def _set_bz_version(self, version):
self._cache.version_raw = version
try:
major, minor = [int(i) for i in version.split(".")[0:2]]
except Exception:
log.debug("version doesn't match expected format X.Y.Z, "
"assuming 5.0", exc_info=True)
major = 5
minor = 0
self._cache.version_parsed = (major, minor)
def _get_backend_class(self, url): # pragma: no cover
# This is a hook for the test suite to do some mock hackery
if self._force_rest and self._force_xmlrpc:
raise BugzillaError(
"Cannot specify both force_rest and force_xmlrpc")
xmlurl = self.fix_url(url)
if self._force_xmlrpc:
return _BackendXMLRPC, xmlurl
resturl = self.fix_url(url, force_rest=self._force_rest)
if self._force_rest:
return _BackendREST, resturl
# Simple heuristic if the original url has a path in it
if "/xmlrpc" in url:
return _BackendXMLRPC, xmlurl
if "/rest" in url:
return _BackendREST, resturl
# We were passed something like bugzilla.example.com but we
# aren't sure which method to use, try probing
if _BackendXMLRPC.probe(xmlurl):
return _BackendXMLRPC, xmlurl
if _BackendREST.probe(resturl):
return _BackendREST, resturl
# Otherwise fallback to XMLRPC default and let it fail
return _BackendXMLRPC, xmlurl
def connect(self, url=None):
"""
Connect to the bugzilla instance with the given url. This is
called by __init__ if a URL is passed. Or it can be called manually
at any time with a passed URL.
This will also read any available config files (see readconfig()),
which may set 'user' and 'password', and others.
If 'user' and 'password' are both set, we'll run login(). Otherwise
you'll have to login() yourself before some methods will work.
"""
if self._session:
self.disconnect()
url = url or self.url
backendclass, newurl = self._get_backend_class(url)
if url != newurl:
log.debug("Converted url=%s to fixed url=%s", url, newurl)
self.url = newurl
log.debug("Connecting with URL %s", self.url)
# we've changed URLs - reload config
self.readconfig(overwrite=False)
# Detect if connecting to redhat bugzilla
self._init_class_from_url()
self._session = _BugzillaSession(self.url, self.user_agent,
sslverify=self._sslverify,
cert=self.cert,
tokencache=self._tokencache,
api_key=self.api_key,
is_redhat_bugzilla=self._is_redhat_bugzilla,
requests_session=self._user_requests_session)
self._backend = backendclass(self.url, self._session)
if (self.user and self.password):
log.info("user and password present - doing login()")
self.login()
if self.api_key:
log.debug("using API key")
version = self._backend.bugzilla_version()["version"]
log.debug("Bugzilla version string: %s", version)
self._set_bz_version(version)
@property
def _proxy(self):
"""
Return an xmlrpc ServerProxy instance that will work seamlessly
with bugzilla
Some apps have historically accessed _proxy directly, like
fedora infrastrucutre pieces. So we consider it part of the API
"""
return self._backend.get_xmlrpc_proxy()
def is_xmlrpc(self):
"""
:returns: True if using the XMLRPC API
"""
return self._backend.is_xmlrpc()
def is_rest(self):
"""
:returns: True if using the REST API
"""
return self._backend.is_rest()
def get_requests_session(self):
"""
Give API users access to the Requests.session object we use for
talking to the remote bugzilla instance.
:returns: The Requests.session object backing the open connection.
"""
return self._session.get_requests_session()
def disconnect(self):
"""
Disconnect from the given bugzilla instance.
"""
self._backend = None
self._session = None
self._cache = _BugzillaAPICache()
def login(self, user=None, password=None, restrict_login=None):
"""
Attempt to log in using the given username and password. Subsequent
method calls will use this username and password. Returns False if
login fails, otherwise returns some kind of login info - typically
either a numeric userid, or a dict of user info.
If user is not set, the value of Bugzilla.user will be used. If *that*
is not set, ValueError will be raised. If login fails, BugzillaError
will be raised.
The login session can be restricted to current user IP address
with restrict_login argument. (Bugzilla 4.4+)
This method will be called implicitly at the end of connect() if user
and password are both set. So under most circumstances you won't need
to call this yourself.
"""
if self.api_key:
raise ValueError("cannot login when using an API key")
if user:
self.user = user
if password:
self.password = password
if not self.user:
raise ValueError("missing username")
if not self.password:
raise ValueError("missing password")
payload = {"login": self.user}
if restrict_login:
payload['restrict_login'] = True
log.debug("logging in with options %s", str(payload))
payload['password'] = self.password
try:
ret = self._backend.user_login(payload)
self.password = ''
log.info("login succeeded for user=%s", self.user)
if "token" in ret:
self._tokencache.set_value(self.url, ret["token"])
return ret
except Exception as e:
log.debug("Login exception: %s", str(e), exc_info=True)
raise BugzillaError("Login failed: %s" %
BugzillaError.get_bugzilla_error_string(e)) from None
def interactive_save_api_key(self):
"""
Helper method to interactively ask for an API key, verify it
is valid, and save it to a bugzillarc file referenced via
self.configpaths
"""
sys.stdout.write('API Key: ')
sys.stdout.flush()
api_key = sys.stdin.readline().strip()
self.disconnect()
self.api_key = api_key
log.info('Checking API key... ')
self.connect()
if not self.logged_in: # pragma: no cover
raise BugzillaError("Login with API_KEY failed")
log.info('API Key accepted')
wrote_filename = self._rcfile.save_api_key(self.url, self.api_key)
log.info("API key written to filename=%s", wrote_filename)
msg = "Login successful."
if wrote_filename:
msg += " API key written to %s" % wrote_filename
print(msg)
def interactive_login(self, user=None, password=None, force=False,
restrict_login=None):
"""
Helper method to handle login for this bugzilla instance.
:param user: bugzilla username. If not specified, prompt for it.
:param password: bugzilla password. If not specified, prompt for it.
:param force: Unused
:param restrict_login: restricts session to IP address
"""
ignore = force
log.debug('Calling interactive_login')
if not user:
sys.stdout.write('Bugzilla Username: ')
sys.stdout.flush()
user = sys.stdin.readline().strip()
if not password:
password = getpass.getpass('Bugzilla Password: ')
log.info('Logging in... ')
out = self.login(user, password, restrict_login)
msg = "Login successful."
if "token" not in out:
msg += " However no token was returned."
else:
if not self.tokenfile:
msg += " Token not saved to disk."
else:
msg += " Token cache saved to %s" % self.tokenfile
if self._get_version() >= 5.0:
msg += "\nToken usage is deprecated. "
msg += "Consider using bugzilla API keys instead. "
msg += "See `man bugzilla` for more details."
print(msg)
def logout(self):
"""
Log out of bugzilla. Drops server connection and user info, and
destroys authentication cache
"""
self._backend.user_logout()
self.disconnect()
self.user = ''
self.password = ''
@property
def logged_in(self):
"""
This is True if this instance is logged in else False.
We test if this session is authenticated by calling the User.get()
XMLRPC method with ids set. Logged-out users cannot pass the 'ids'
parameter and will result in a 505 error. If we tried to login with a
token, but the token was incorrect or expired, the server returns a
32000 error.
For Bugzilla 5 and later, a new method, User.valid_login is available
to test the validity of the token. However, this will require that the
username be cached along with the token in order to work effectively in
all scenarios and is not currently used. For more information, refer to
the following url.
http://bugzilla.readthedocs.org/en/latest/api/core/v1/user.html#valid-login
"""
try:
self._backend.user_get({"ids": [1]})
return True
except Exception as e:
code = BugzillaError.get_bugzilla_error_code(e)
if code in [505, 32000]:
return False
raise e
######################
# Bugfields querying #
######################
def getbugfields(self, force_refresh=False, names=None):
"""
Calls getBugFields, which returns a list of fields in each bug
for this bugzilla instance. This can be used to set the list of attrs
on the Bug object.
:param force_refresh: If True, overwrite the bugfield cache
with these newly checked values.
:param names: Only check for the passed bug field names
"""
def _fieldnames():
data = {"include_fields": ["name"]}
if names:
data["names"] = names
r = self._backend.bug_fields(data)
return [f['name'] for f in r['fields']]
if force_refresh or not self._cache.bugfields:
log.debug("Refreshing bugfields")
self._cache.bugfields = _fieldnames()
self._cache.bugfields.sort()
log.debug("bugfields = %s", self._cache.bugfields)
return self._cache.bugfields
bugfields = property(fget=lambda self: self.getbugfields(),
fdel=lambda self: setattr(self, '_bugfields', None))
####################
# Product querying #
####################
def product_get(self, ids=None, names=None,
include_fields=None, exclude_fields=None,
ptype=None):
"""
Raw wrapper around Product.get
https://bugzilla.readthedocs.io/en/latest/api/core/v1/product.html#get-product
This does not perform any caching like other product API calls.
If ids, names, or ptype is not specified, we default to
ptype=accessible for historical reasons
@ids: List of product IDs to lookup
@names: List of product names to lookup
@ptype: Either 'accessible', 'selectable', or 'enterable'. If
specified, we return data for all those
@include_fields: Only include these fields in the output
@exclude_fields: Do not include these fields in the output
"""
if ids is None and names is None and ptype is None:
ptype = "accessible"
if ptype:
raw = None
if ptype == "accessible":
raw = self._backend.product_get_accessible()
elif ptype == "enterable":
raw = self._backend.product_get_enterable()
elif ptype == "selectable":
raw = self._backend.product_get_selectable()
if raw is None:
raise RuntimeError("Unknown ptype=%s" % ptype)
ids = raw['ids']
log.debug("For ptype=%s found ids=%s", ptype, ids)
kwargs = {}
if ids:
kwargs["ids"] = listify(ids)
if names:
kwargs["names"] = listify(names)
if include_fields:
kwargs["include_fields"] = include_fields
if exclude_fields:
kwargs["exclude_fields"] = exclude_fields
ret = self._backend.product_get(kwargs)
return ret['products']
def refresh_products(self, **kwargs):
"""
Refresh a product's cached info. Basically calls product_get
with the passed arguments, and tries to intelligently update
our product cache.
For example, if we already have cached info for product=foo,
and you pass in names=["bar", "baz"], the new cache will have
info for products foo, bar, baz. Individual product fields are
also updated.
"""
for product in self.product_get(**kwargs):
updated = False
for current in self._cache.products[:]:
if (current.get("id", -1) != product.get("id", -2) and
current.get("name", -1) != product.get("name", -2)):
continue
_nested_update(current, product)
updated = True
break
if not updated:
self._cache.products.append(product)
def getproducts(self, force_refresh=False, **kwargs):
"""
Query all products and return the raw dict info. Takes all the
same arguments as product_get.
On first invocation this will contact bugzilla and internally
cache the results. Subsequent getproducts calls or accesses to
self.products will return this cached data only.
:param force_refresh: force refreshing via refresh_products()
"""
if force_refresh or not self._cache.products:
self.refresh_products(**kwargs)
return self._cache.products
products = property(
fget=lambda self: self.getproducts(),
fdel=lambda self: setattr(self, '_products', None),
doc="Helper for accessing the products cache. If nothing "
"has been cached yet, this calls getproducts()")
#######################
# components querying #
#######################
def _lookup_product_in_cache(self, productname):
prodstr = isinstance(productname, str) and productname or None
prodint = isinstance(productname, int) and productname or None
for proddict in self._cache.products:
if prodstr == proddict.get("name", -1):
return proddict
if prodint == proddict.get("id", "nope"):
return proddict
return {}
def getcomponentsdetails(self, product, force_refresh=False):
"""
Wrapper around Product.get(include_fields=["components"]),
returning only the "components" data for the requested product,
slightly reworked to a dict mapping of components.name: components,
for historical reasons.
This uses the product cache, but will update it if the product
isn't found or "components" isn't cached for the product.
In cases like bugzilla.redhat.com where there are tons of
components for some products, this API will time out. You
should use product_get instead.
"""
proddict = self._lookup_product_in_cache(product)
if (force_refresh or not proddict or "components" not in proddict):
self.refresh_products(names=[product],
include_fields=["name", "id", "components"])
proddict = self._lookup_product_in_cache(product)
ret = {}
for compdict in proddict["components"]:
ret[compdict["name"]] = compdict
return ret
def getcomponentdetails(self, product, component, force_refresh=False):
"""
Helper for accessing a single component's info. This is a wrapper
around getcomponentsdetails, see that for explanation
"""
d = self.getcomponentsdetails(product, force_refresh)
return d[component]
def getcomponents(self, product, force_refresh=False):
"""
Return a list of component names for the passed product.
On first invocation the value is cached, and subsequent calls
will return the cached data.
:param force_refresh: Force refreshing the cache, and return
the new data
"""
proddict = self._lookup_product_in_cache(product)
product_id = proddict.get("id", None)
if (force_refresh or product_id is None or
"components" not in proddict):
self.refresh_products(
names=[product],
include_fields=["name", "id", "components.name"])
proddict = self._lookup_product_in_cache(product)
if "id" not in proddict:
raise BugzillaError("Product '%s' not found" % product)
product_id = proddict["id"]
if product_id not in self._cache.component_names:
names = []
for comp in proddict.get("components", []):
name = comp.get("name")
if name:
names.append(name)
self._cache.component_names[product_id] = names
return self._cache.component_names[product_id]
############################
# component adding/editing #
############################
def _component_data_convert(self, data, update=False):
# Back compat for the old RH interface
convert_fields = [
("initialowner", "default_assignee"),
("initialqacontact", "default_qa_contact"),
("initialcclist", "default_cc"),
]
for old, new in convert_fields:
if old in data:
data[new] = data.pop(old)
if update:
names = {"product": data.pop("product"),
"component": data.pop("component")}
updates = {}
for k in list(data.keys()):
updates[k] = data.pop(k)
data["names"] = [names]
data["updates"] = updates
def addcomponent(self, data):
"""
A method to create a component in Bugzilla. Takes a dict, with the
following elements:
product: The product to create the component in
component: The name of the component to create
description: A one sentence summary of the component
default_assignee: The bugzilla login (email address) of the initial
owner of the component
default_qa_contact (optional): The bugzilla login of the
initial QA contact
default_cc: (optional) The initial list of users to be CC'ed on
new bugs for the component.
is_active: (optional) If False, the component is hidden from
the component list when filing new bugs.
"""
data = data.copy()
self._component_data_convert(data)
return self._backend.component_create(data)
def editcomponent(self, data):
"""
A method to edit a component in Bugzilla. Takes a dict, with
mandatory elements of product. component, and initialowner.
All other elements are optional and use the same names as the
addcomponent() method.
"""
data = data.copy()
self._component_data_convert(data, update=True)
return self._backend.component_update(data)
###################
# getbug* methods #
###################
def _process_include_fields(self, include_fields, exclude_fields,
extra_fields):
"""
Internal helper to process include_fields lists
"""
def _convert_fields(_in):
for newname, oldname in self._get_api_aliases():
if oldname in _in:
_in.remove(oldname)
if newname not in _in:
_in.append(newname)
return _in
ret = {}
if include_fields:
include_fields = _convert_fields(include_fields)
if "id" not in include_fields:
include_fields.append("id")
ret["include_fields"] = include_fields
if exclude_fields:
exclude_fields = _convert_fields(exclude_fields)
ret["exclude_fields"] = exclude_fields
if self._supports_getbug_extra_fields():
if extra_fields:
ret["extra_fields"] = _convert_fields(extra_fields)
return ret
def _get_bug_autorefresh(self):
"""
This value is passed to Bug.autorefresh for all fetched bugs.
If True, and an uncached attribute is requested from a Bug,
the Bug will update its contents and try again.
"""
return self._bug_autorefresh
def _set_bug_autorefresh(self, val):
self._bug_autorefresh = bool(val)
bug_autorefresh = property(_get_bug_autorefresh, _set_bug_autorefresh)
def _getbug_extra_fields(self):
"""
Extra fields that need to be explicitly
requested from Bug.get in order for the data to be returned.
"""
rhbz_extra_fields = [
"comments", "description",
"external_bugs", "flags", "sub_components",
"tags",
]
if self._is_redhat_bugzilla:
return rhbz_extra_fields
return []
def _supports_getbug_extra_fields(self):
"""
Return True if the bugzilla instance supports passing
extra_fields to getbug
As of Dec 2012 it seems like only RH bugzilla actually has behavior
like this, for upstream bz it returns all info for every Bug.get()
"""
return self._is_redhat_bugzilla
def _getbugs(self, idlist, permissive,
include_fields=None, exclude_fields=None, extra_fields=None):
"""
Return a list of dicts of full bug info for each given bug id.
bug ids that couldn't be found will return None instead of a dict.
"""
ids = []
aliases = []
def _alias_or_int(_v):
if str(_v).isdigit():
return int(_v), None
return None, str(_v)
for idstr in idlist:
idint, alias = _alias_or_int(idstr)
if alias:
aliases.append(alias)
else:
ids.append(idstr)
if (include_fields is not None and aliases
and "alias" not in include_fields):
# Extra field to prevent sorting (see below) from causing an error
include_fields.append("alias")
extra_fields = listify(extra_fields or [])
extra_fields += self._getbug_extra_fields()
getbugdata = {}
if permissive:
getbugdata["permissive"] = 1
getbugdata.update(self._process_include_fields(
include_fields, exclude_fields, extra_fields))
r = self._backend.bug_get(ids, aliases, getbugdata)
# Do some wrangling to ensure we return bugs in the same order
# the were passed in, for historical reasons
ret = []
for idval in idlist:
idint, alias = _alias_or_int(idval)
for bugdict in r["bugs"]:
if idint is not None and idint != bugdict.get("id", None):
continue
aliaslist = listify(bugdict.get("alias", None) or [])
if alias and alias not in aliaslist:
continue
ret.append(bugdict)
break
return ret
def _getbug(self, objid, **kwargs):
"""
Thin wrapper around _getbugs to handle the slight argument tweaks
for fetching a single bug. The main bit is permissive=False, which
will tell bugzilla to raise an explicit error if we can't fetch
that bug.
This logic is called from Bug() too
"""
return self._getbugs([objid], permissive=False, **kwargs)[0]
def getbug(self, objid,
include_fields=None, exclude_fields=None, extra_fields=None):
"""
Return a Bug object with the full complement of bug data
already loaded.
"""
data = self._getbug(objid,
include_fields=include_fields, exclude_fields=exclude_fields,
extra_fields=extra_fields)
return Bug(self, dict=data, autorefresh=self.bug_autorefresh)
def getbugs(self, idlist,
include_fields=None, exclude_fields=None, extra_fields=None,
permissive=True):
"""
Return a list of Bug objects with the full complement of bug data
already loaded. If there's a problem getting the data for a given id,
the corresponding item in the returned list will be None.
"""
data = self._getbugs(idlist, include_fields=include_fields,
exclude_fields=exclude_fields, extra_fields=extra_fields,
permissive=permissive)
return [(b and Bug(self, dict=b,
autorefresh=self.bug_autorefresh)) or None
for b in data]
def get_comments(self, idlist):
"""
Returns a dictionary of bugs and comments. The comments key will
be empty. See bugzilla docs for details
"""
return self._backend.bug_comments(idlist, {})
#################
# query methods #
#################
def build_query(self,
product=None,
component=None,
version=None,
long_desc=None,
bug_id=None,
short_desc=None,
cc=None,
assigned_to=None,
reporter=None,
qa_contact=None,
status=None,
blocked=None,
dependson=None,
keywords=None,
keywords_type=None,
url=None,
url_type=None,
status_whiteboard=None,
status_whiteboard_type=None,
fixed_in=None,
fixed_in_type=None,
flag=None,
alias=None,
qa_whiteboard=None,
devel_whiteboard=None,
bug_severity=None,
priority=None,
target_release=None,
target_milestone=None,
emailtype=None,
include_fields=None,
quicksearch=None,
savedsearch=None,
savedsearch_sharer_id=None,
sub_component=None,
tags=None,
exclude_fields=None,
extra_fields=None,
limit=None):
"""
Build a query string from passed arguments. Will handle
query parameter differences between various bugzilla versions.
Most of the parameters should be self-explanatory. However,
if you want to perform a complex query, and easy way is to
create it with the bugzilla web UI, copy the entire URL it
generates, and pass it to the static method
Bugzilla.url_to_query
Then pass the output to Bugzilla.query()
For details about the specific argument formats, see the bugzilla docs:
https://bugzilla.readthedocs.io/en/latest/api/core/v1/bug.html#search-bugs
"""
query = {
"alias": alias,
"product": listify(product),
"component": listify(component),
"version": version,
"id": bug_id,
"short_desc": short_desc,
"bug_status": status,
"bug_severity": bug_severity,
"priority": priority,
"target_release": target_release,
"target_milestone": target_milestone,
"tag": listify(tags),
"quicksearch": quicksearch,
"savedsearch": savedsearch,
"sharer_id": savedsearch_sharer_id,
"limit": limit,
# RH extensions... don't add any more. See comment below
"sub_components": listify(sub_component),
}
def add_bool(bzkey, value, bool_id, booltype=None):
value = listify(value)
if value is None:
return bool_id
query["query_format"] = "advanced"
for boolval in value:
def make_bool_str(prefix):
# pylint: disable=cell-var-from-loop
return "%s%i-0-0" % (prefix, bool_id)
query[make_bool_str("field")] = bzkey
query[make_bool_str("value")] = boolval
query[make_bool_str("type")] = booltype or "substring"
bool_id += 1
return bool_id
# RH extensions that we have to maintain here for back compat,
# but all future custom fields should be specified via
# cli --field option, or via extending the query dict() manually.
# No more supporting custom fields in this API
bool_id = 0
bool_id = add_bool("keywords", keywords, bool_id, keywords_type)
bool_id = add_bool("blocked", blocked, bool_id)
bool_id = add_bool("dependson", dependson, bool_id)
bool_id = add_bool("bug_file_loc", url, bool_id, url_type)
bool_id = add_bool("cf_fixed_in", fixed_in, bool_id, fixed_in_type)
bool_id = add_bool("flagtypes.name", flag, bool_id)
bool_id = add_bool("status_whiteboard",
status_whiteboard, bool_id, status_whiteboard_type)
bool_id = add_bool("cf_qa_whiteboard", qa_whiteboard, bool_id)
bool_id = add_bool("cf_devel_whiteboard", devel_whiteboard, bool_id)
def add_email(key, value, count):
if value is None:
return count
if not emailtype:
query[key] = value
return count
query["query_format"] = "advanced"
query['email%i' % count] = value
query['email%s%i' % (key, count)] = True
query['emailtype%i' % count] = emailtype
return count + 1
email_count = 1
email_count = add_email("cc", cc, email_count)
email_count = add_email("assigned_to", assigned_to, email_count)
email_count = add_email("reporter", reporter, email_count)
email_count = add_email("qa_contact", qa_contact, email_count)
if long_desc is not None:
query["query_format"] = "advanced"
query["longdesc"] = long_desc
query["longdesc_type"] = "allwordssubstr"
# 'include_fields' only available for Bugzilla4+
# 'extra_fields' is an RHBZ extension
query.update(self._process_include_fields(
include_fields, exclude_fields, extra_fields))
# Strip out None elements in the dict
for k, v in query.copy().items():
if v is None:
del query[k]
self.pre_translation(query)
return query
def query_return_extra(self, query):
"""
Same as `query()`, but the return value is altered to be
(buglist, values), where `values` is raw dictionary output from
the API call, excluding the bug content. For example this may
include a `limit` value if the bugzilla instance puts an implied
limit on returned result numbers.
"""
try:
r = self._backend.bug_search(query)
log.debug("bug_search returned:\n%s", str(r))
except Exception as e:
# Try to give a hint in the error message if url_to_query
# isn't supported by this bugzilla instance
if ("query_format" not in str(e) or
not BugzillaError.get_bugzilla_error_code(e) or
self._get_version() >= 5.0):
raise
raise BugzillaError("%s\nYour bugzilla instance does not "
"appear to support API queries derived from bugzilla "
"web URL queries." % e) from None
rawbugs = r.pop("bugs")
log.debug("Query returned %s bugs", len(rawbugs))
bugs = [Bug(self, dict=b,
autorefresh=self.bug_autorefresh) for b in rawbugs]
return bugs, r
def query(self, query):
"""
Pass search terms to bugzilla and and return a list of matching
Bug objects.
See `build_query` for more details about constructing the
`query` dict parameter.
"""
bugs, dummy = self.query_return_extra(query)
return bugs
def pre_translation(self, query):
"""
In order to keep the API the same, Bugzilla4 needs to process the
query and the result. This also applies to the refresh() function
"""
if self._is_redhat_bugzilla:
_RHBugzillaConverters.pre_translation(query)
query.update(self._process_include_fields(
query.get("include_fields", []), None, None))
def post_translation(self, query, bug):
"""
In order to keep the API the same, Bugzilla4 needs to process the
query and the result. This also applies to the refresh() function
"""
if self._is_redhat_bugzilla:
_RHBugzillaConverters.post_translation(query, bug)
def bugs_history_raw(self, bug_ids):
"""
Experimental. Gets the history of changes for
particular bugs in the database.
"""
return self._backend.bug_history(bug_ids, {})
#######################################
# Methods for modifying existing bugs #
#######################################
# Bug() also has individual methods for many ops, like setassignee()
def update_bugs(self, ids, updates):
"""
A thin wrapper around bugzilla Bug.update(). Used to update all
values of an existing bug report, as well as add comments.
The dictionary passed to this function should be generated with
build_update(), otherwise we cannot guarantee back compatibility.
"""
tmp = updates.copy()
return self._backend.bug_update(listify(ids), tmp)
def update_tags(self, idlist, tags_add=None, tags_remove=None):
"""
Updates the 'tags' field for a bug.
"""
tags = {}
if tags_add:
tags["add"] = listify(tags_add)
if tags_remove:
tags["remove"] = listify(tags_remove)
d = {
"tags": tags,
}
return self._backend.bug_update_tags(listify(idlist), d)
def update_flags(self, idlist, flags):
"""
A thin back compat wrapper around build_update(flags=X)
"""
return self.update_bugs(idlist, self.build_update(flags=flags))
def build_update(self,
alias=None,
assigned_to=None,
blocks_add=None,
blocks_remove=None,
blocks_set=None,
depends_on_add=None,
depends_on_remove=None,
depends_on_set=None,
cc_add=None,
cc_remove=None,
is_cc_accessible=None,
comment=None,
comment_private=None,
component=None,
deadline=None,
dupe_of=None,
estimated_time=None,
groups_add=None,
groups_remove=None,
keywords_add=None,
keywords_remove=None,
keywords_set=None,
op_sys=None,
platform=None,
priority=None,
product=None,
qa_contact=None,
is_creator_accessible=None,
remaining_time=None,
reset_assigned_to=None,
reset_qa_contact=None,
resolution=None,
see_also_add=None,
see_also_remove=None,
severity=None,
status=None,
summary=None,
target_milestone=None,
target_release=None,
url=None,
version=None,
whiteboard=None,
work_time=None,
fixed_in=None,
qa_whiteboard=None,
devel_whiteboard=None,
internal_whiteboard=None,
sub_component=None,
flags=None,
comment_tags=None,
minor_update=None):
"""
Returns a python dict() with properly formatted parameters to
pass to update_bugs(). See bugzilla documentation for the format
of the individual fields:
https://bugzilla.readthedocs.io/en/latest/api/core/v1/bug.html#create-bug
"""
ret = {}
rhbzret = {}
# These are only supported for rhbugzilla
#
# This should not be extended any more.
# If people want to handle custom fields, manually extend the
# returned dictionary.
rhbzargs = {
"fixed_in": fixed_in,
"devel_whiteboard": devel_whiteboard,
"qa_whiteboard": qa_whiteboard,
"internal_whiteboard": internal_whiteboard,
"sub_component": sub_component,
}
if self._is_redhat_bugzilla:
rhbzret = _RHBugzillaConverters.convert_build_update(
component=component, **rhbzargs)
else:
for key, val in rhbzargs.items():
if val is not None:
raise ValueError("bugzilla instance does not support "
"updating '%s'" % key)
def s(key, val, convert=None):
if val is None:
return
if convert:
val = convert(val)
ret[key] = val
def add_dict(key, add, remove, _set=None):
if add is remove is _set is None:
return
newdict = {}
if add is not None:
newdict["add"] = listify(add)
if remove is not None:
newdict["remove"] = listify(remove)
if _set is not None:
newdict["set"] = listify(_set)
ret[key] = newdict
s("alias", alias)
s("assigned_to", assigned_to)
s("is_cc_accessible", is_cc_accessible, bool)
s("component", component)
s("deadline", deadline)
s("dupe_of", dupe_of, int)
s("estimated_time", estimated_time, int)
s("op_sys", op_sys)
s("platform", platform)
s("priority", priority)
s("product", product)
s("qa_contact", qa_contact)
s("is_creator_accessible", is_creator_accessible, bool)
s("remaining_time", remaining_time, float)
s("reset_assigned_to", reset_assigned_to, bool)
s("reset_qa_contact", reset_qa_contact, bool)
s("resolution", resolution)
s("severity", severity)
s("status", status)
s("summary", summary)
s("target_milestone", target_milestone)
s("target_release", target_release)
s("url", url)
s("version", version)
s("whiteboard", whiteboard)
s("work_time", work_time, float)
s("flags", flags)
s("comment_tags", comment_tags, listify)
s("minor_update", minor_update, bool)
add_dict("blocks", blocks_add, blocks_remove, blocks_set)
add_dict("depends_on", depends_on_add, depends_on_remove, depends_on_set)
add_dict("cc", cc_add, cc_remove)
add_dict("groups", groups_add, groups_remove)
add_dict("keywords", keywords_add, keywords_remove, keywords_set)
add_dict("see_also", see_also_add, see_also_remove)
if comment is not None:
ret["comment"] = {"comment": comment}
if comment_private:
ret["comment"]["is_private"] = comment_private
ret.update(rhbzret)
return ret
########################################
# Methods for working with attachments #
########################################
def attachfile(self, idlist, attachfile, description, **kwargs):
"""
Attach a file to the given bug IDs. Returns the ID of the attachment
or raises XMLRPC Fault if something goes wrong.
attachfile may be a filename (which will be opened) or a file-like
object, which must provide a 'read' method. If it's not one of these,
this method will raise a TypeError.
description is the short description of this attachment.
Optional keyword args are as follows:
file_name: this will be used as the filename for the attachment.
REQUIRED if attachfile is a file-like object with no
'name' attribute, otherwise the filename or .name
attribute will be used.
comment: An optional comment about this attachment.
is_private: Set to True if the attachment should be marked private.
is_patch: Set to True if the attachment is a patch.
content_type: The mime-type of the attached file. Defaults to
application/octet-stream if not set. NOTE that text
files will *not* be viewable in bugzilla unless you
remember to set this to text/plain. So remember that!
Returns the list of attachment ids that were added. If only one
attachment was added, we return the single int ID for back compat
"""
if isinstance(attachfile, str):
f = open(attachfile, "rb")
elif hasattr(attachfile, 'read'):
f = attachfile
else:
raise TypeError("attachfile must be filename or file-like object")
# Back compat
if "contenttype" in kwargs:
kwargs["content_type"] = kwargs.pop("contenttype")
if "ispatch" in kwargs:
kwargs["is_patch"] = kwargs.pop("ispatch")
if "isprivate" in kwargs:
kwargs["is_private"] = kwargs.pop("isprivate")
if "filename" in kwargs:
kwargs["file_name"] = kwargs.pop("filename")
kwargs['summary'] = description
data = f.read()
if not isinstance(data, bytes): # pragma: no cover
data = data.encode(locale.getpreferredencoding())
if 'file_name' not in kwargs and hasattr(f, "name"):
kwargs['file_name'] = os.path.basename(f.name)
if 'content_type' not in kwargs:
ctype = None
if kwargs['file_name']:
ctype = mimetypes.guess_type(
kwargs['file_name'], strict=False)[0]
kwargs['content_type'] = ctype or 'application/octet-stream'
ret = self._backend.bug_attachment_create(
listify(idlist), data, kwargs)
if "attachments" in ret:
# Up to BZ 4.2
ret = [int(k) for k in ret["attachments"].keys()]
elif "ids" in ret:
# BZ 4.4+
ret = ret["ids"]
if isinstance(ret, list) and len(ret) == 1:
ret = ret[0]
return ret
def openattachment_data(self, attachment_dict):
"""
Helper for turning passed API attachment dictionary into a
filelike object
"""
ret = BytesIO()
data = attachment_dict["data"]
if hasattr(data, "data"):
# This is for xmlrpc Binary
content = data.data # pragma: no cover
else:
import base64
content = base64.b64decode(data)
ret.write(content)
ret.name = attachment_dict["file_name"]
ret.seek(0)
return ret
def openattachment(self, attachid):
"""
Get the contents of the attachment with the given attachment ID.
Returns a file-like object.
"""
attachments = self.get_attachments(None, attachid)
data = attachments["attachments"][str(attachid)]
return self.openattachment_data(data)
def updateattachmentflags(self, bugid, attachid, flagname, **kwargs):
"""
Updates a flag for the given attachment ID.
Optional keyword args are:
status: new status for the flag ('-', '+', '?', 'X')
requestee: new requestee for the flag
"""
# Bug ID was used for the original custom redhat API, no longer
# needed though
ignore = bugid
flags = {"name": flagname}
flags.update(kwargs)
attachment_ids = [int(attachid)]
update = {'flags': [flags]}
return self._backend.bug_attachment_update(attachment_ids, update)
def get_attachments(self, ids, attachment_ids,
include_fields=None, exclude_fields=None):
"""
Wrapper for Bug.attachments. One of ids or attachment_ids is required
:param ids: Get attachments for this bug ID
:param attachment_ids: Specific attachment ID to get
https://bugzilla.readthedocs.io/en/latest/api/core/v1/attachment.html#get-attachment
"""
params = {}
if include_fields:
params["include_fields"] = listify(include_fields)
if exclude_fields:
params["exclude_fields"] = listify(exclude_fields)
if attachment_ids:
return self._backend.bug_attachment_get(attachment_ids, params)
return self._backend.bug_attachment_get_all(ids, params)
#####################
# createbug methods #
#####################
createbug_required = ('product', 'component', 'summary', 'version',
'description')
def build_createbug(self,
product=None,
component=None,
version=None,
summary=None,
description=None,
comment_private=None,
blocks=None,
cc=None,
assigned_to=None,
keywords=None,
depends_on=None,
groups=None,
op_sys=None,
platform=None,
priority=None,
qa_contact=None,
resolution=None,
severity=None,
status=None,
target_milestone=None,
target_release=None,
url=None,
sub_component=None,
alias=None,
comment_tags=None):
"""
Returns a python dict() with properly formatted parameters to
pass to createbug(). See bugzilla documentation for the format
of the individual fields:
https://bugzilla.readthedocs.io/en/latest/api/core/v1/bug.html#update-bug
"""
localdict = {}
if blocks:
localdict["blocks"] = listify(blocks)
if cc:
localdict["cc"] = listify(cc)
if depends_on:
localdict["depends_on"] = listify(depends_on)
if groups is not None:
localdict["groups"] = listify(groups)
if keywords:
localdict["keywords"] = listify(keywords)
if description:
localdict["description"] = description
if comment_private:
localdict["comment_is_private"] = True
# Most of the machinery and formatting here is the same as
# build_update, so reuse that as much as possible
ret = self.build_update(product=product, component=component,
version=version, summary=summary, op_sys=op_sys,
platform=platform, priority=priority, qa_contact=qa_contact,
resolution=resolution, severity=severity, status=status,
target_milestone=target_milestone,
target_release=target_release, url=url,
assigned_to=assigned_to, sub_component=sub_component,
alias=alias, comment_tags=comment_tags)
ret.update(localdict)
return ret
def _validate_createbug(self, *args, **kwargs):
# Previous API required users specifying keyword args that mapped
# to the XMLRPC arg names. Maintain that bad compat, but also allow
# receiving a single dictionary like query() does
if kwargs and args: # pragma: no cover
raise BugzillaError("createbug: cannot specify positional "
"args=%s with kwargs=%s, must be one or the "
"other." % (args, kwargs))
if args:
if len(args) > 1 or not isinstance(args[0], dict):
raise BugzillaError( # pragma: no cover
"createbug: positional arguments only "
"accept a single dictionary.")
data = args[0]
else:
data = kwargs
# If we're getting a call that uses an old fieldname, convert it to the
# new fieldname instead.
for newname, oldname in self._get_api_aliases():
if (newname in self.createbug_required and
newname not in data and
oldname in data):
data[newname] = data.pop(oldname)
# Back compat handling for check_args
if "check_args" in data:
del data["check_args"]
return data
def createbug(self, *args, **kwargs):
"""
Create a bug with the given info. Returns a new Bug object.
Check bugzilla API documentation for valid values, at least
product, component, summary, version, and description need to
be passed.
"""
data = self._validate_createbug(*args, **kwargs)
rawbug = self._backend.bug_create(data)
return Bug(self, bug_id=rawbug["id"],
autorefresh=self.bug_autorefresh)
##############################
# Methods for handling Users #
##############################
def getuser(self, username):
"""
Return a bugzilla User for the given username
:arg username: The username used in bugzilla.
:raises XMLRPC Fault: Code 51 if the username does not exist
:returns: User record for the username
"""
ret = self.getusers(username)
return ret and ret[0]
def getusers(self, userlist):
"""
Return a list of Users from .
:userlist: List of usernames to lookup
:returns: List of User records
"""
userlist = listify(userlist)
rawusers = self._backend.user_get({"names": userlist})
userobjs = [User(self, **rawuser) for rawuser in
rawusers.get('users', [])]
# Return users in same order they were passed in
ret = []
for u in userlist:
for uobj in userobjs[:]:
if uobj.email == u:
userobjs.remove(uobj)
ret.append(uobj)
break
ret += userobjs
return ret
def searchusers(self, pattern):
"""
Return a bugzilla User for the given list of patterns
:arg pattern: List of patterns to match against.
:returns: List of User records
"""
rawusers = self._backend.user_get({"match": listify(pattern)})
return [User(self, **rawuser) for rawuser in
rawusers.get('users', [])]
def createuser(self, email, name='', password=''):
"""
Return a bugzilla User for the given username
:arg email: The email address to use in bugzilla
:kwarg name: Real name to associate with the account
:kwarg password: Password to set for the bugzilla account
:raises XMLRPC Fault: Code 501 if the username already exists
Code 500 if the email address isn't valid
Code 502 if the password is too short
Code 503 if the password is too long
:return: User record for the username
"""
args = {"email": email}
if name:
args["name"] = name
if password:
args["password"] = password
self._backend.user_create(args)
return self.getuser(email)
def updateperms(self, user, action, groups):
"""
A method to update the permissions (group membership) of a bugzilla
user.
:arg user: The e-mail address of the user to be acted upon. Can
also be a list of emails.
:arg action: add, remove, or set
:arg groups: list of groups to be added to (i.e. ['fedora_contrib'])
"""
groups = listify(groups)
if action == "rem":
action = "remove"
if action not in ["add", "remove", "set"]:
raise BugzillaError("Unknown user permission action '%s'" % action)
update = {
"names": listify(user),
"groups": {
action: groups,
}
}
return self._backend.user_update(update)
###############################
# Methods for handling Groups #
###############################
def _getgroups(self, names, membership=False):
"""
Return a list of groups that match criteria.
:kwarg ids: list of group ids to return data on
:kwarg membership: boolean specifying wether to query the members
of the group or not.
:raises XMLRPC Fault: Code 51: if a Bad Login Name was sent to the
names array.
Code 304: if the user was not authorized to see user they
requested.
Code 505: user is logged out and can't use the match or ids
parameter.
Code 805: logged in user do not have enough priviledges to view
groups.
"""
params = {"membership": membership}
params['names'] = listify(names)
return self._backend.group_get(params)
def getgroup(self, name, membership=False):
"""
Return a bugzilla Group for the given name
:arg name: The group name used in bugzilla.
:raises XMLRPC Fault: Code 51 if the name does not exist
:raises XMLRPC Fault: Code 805 if the user does not have enough
permissions to view groups
:returns: Group record for the name
"""
ret = self.getgroups(name, membership=membership)
return ret and ret[0]
def getgroups(self, grouplist, membership=False):
"""
Return a list of Groups from .
:userlist: List of group names to lookup
:returns: List of Group records
"""
grouplist = listify(grouplist)
groupobjs = [
Group(self, **rawgroup)
for rawgroup in self._getgroups(
names=grouplist, membership=membership).get('groups', [])
]
# Return in same order they were passed in
ret = []
for g in grouplist:
for gobj in groupobjs[:]:
if gobj.name == g:
groupobjs.remove(gobj)
ret.append(gobj)
break
ret += groupobjs
return ret
#############################
# ExternalBugs API wrappers #
#############################
def add_external_tracker(self, bug_ids, ext_bz_bug_id, ext_type_id=None,
ext_type_description=None, ext_type_url=None,
ext_status=None, ext_description=None,
ext_priority=None):
"""
Wrapper method to allow adding of external tracking bugs using the
ExternalBugs::WebService::add_external_bug method.
This is documented at
https://bugzilla.redhat.com/docs/en/html/integrating/api/Bugzilla/Extension/ExternalBugs/WebService.html#add-external-bug
bug_ids: A single bug id or list of bug ids to have external trackers
added.
ext_bz_bug_id: The external bug id (ie: the bug number in the
external tracker).
ext_type_id: The external tracker id as used by Bugzilla.
ext_type_description: The external tracker description as used by
Bugzilla.
ext_type_url: The external tracker url as used by Bugzilla.
ext_status: The status of the external bug.
ext_description: The description of the external bug.
ext_priority: The priority of the external bug.
"""
param_dict = {'ext_bz_bug_id': ext_bz_bug_id}
if ext_type_id is not None:
param_dict['ext_type_id'] = ext_type_id
if ext_type_description is not None:
param_dict['ext_type_description'] = ext_type_description
if ext_type_url is not None:
param_dict['ext_type_url'] = ext_type_url
if ext_status is not None:
param_dict['ext_status'] = ext_status
if ext_description is not None:
param_dict['ext_description'] = ext_description
if ext_priority is not None:
param_dict['ext_priority'] = ext_priority
params = {
'bug_ids': listify(bug_ids),
'external_bugs': [param_dict],
}
return self._backend.externalbugs_add(params)
def update_external_tracker(self, ids=None, ext_type_id=None,
ext_type_description=None, ext_type_url=None,
ext_bz_bug_id=None, bug_ids=None,
ext_status=None, ext_description=None,
ext_priority=None):
"""
Wrapper method to allow adding of external tracking bugs using the
ExternalBugs::WebService::update_external_bug method.
This is documented at
https://bugzilla.redhat.com/docs/en/html/integrating/api/Bugzilla/Extension/ExternalBugs/WebService.html#update-external-bug
ids: A single external tracker bug id or list of external tracker bug
ids.
ext_type_id: The external tracker id as used by Bugzilla.
ext_type_description: The external tracker description as used by
Bugzilla.
ext_type_url: The external tracker url as used by Bugzilla.
ext_bz_bug_id: A single external bug id or list of external bug ids
(ie: the bug number in the external tracker).
bug_ids: A single bug id or list of bug ids to have external tracker
info updated.
ext_status: The status of the external bug.
ext_description: The description of the external bug.
ext_priority: The priority of the external bug.
"""
params = {}
if ids is not None:
params['ids'] = listify(ids)
if ext_type_id is not None:
params['ext_type_id'] = ext_type_id
if ext_type_description is not None:
params['ext_type_description'] = ext_type_description
if ext_type_url is not None:
params['ext_type_url'] = ext_type_url
if ext_bz_bug_id is not None:
params['ext_bz_bug_id'] = listify(ext_bz_bug_id)
if bug_ids is not None:
params['bug_ids'] = listify(bug_ids)
if ext_status is not None:
params['ext_status'] = ext_status
if ext_description is not None:
params['ext_description'] = ext_description
if ext_priority is not None:
params['ext_priority'] = ext_priority
return self._backend.externalbugs_update(params)
def remove_external_tracker(self, ids=None, ext_type_id=None,
ext_type_description=None, ext_type_url=None,
ext_bz_bug_id=None, bug_ids=None):
"""
Wrapper method to allow removal of external tracking bugs using the
ExternalBugs::WebService::remove_external_bug method.
This is documented at
https://bugzilla.redhat.com/docs/en/html/integrating/api/Bugzilla/Extension/ExternalBugs/WebService.html#remove-external-bug
ids: A single external tracker bug id or list of external tracker bug
ids.
ext_type_id: The external tracker id as used by Bugzilla.
ext_type_description: The external tracker description as used by
Bugzilla.
ext_type_url: The external tracker url as used by Bugzilla.
ext_bz_bug_id: A single external bug id or list of external bug ids
(ie: the bug number in the external tracker).
bug_ids: A single bug id or list of bug ids to have external tracker
info updated.
"""
params = {}
if ids is not None:
params['ids'] = listify(ids)
if ext_type_id is not None:
params['ext_type_id'] = ext_type_id
if ext_type_description is not None:
params['ext_type_description'] = ext_type_description
if ext_type_url is not None:
params['ext_type_url'] = ext_type_url
if ext_bz_bug_id is not None:
params['ext_bz_bug_id'] = listify(ext_bz_bug_id)
if bug_ids is not None:
params['bug_ids'] = listify(bug_ids)
return self._backend.externalbugs_remove(params)
0707010000001B000081A400000000000000000000000166EC671500004471000000000000000000000000000000000000003D00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/bugzilla/bug.py# Copyright (C) 2007, 2008, 2009, 2010 Red Hat Inc.
# Author: Will Woods <wwoods@redhat.com>
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import copy
from logging import getLogger
from urllib.parse import urlparse, urlunparse
log = getLogger(__name__)
class Bug(object):
"""
A container object for a bug report. Requires a Bugzilla instance -
every Bug is on a Bugzilla, obviously.
Optional keyword args:
dict=DICT - populate attributes with the result of a getBug() call
bug_id=ID - if dict does not contain bug_id, this is required before
you can read any attributes or make modifications to this
bug.
"""
def __init__(self, bugzilla, bug_id=None, dict=None, autorefresh=False):
# pylint: disable=redefined-builtin
# API had pre-existing issue that we can't change ('dict' usage)
self.bugzilla = bugzilla
self._rawdata = {}
self.autorefresh = autorefresh
# pylint: disable=protected-access
self._aliases = self.bugzilla._get_bug_aliases()
# pylint: enable=protected-access
if not dict:
dict = {}
if bug_id:
dict["id"] = bug_id
self._update_dict(dict)
self.weburl = self._generate_weburl()
def _generate_weburl(self):
"""
Generate the URL to the bug in the web UI
"""
parsed = urlparse(self.bugzilla.url)
return urlunparse((parsed.scheme, parsed.netloc,
'/show_bug.cgi', '', 'id=%s' % self.bug_id,
''))
def __str__(self):
"""
Return a simple string representation of this bug
"""
return self.__unicode__()
def __unicode__(self):
"""
Return a simple unicode string representation of this bug
"""
return "#%-6s %-10s - %s - %s" % (self.bug_id, self.bug_status,
self.assigned_to, self.summary)
def __repr__(self):
url = ""
if self.bugzilla:
url = self.bugzilla.url
return '<Bug #%i on %s at %#x>' % (self.bug_id, url, id(self))
def __getattr__(self, name):
refreshed = False
while True:
if refreshed and name in self.__dict__:
# If name was in __dict__ to begin with, __getattr__ would
# have never been called.
return self.__dict__[name]
for newname, oldname in self._aliases:
if name == oldname and newname in self.__dict__:
return self.__dict__[newname]
# Doing dir(bugobj) does getattr __members__/__methods__,
# don't refresh for those
if name.startswith("__") and name.endswith("__"):
break
if refreshed or not self.autorefresh:
break
log.info("Bug %i missing attribute '%s' - doing implicit "
"refresh(). This will be slow, if you want to avoid "
"this, properly use query/getbug include_fields, and "
"set bugzilla.bug_autorefresh = False to force failure.",
self.bug_id, name)
# We pass the attribute name to getbug, since for something like
# 'attachments' which downloads lots of data we really want the
# user to opt in.
self.refresh(extra_fields=[name])
refreshed = True
msg = ("Bug object has no attribute '%s'." % name)
if not self.autorefresh:
msg += ("\nIf '%s' is a bugzilla attribute, it may not have "
"been cached when the bug was fetched. You may want "
"to adjust your include_fields for getbug/query." % name)
raise AttributeError(msg)
def get_raw_data(self):
"""
Return the raw API dictionary data that has been used to
populate this bug
"""
return copy.deepcopy(self._rawdata)
def refresh(self, include_fields=None, exclude_fields=None,
extra_fields=None):
"""
Refresh the bug with the latest data from bugzilla
"""
# pylint: disable=protected-access
extra_fields = list(self._rawdata.keys()) + (extra_fields or [])
r = self.bugzilla._getbug(self.bug_id,
include_fields=include_fields, exclude_fields=exclude_fields,
extra_fields=extra_fields)
# pylint: enable=protected-access
self._update_dict(r)
reload = refresh
def _translate_dict(self, newdict):
if self.bugzilla:
self.bugzilla.post_translation({}, newdict)
for newname, oldname in self._aliases:
if oldname not in newdict:
continue
if newname not in newdict:
newdict[newname] = newdict[oldname]
elif newdict[newname] != newdict[oldname]:
log.debug("Update dict contained differing alias values "
"d[%s]=%s and d[%s]=%s , dropping the value "
"d[%s]", newname, newdict[newname], oldname,
newdict[oldname], oldname)
del newdict[oldname]
def _update_dict(self, newdict):
"""
Update internal dictionary, in a way that ensures no duplicate
entries are stored WRT field aliases
"""
self._translate_dict(newdict)
self._rawdata.update(newdict)
self.__dict__.update(newdict)
if 'id' not in self.__dict__ and 'bug_id' not in self.__dict__:
raise TypeError("Bug object needs a bug_id")
##################
# pickle helpers #
##################
def __getstate__(self):
ret = self._rawdata.copy()
ret["_aliases"] = self._aliases
return ret
def __setstate__(self, vals):
self._rawdata = {}
self.bugzilla = None
self._aliases = vals.get("_aliases", [])
self.autorefresh = False
self._update_dict(vals)
#####################
# Modify bug status #
#####################
def setstatus(self, status, comment=None, private=False):
"""
Update the status for this bug report.
Commonly-used values are ASSIGNED, MODIFIED, and NEEDINFO.
To change bugs to CLOSED, use .close() instead.
"""
# Note: fedora bodhi uses this function
vals = self.bugzilla.build_update(status=status,
comment=comment,
comment_private=private)
log.debug("setstatus: update=%s", vals)
return self.bugzilla.update_bugs(self.bug_id, vals)
def close(self, resolution, dupeid=None, fixedin=None,
comment=None, isprivate=False):
"""
Close this bug.
Valid values for resolution are in bz.querydefaults['resolution_list']
For bugzilla.redhat.com that's:
['NOTABUG', 'WONTFIX', 'DEFERRED', 'WORKSFORME', 'CURRENTRELEASE',
'RAWHIDE', 'ERRATA', 'DUPLICATE', 'UPSTREAM', 'NEXTRELEASE',
'CANTFIX', 'INSUFFICIENT_DATA']
If using DUPLICATE, you need to set dupeid to the ID of the other bug.
If using WORKSFORME/CURRENTRELEASE/RAWHIDE/ERRATA/UPSTREAM/NEXTRELEASE
you can (and should) set 'new_fixed_in' to a string representing the
version that fixes the bug.
You can optionally add a comment while closing the bug. Set 'isprivate'
to True if you want that comment to be private.
"""
# Note: fedora bodhi uses this function
vals = self.bugzilla.build_update(comment=comment,
comment_private=isprivate,
resolution=resolution,
dupe_of=dupeid,
fixed_in=fixedin,
status=str("CLOSED"))
log.debug("close: update=%s", vals)
return self.bugzilla.update_bugs(self.bug_id, vals)
#####################
# Modify bug emails #
#####################
def setassignee(self, assigned_to=None,
qa_contact=None, comment=None):
"""
Set any of the assigned_to or qa_contact fields to a new
bugzilla account, with an optional comment, e.g.
setassignee(assigned_to='wwoods@redhat.com')
setassignee(qa_contact='wwoods@redhat.com', comment='wwoods QA ftw')
You must set at least one of the two assignee fields, or this method
will throw a ValueError.
Returns [bug_id, mailresults].
"""
if not (assigned_to or qa_contact):
raise ValueError("You must set one of assigned_to "
" or qa_contact")
vals = self.bugzilla.build_update(assigned_to=assigned_to,
qa_contact=qa_contact,
comment=comment)
log.debug("setassignee: update=%s", vals)
return self.bugzilla.update_bugs(self.bug_id, vals)
def addcc(self, cclist, comment=None):
"""
Adds the given email addresses to the CC list for this bug.
cclist: list of email addresses (strings)
comment: optional comment to add to the bug
"""
vals = self.bugzilla.build_update(comment=comment,
cc_add=cclist)
log.debug("addcc: update=%s", vals)
return self.bugzilla.update_bugs(self.bug_id, vals)
def deletecc(self, cclist, comment=None):
"""
Removes the given email addresses from the CC list for this bug.
"""
vals = self.bugzilla.build_update(comment=comment,
cc_remove=cclist)
log.debug("deletecc: update=%s", vals)
return self.bugzilla.update_bugs(self.bug_id, vals)
####################
# comment handling #
####################
def addcomment(self, comment, private=False):
"""
Add the given comment to this bug. Set private to True to mark this
comment as private.
"""
# Note: fedora bodhi uses this function
vals = self.bugzilla.build_update(comment=comment,
comment_private=private)
log.debug("addcomment: update=%s", vals)
return self.bugzilla.update_bugs(self.bug_id, vals)
def getcomments(self):
"""
Returns an array of comment dictionaries for this bug
"""
comment_list = self.bugzilla.get_comments([self.bug_id])
return comment_list['bugs'][str(self.bug_id)]['comments']
#####################
# Get/Set bug flags #
#####################
def get_flag_type(self, name):
"""
Return flag_type information for a specific flag
Older RHBugzilla returned a lot more info here, but it was
non-upstream and is now gone.
"""
for t in self.flags:
if t['name'] == name:
return t
return None
def get_flags(self, name):
"""
Return flag value information for a specific flag
"""
ft = self.get_flag_type(name)
if not ft:
return None
return [ft]
def get_flag_status(self, name):
"""
Return a flag 'status' field
This method works only for simple flags that have only a 'status' field
with no "requestee" info, and no multiple values. For more complex
flags, use get_flags() to get extended flag value information.
"""
f = self.get_flags(name)
if not f:
return None
# This method works only for simple flags that have only one
# value set.
assert len(f) <= 1
return f[0]['status']
def updateflags(self, flags):
"""
Thin wrapper around build_update(flags=X). This only handles simple
status changes, anything like needinfo requestee needs to call
build_update + update_bugs directly
:param flags: Dictionary of the form {"flagname": "status"}, example
{"needinfo": "?", "devel_ack": "+"}
"""
flaglist = []
for key, value in flags.items():
flaglist.append({"name": key, "status": value})
return self.bugzilla.update_bugs([self.bug_id],
self.bugzilla.build_update(flags=flaglist))
########################
# Experimental methods #
########################
def get_attachments(self, include_fields=None, exclude_fields=None):
"""
Helper call to Bugzilla.get_attachments. If you want to fetch
specific attachment IDs, use that function instead
"""
if "attachments" in self.__dict__:
return self.attachments
data = self.bugzilla.get_attachments([self.bug_id], None,
include_fields, exclude_fields)
return data["bugs"][str(self.bug_id)]
def get_attachment_ids(self):
"""
Helper function to return only the attachment IDs for this bug
"""
return [a["id"] for a in self.get_attachments(exclude_fields=["data"])]
def get_history_raw(self):
"""
Experimental. Get the history of changes for this bug.
"""
return self.bugzilla.bugs_history_raw([self.bug_id])
class User(object):
"""
Container object for a bugzilla User.
:arg bugzilla: Bugzilla instance that this User belongs to.
Rest of the params come straight from User.get()
"""
def __init__(self, bugzilla, **kwargs):
self.bugzilla = bugzilla
self.__userid = kwargs.get('id')
self.__name = kwargs.get('name')
self.__email = kwargs.get('email', self.__name)
self.__can_login = kwargs.get('can_login', False)
self.real_name = kwargs.get('real_name', None)
self.password = None
self.groups = kwargs.get('groups', {})
self.groupnames = []
for g in self.groups:
if "name" in g:
self.groupnames.append(g["name"])
self.groupnames.sort()
########################
# Read-only attributes #
########################
# We make these properties so that the user cannot set them. They are
# unaffected by the update() method so it would be misleading to let them
# be changed.
@property
def userid(self):
return self.__userid
@property
def email(self):
return self.__email
@property
def can_login(self):
return self.__can_login
# name is a key in some methods. Mark it dirty when we change it #
@property
def name(self):
return self.__name
def refresh(self):
"""
Update User object with latest info from bugzilla
"""
newuser = self.bugzilla.getuser(self.email)
self.__dict__.update(newuser.__dict__)
def updateperms(self, action, groups):
"""
A method to update the permissions (group membership) of a bugzilla
user.
:arg action: add, remove, or set
:arg groups: list of groups to be added to (i.e. ['fedora_contrib'])
"""
self.bugzilla.updateperms(self.name, action, groups)
class Group(object):
"""
Container object for a bugzilla Group.
:arg bugzilla: Bugzilla instance that this Group belongs to.
Rest of the params come straight from Group.get()
"""
def __init__(self, bugzilla, **kwargs):
self.bugzilla = bugzilla
self.__groupid = kwargs.get('id')
self.name = kwargs.get('name')
self.description = kwargs.get('description', self.name)
self.is_active = kwargs.get('is_active', False)
self.icon_url = kwargs.get('icon_url', None)
self.is_active_bug_group = kwargs.get('is_active_bug_group', None)
self.membership = kwargs.get('membership', [])
self.__member_emails = set()
self._refresh_member_emails_list()
########################
# Read-only attributes #
########################
# We make these properties so that the user cannot set them. They are
# unaffected by the update() method so it would be misleading to let them
# be changed.
@property
def groupid(self):
return self.__groupid
@property
def member_emails(self):
return sorted(self.__member_emails)
def _refresh_member_emails_list(self):
"""
Refresh the list of emails of the members of the group.
"""
if self.membership:
for m in self.membership:
if "email" in m:
self.__member_emails.add(m["email"])
def refresh(self, membership=False):
"""
Update Group object with latest info from bugzilla
"""
newgroup = self.bugzilla.getgroup(
self.name, membership=membership)
self.__dict__.update(newgroup.__dict__)
self._refresh_member_emails_list()
def members(self):
"""
Retrieve the members of this Group from bugzilla
"""
if not self.membership:
self.refresh(membership=True)
return self.membership
0707010000001C000081A400000000000000000000000166EC671500000556000000000000000000000000000000000000004400000000python-bugzilla-3.2.0+git.1726768917.5eedea3/bugzilla/exceptions.py# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
from requests import HTTPError
class BugzillaError(Exception):
"""
Error raised in the Bugzilla client code.
"""
@staticmethod
def get_bugzilla_error_string(exc):
"""
Helper to return the bugzilla instance error message from an
XMLRPC Fault, or any other exception type that's raised from bugzilla
interaction
"""
return getattr(exc, "faultString", str(exc))
@staticmethod
def get_bugzilla_error_code(exc):
"""
Helper to return the bugzilla instance error code from an
XMLRPC Fault, or any other exception type that's raised from bugzilla
interaction
"""
for propname in ["faultCode", "code"]:
if hasattr(exc, propname):
return getattr(exc, propname)
return None
def __init__(self, message, code=None):
"""
:param code: The error code from the remote bugzilla instance. Only
set if the error came directly from the remove bugzilla
"""
self.code = code
if self.code:
message += " (code=%s)" % self.code
Exception.__init__(self, message)
class BugzillaHTTPError(HTTPError):
"""Error raised in the Bugzilla session"""
0707010000001D000081A400000000000000000000000166EC6715000004BB000000000000000000000000000000000000004400000000python-bugzilla-3.2.0+git.1726768917.5eedea3/bugzilla/oldclasses.py# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
from .base import Bugzilla
# These are old compat classes. Nothing new should be added here,
# and these should not be altered
class Bugzilla3(Bugzilla):
pass
class Bugzilla32(Bugzilla):
pass
class Bugzilla34(Bugzilla):
pass
class Bugzilla36(Bugzilla):
pass
class Bugzilla4(Bugzilla):
pass
class Bugzilla42(Bugzilla):
pass
class Bugzilla44(Bugzilla):
pass
class NovellBugzilla(Bugzilla):
pass
class RHBugzilla(Bugzilla):
"""
Helper class for historical bugzilla.redhat.com back compat
Historically this class used many more non-upstream methods, but
in 2012 RH started dropping most of its custom bits. By that time,
upstream BZ had most of the important functionality.
Much of the remaining code here is just trying to keep things operating
in python-bugzilla back compatible manner.
This class was written using bugzilla.redhat.com's API docs:
https://bugzilla.redhat.com/docs/en/html/api/
"""
_is_redhat_bugzilla = True
class RHBugzilla3(RHBugzilla):
pass
class RHBugzilla4(RHBugzilla):
pass
0707010000001E000081A400000000000000000000000166EC671500000118000000000000000000000000000000000000004400000000python-bugzilla-3.2.0+git.1726768917.5eedea3/bugzilla/rhbugzilla.py# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
# This class needs to live in rhbugzilla.py to preserve historical
# 'bugzilla.rhbugzilla' import compat
from .oldclasses import RHBugzilla # pylint: disable=unused-import
0707010000001F000081A400000000000000000000000166EC6715000000CF000000000000000000000000000000000000003900000000python-bugzilla-3.2.0+git.1726768917.5eedea3/codecov.yml# These files will only get full coverage from running the functional
# tests, but those aren't run from CI
ignore:
- "bugzilla/_backendrest.py"
- "bugzilla/_backendxmlrpc.py"
- "bugzilla/_session.py"
07070100000020000041ED00000000000000000000000266EC671500000000000000000000000000000000000000000000003600000000python-bugzilla-3.2.0+git.1726768917.5eedea3/examples07070100000021000081A400000000000000000000000166EC6715000003AB000000000000000000000000000000000000004000000000python-bugzilla-3.2.0+git.1726768917.5eedea3/examples/apikey.py#!/usr/bin/env python
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
# apikey.py: Demostrate prompting for API key and passing it to Bugzilla
# pylint: disable=undefined-variable
import bugzilla
# Don't worry, changing things here is fine, and won't send any email to
# users or anything. It's what landfill.bugzilla.org is for!
URL = "https://landfill.bugzilla.org/bugzilla-5.0-branch/xmlrpc.cgi"
print("You can get an API key at:\n "
" https://landfill.bugzilla.org/bugzilla-5.0-branch/userprefs.cgi")
print("This is a test site, so no harm will come!\n")
api_key = input("Enter Bugzilla API Key: ")
# API key usage assumes the API caller is storing the API key; if you would
# like to use one of the login options that stores credentials on-disk for
# command-line usage, use login tokens.
bzapi = bugzilla.Bugzilla(URL, api_key=api_key)
assert bzapi.logged_in
07070100000022000081A400000000000000000000000166EC67150000098F000000000000000000000000000000000000004900000000python-bugzilla-3.2.0+git.1726768917.5eedea3/examples/bug_autorefresh.py#!/usr/bin/env python
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
# bug_autorefresh.py: Show what bug_autorefresh is all about, and explain
# how to handle the default change via python-bugzilla in 2016
import bugzilla
# public test instance of bugzilla.redhat.com. It's okay to make changes
URL = "bugzilla.stage.redhat.com"
bzapi = bugzilla.Bugzilla(URL)
# The Bugzilla.bug_autorefresh setting controls whether bugs will
# automatically go out and try to update their cached contents when code
# tries to access a bug attribute that isn't already cached.
#
# Note this is likely only relevant if some part of your code is using
# include_fields, or exclude_fields, or you are depending on access
# to bugzilla.redhat.com 'extra_fields' type data like 'attachments'
# without explicitly asking the API for them. If you aren't using any
# of those bits, you can ignore this.
#
# Though if you aren't using include_fields and you are running regular
# queries in a script, check examples/query.py for a simple usecase that
# shows how much include_fields usage can speed up your scripts.
# The default as of mid 2016 is bug_autorefresh=off, so set it True here
# to demonstrate
bzapi.bug_autorefresh = True
bug = bzapi.getbug(427301, include_fields=["id", "summary"])
# The limited include_fields here means that only "id" and "summary" fields
# of the bug are cached in the bug object. What happens when we try to
# get component for example?
print("Bug component=%s" % bug.component)
# Because bug_autorefresh is True, the bug object basically did a
# a bug.refresh() for us, grabbed all its data, and now the component field
# is there. Let's try it again, but this time without bug_autorefresh
bzapi.bug_autorefresh = False
bug = bzapi.getbug(427301, include_fields=["id", "summary"])
try:
print("Shouldn't see this! bug component=%s" % bug.component)
except AttributeError:
print("With bug_autorefresh=False, we received AttributeError as expected")
# Why does this matter? Some scripts are implicitly depending on this
# auto-refresh behavior, because their include_fields specification doesn't
# cover all attributes they actually use. Your script will work, sure, but
# it's likely doing many more API calls than needed, possibly 1 per bug.
# So if after upgrading python-bugzilla you start hitting issues, the
# recommendation is to fix your include_fields.
07070100000023000081A400000000000000000000000166EC6715000005CF000000000000000000000000000000000000004000000000python-bugzilla-3.2.0+git.1726768917.5eedea3/examples/create.py#!/usr/bin/env python
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
# create.py: Create a new bug report
import time
import bugzilla
# public test instance of bugzilla.redhat.com.
#
# Don't worry, changing things here is fine, and won't send any email to
# users or anything. It's what bugzilla.stage.redhat.com is for!
URL = "bugzilla.stage.redhat.com"
bzapi = bugzilla.Bugzilla(URL)
if not bzapi.logged_in:
print("This example requires cached login credentials for %s" % URL)
bzapi.interactive_login()
# Similar to build_query, build_createbug is a helper function that handles
# some bugzilla version incompatibility issues. All it does is return a
# properly formatted dict(), and provide friendly parameter names.
# The argument names map to those accepted by Bugzilla Bug.create:
# https://bugzilla.readthedocs.io/en/latest/api/core/v1/bug.html#create-bug
#
# The arguments specified here are mandatory, but there are many other
# optional ones like op_sys, platform, etc. See the docs
createinfo = bzapi.build_createbug(
product="Fedora",
version="rawhide",
component="python-bugzilla",
summary="new example python-bugzilla bug %s" % time.time(),
description="This is comment #0 of an example bug created by "
"the python-bugzilla.git examples/create.py script.")
newbug = bzapi.createbug(createinfo)
print("Created new bug id=%s url=%s" % (newbug.id, newbug.weburl))
07070100000024000081A400000000000000000000000166EC67150000054E000000000000000000000000000000000000004000000000python-bugzilla-3.2.0+git.1726768917.5eedea3/examples/getbug.py#!/usr/bin/env python
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
# getbug.py: Simple demonstration of connecting to bugzilla, fetching
# a bug, and printing some details.
import pprint
import bugzilla
# public test instance of bugzilla.redhat.com. It's okay to make changes
URL = "bugzilla.stage.redhat.com"
bzapi = bugzilla.Bugzilla(URL)
# getbug() is just a simple wrapper around getbugs(), which takes a list
# IDs, if you need to fetch multiple
#
# Example bug: https://bugzilla.stage.redhat.com/show_bug.cgi?id=427301
bug = bzapi.getbug(427301)
print("Fetched bug #%s:" % bug.id)
print(" Product = %s" % bug.product)
print(" Component = %s" % bug.component)
print(" Status = %s" % bug.status)
print(" Resolution= %s" % bug.resolution)
print(" Summary = %s" % bug.summary)
# Just check dir(bug) for other attributes, or check upstream bugzilla
# Bug.get docs for field names:
# https://bugzilla.readthedocs.io/en/latest/api/core/v1/bug.html#get-bug
# comments must be fetched separately on stock bugzilla. this just returns
# a raw dict with all the info.
comments = bug.getcomments()
print("\nLast comment data:\n%s" % pprint.pformat(comments[-1]))
# getcomments is just a wrapper around bzapi.get_comments(), which can be
# used for bulk comments fetching
07070100000025000081A400000000000000000000000166EC671500000418000000000000000000000000000000000000004800000000python-bugzilla-3.2.0+git.1726768917.5eedea3/examples/getbug_restapi.py#!/usr/bin/env python
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
# getbug_restapi.py:
# Simple demonstration of connecting to bugzilla over the REST
# API and printing some bug details.
import bugzilla
# public test instance of bugzilla.redhat.com. It's okay to make changes
URL = "bugzilla.stage.redhat.com"
# By default, if plain Bugzilla(URL) is invoked, the Bugzilla class will
# attempt to determine if XMLRPC or REST API is available, with a preference
# for XMLRPC for back compatability. But you can use the REST API
# with force_rest=True
bzapi = bugzilla.Bugzilla(URL, force_rest=True)
# After that, the bugzilla API can be used as normal. See getbug.py for
# some more info here.
bug = bzapi.getbug(427301)
print("Fetched bug #%s:" % bug.id)
print(" Product = %s" % bug.product)
print(" Component = %s" % bug.component)
print(" Status = %s" % bug.status)
print(" Resolution= %s" % bug.resolution)
print(" Summary = %s" % bug.summary)
07070100000026000081A400000000000000000000000166EC671500000C69000000000000000000000000000000000000003F00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/examples/query.py#!/usr/bin/env python
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
# query.py: Perform a few varieties of queries
import time
import bugzilla
# public test instance of bugzilla.redhat.com. It's okay to make changes
URL = "bugzilla.stage.redhat.com"
bzapi = bugzilla.Bugzilla(URL)
# build_query is a helper function that handles some bugzilla version
# incompatibility issues. All it does is return a properly formatted
# dict(), and provide friendly parameter names. The param names map
# to those accepted by Bugzilla Bug.search:
# https://bugzilla.readthedocs.io/en/latest/api/core/v1/bug.html#search-bugs
query = bzapi.build_query(
product="Fedora",
component="python-bugzilla")
# Since 'query' is just a dict, you could set your own parameters too, like
# if your bugzilla had a custom field. This will set 'status' for example,
# but for common opts it's better to use build_query
query["status"] = "CLOSED"
# query() is what actually performs the query. it's a wrapper around Bug.search
t1 = time.time()
bugs = bzapi.query(query)
t2 = time.time()
print("Found %d bugs with our query" % len(bugs))
print("Query processing time: %s" % (t2 - t1))
# Depending on the size of your query, you can massively speed things up
# by telling bugzilla to only return the fields you care about, since a
# large chunk of the return time is transmitting the extra bug data. You
# tweak this with include_fields:
# https://wiki.mozilla.org/Bugzilla:BzAPI#Field_Control
# Bugzilla will only return those fields listed in include_fields.
query = bzapi.build_query(
product="Fedora",
component="python-bugzilla",
include_fields=["id", "summary"])
t1 = time.time()
bugs = bzapi.query(query)
t2 = time.time()
print("Quicker query processing time: %s" % (t2 - t1))
# bugzilla.redhat.com, and bugzilla >= 5.0 support queries using the same
# format as is used for 'advanced' search URLs via the Web UI. For example,
# I go to bugzilla.stage.redhat.com -> Search -> Advanced Search, select
# Classification=Fedora
# Product=Fedora
# Component=python-bugzilla
# Unselect all bug statuses (so, all status values)
# Under Custom Search
# Creation date -- is less than or equal to -- 2010-01-01
#
# Run that, copy the URL and bring it here, pass it to url_to_query to
# convert it to a dict(), and query as usual
query = bzapi.url_to_query("https://bugzilla.stage.redhat.com/"
"buglist.cgi?classification=Fedora&component=python-bugzilla&"
"f1=creation_ts&o1=lessthaneq&order=Importance&product=Fedora&"
"query_format=advanced&v1=2010-01-01")
query["include_fields"] = ["id", "summary"]
bugs = bzapi.query(query)
print("The URL query returned 22 bugs... "
"I know that without even checking because it shouldn't change!... "
"(count is %d)" % len(bugs))
# One note about querying... you can get subtley different results if
# you are not logged in. Depending on your bugzilla setup it may not matter,
# but if you are dealing with private bugs, check bzapi.logged_in setting
# to ensure your cached credentials are up to date. See update.py for
# an example usage
07070100000027000081A400000000000000000000000166EC6715000007C6000000000000000000000000000000000000004A00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/examples/redhat_query_all.py#!/usr/bin/env python
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
# redhat_query_all.py: Perform a few varieties of queries
import bugzilla
# public test instance of bugzilla.redhat.com. It's okay to make changes
URL = "bugzilla.stage.redhat.com"
bzapi = bugzilla.Bugzilla(URL)
# In late 2021, bugzilla.redhat.com changed query() results to default to
# returning only 20 bugs. If the user passes in limit=0, that number changes
# to 1000, but is still capped if the query would return more than that.
#
# There's a discussion here with multiple proposed ways to work around it:
# https://github.com/python-bugzilla/python-bugzilla/issues/149
#
# This method uses ids_only=True, which is a custom bugzilla.redhat.com
# query feature to bypass the query limit by only returning matching bug IDs.
# rhbz feature bug: https://bugzilla.redhat.com/show_bug.cgi?id=2005153
# As of Feb 2024 this 1300+ bugs, which would have hit the query limit of 1000
query = bzapi.build_query(
product="Fedora",
component="virt-manager")
# Request the bugzilla.redhat.com extension ids_only=True to bypass limit
query["ids_only"] = True
queried_bugs = bzapi.query(query)
ids = [bug.id for bug in queried_bugs]
print(f"Queried {len(ids)} ids")
# Use getbugs to fetch the full list. getbugs is not affected by
# default RHBZ limits. However, requesting too much data via getbugs
# will timeout. This paginates the lookup to query 1000 bugs at a time.
#
# We also limit the returned data to just give us the `summary`.
# You should always limit your queries with include_fields` to only return
# the data you need.
count = 0
pagesize = 1000
include_fields = ["summary"]
while count < len(ids):
idslice = ids[count:(count + pagesize)]
print(f"Fetching data for bugs {count}-{count+len(idslice)-1}")
bugs = bzapi.getbugs(idslice, include_fields=include_fields)
print(f"Fetched {len(bugs)} bugs")
count += pagesize
07070100000028000081A400000000000000000000000166EC6715000008BC000000000000000000000000000000000000004000000000python-bugzilla-3.2.0+git.1726768917.5eedea3/examples/update.py#!/usr/bin/env python
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
# update.py: Make changes to an existing bug
import time
import bugzilla
# public test instance of bugzilla.redhat.com. It's okay to make changes
URL = "bugzilla.stage.redhat.com"
bzapi = bugzilla.Bugzilla(URL)
if not bzapi.logged_in:
print("This example requires cached login credentials for %s" % URL)
bzapi.interactive_login()
# Similar to build_query, build_update is a helper function that handles
# some bugzilla version incompatibility issues. All it does is return a
# properly formatted dict(), and provide friendly parameter names.
# The param names map to those accepted by Bugzilla Bug.update:
# https://bugzilla.readthedocs.io/en/latest/api/core/v1/bug.html#update-bug
#
# Example bug: https://bugzilla.stage.redhat.com/show_bug.cgi?id=427301
# Don't worry, changing things here is fine, and won't send any email to
# users or anything. It's what bugzilla.stage.redhat.com is for!
bug = bzapi.getbug(427301)
print("Bug id=%s original summary=%s" % (bug.id, bug.summary))
update = bzapi.build_update(summary="new example summary %s" % time.time())
bzapi.update_bugs([bug.id], update)
# Call bug.refresh() to update its cached state
bug.refresh()
print("Bug id=%s new summary=%s" % (bug.id, bug.summary))
# Now let's add a comment
comments = bug.getcomments()
print("Bug originally has %d comments" % len(comments))
update = bzapi.build_update(comment="new example comment %s" % time.time())
bzapi.update_bugs([bug.id], update)
# refresh() actually isn't required here because comments are fetched
# on demand
comments = bug.getcomments()
print("Bug now has %d comments. Last comment=%s" % (len(comments),
comments[-1]["text"]))
# The 'bug' object actually has some old convenience APIs for specific
# actions like commenting, and closing. However these aren't recommended:
# they encourage splitting up bug edits when really batching should be done
# as much as possible, not only to make your code quicker and save strain
# on the bugzilla instance, but also to avoid spamming bugzilla users with
# redundant email from two modifications that could have been batched.
07070100000029000041ED00000000000000000000000266EC671500000000000000000000000000000000000000000000003100000000python-bugzilla-3.2.0+git.1726768917.5eedea3/man0707010000002A000081A400000000000000000000000166EC6715000043F0000000000000000000000000000000000000003C00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/man/bugzilla.1.\" Man page generated from reStructuredText.
.
.
.nr rst2man-indent-level 0
.
.de1 rstReportMargin
\\$1 \\n[an-margin]
level \\n[rst2man-indent-level]
level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
-
\\n[rst2man-indent0]
\\n[rst2man-indent1]
\\n[rst2man-indent2]
..
.de1 INDENT
.\" .rstReportMargin pre:
. RS \\$1
. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
. nr rst2man-indent-level +1
.\" .rstReportMargin post:
..
.de UNINDENT
. RE
.\" indent \\n[an-margin]
.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
.nr rst2man-indent-level -1
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "BUGZILLA" 1 "" "" "User Commands"
.SH NAME
bugzilla \- command line tool for interacting with Bugzilla
.SH SYNOPSIS
.sp
\fBbugzilla\fP [\fIoptions\fP] [\fIcommand\fP] [\fIcommand\-options\fP]
.SH DESCRIPTION
.sp
\fBbugzilla\fP is a command line tool for interacting with a Bugzilla
instance over REST or XMLRPC.
.nf
\fIcommand\fP is one of:
* login \- log into the given bugzilla instance
* new \- create a new bug
* query \- search for bugs matching given criteria
* modify \- modify existing bugs
* attach \- attach files to existing bugs, or get attachments
* info \- get info about the given bugzilla instance
.fi
.sp
.SH GLOBAL OPTIONS
.SS \fB\-\-help, \-h\fP
.sp
\fBSyntax:\fP \fB\-h\fP
.sp
show this help message and exit
.SS \fB\-\-bugzilla\fP
.sp
\fBSyntax:\fP \fB\-\-bugzilla\fP BUGZILLA
.sp
The bugzilla URL. Full API URLs are typically like:
.nf
* \fI\%https://bugzilla.example.com/xmlrpc.cgi\fP # XMLRPC API
* \fI\%https://bugzilla.example.com/rest/\fP # REST API
.fi
.sp
.sp
If a non\-specific URL is passed, like \(aqbugzilla.redhat.com\(aq, \fBbugzilla\fP
will try to probe whether the expected XMLRPC or REST path is available,
preferring XMLRPC for backwards compatibility.
.sp
The default URL \fI\%https://bugzilla.redhat.com\fP
.SS \fB\-\-nosslverify\fP
.sp
\fBSyntax:\fP \fB\-\-nosslverify\fP
.sp
Don\(aqt error on invalid bugzilla SSL certificate
.SS \fB\-\-cert\fP
.sp
\fBSyntax:\fP \fB\-\-cert\fP CERTFILE
.sp
client side certificate file needed by the webserver.
.SS \fB\-\-login\fP
.sp
\fBSyntax:\fP \fB\-\-login\fP
.sp
Run interactive \(dqlogin\(dq before performing the specified command.
.SS \fB\-\-username\fP
.sp
\fBSyntax:\fP \fB\-\-username\fP USERNAME
.sp
Log in with this username
.SS \fB\-\-password\fP
.sp
\fBSyntax:\fP \fB\-\-password\fP PASSWORD
.sp
Log in with this password
.SS \fB\-\-restrict\-login\fP
.sp
\fBSyntax:\fP \fB\-\-restrict\-login\fP
.sp
The session (login token) will be restricted to the current IP
address.
.SS \fB\-\-ensure\-logged\-in\fP
.sp
\fBSyntax:\fP \fB\-\-ensure\-logged\-in\fP
.sp
Raise an error if we aren\(aqt logged in to bugzilla. Consider using
this if you are depending on cached credentials, to ensure that when
they expire the tool errors, rather than subtly change output.
.SS \fB\-\-no\-cache\-credentials\fP
.sp
\fBSyntax:\fP \fB\-\-no\-cache\-credentials\fP
.sp
Don\(aqt save any bugzilla tokens to disk, and don\(aqt use any
pre\-existing credentials.
.SS \fB\-\-tokenfile\fP
.sp
\fBSyntax:\fP \fB\-\-tokenfile\fP TOKENFILE
.sp
token file to use for bugzilla authentication
.SS \fB\-\-verbose\fP
.sp
\fBSyntax:\fP \fB\-\-verbose\fP
.sp
give more info about what\(aqs going on
.SS \fB\-\-debug\fP
.sp
\fBSyntax:\fP \fB\-\-debug\fP
.sp
output bunches of debugging info
.SS \fB\-\-version\fP
.sp
\fBSyntax:\fP \fB\-\-version\fP
.sp
show program\(aqs version number and exit
.SH STANDARD BUGZILLA OPTIONS
.sp
These options are shared by some combination of the \(aqnew\(aq, \(aqquery\(aq, and
\(aqmodify\(aq sub commands. Not every option works for each command though.
.SS \fB\-p, \-\-product\fP
.sp
\fBSyntax:\fP \fB\-\-product\fP PRODUCT
.sp
Product name
.SS \fB\-v, \-\-version\fP
.sp
\fBSyntax:\fP \fB\-\-version\fP VERSION
.sp
Product version
.SS \fB\-c, \-\-component\fP
.sp
\fBSyntax:\fP \fB\-\-component\fP COMPONENT
.sp
Component name
.SS \fB\-s, \-\-summary\fP
.sp
\fBSyntax:\fP \fB\-\-summary\fP SUMMARY
.sp
Bug summary
.SS \fB\-l, \-\-comment\fP
.sp
\fBSyntax:\fP \fB\-\-comment\fP DESCRIPTION
.sp
Set initial bug comment/description
.SS \fB\-\-comment\-tag\fP
.sp
\fBSyntax:\fP \fB\-\-comment\-tag\fP TAG
.sp
Comment tag for the new comment
.SS \fB\-\-sub\-component\fP
.sp
\fBSyntax:\fP \fB\-\-sub\-component\fP SUB_COMPONENT
.sp
RHBZ sub component name
.SS \fB\-o, \-\-os\fP
.sp
\fBSyntax:\fP \fB\-\-os\fP OS
.sp
Operating system
.SS \fB\-\-arch\fP
.sp
\fBSyntax:\fP \fB\-\-arch\fP ARCH
.sp
Arch this bug occurs on
.SS \fB\-x, \-\-severity\fP
.sp
\fBSyntax:\fP \fB\-\-severity\fP SEVERITY
.sp
Bug severity
.SS \fB\-z, \-\-priority\fP
.sp
\fBSyntax:\fP \fB\-\-priority\fP PRIORITY
.sp
Bug priority
.SS \fB\-\-alias\fP
.sp
\fBSyntax:\fP \fB\-\-alias\fP ALIAS
.sp
Bug alias (name)
.SS \fB\-s, \-\-status\fP
.sp
\fBSyntax:\fP \fB\-\-status\fP STATUS
.sp
Bug status (NEW, ASSIGNED, etc.)
.SS \fB\-u, \-\-url\fP
.sp
\fBSyntax:\fP \fB\-\-url\fP URL
.sp
URL for further bug info
.SS \fB\-m \-\-target_milestone\fP
.sp
\fBSyntax:\fP \fB\-\-target_milestone\fP TARGET_MILESTONE
.sp
Target milestone
.SS \fB\-\-target_release\fP
.sp
\fBSyntax:\fP \fB\-\-target_release\fP TARGET_RELEASE
.sp
RHBZ Target release
.SS \fB\-\-blocked\fP
.sp
\fBSyntax:\fP \fB\&...]\fP
.sp
Bug IDs that this bug blocks
.SS \fB\-\-dependson\fP
.sp
\fBSyntax:\fP \fB\&...]\fP
.sp
Bug IDs that this bug depends on
.SS \fB\-\-keywords\fP
.sp
\fBSyntax:\fP \fB\&...]\fP
.sp
Bug keywords
.SS \fB\-\-groups\fP
.sp
\fBSyntax:\fP \fB\&...]\fP
.sp
Which user groups can view this bug
.SS \fB\-\-cc\fP
.sp
\fBSyntax:\fP \fB\&...]\fP
.sp
CC list
.SS \fB\-a, \-\-assignee, \-\-assigned_to\fP
.sp
\fBSyntax:\fP \fB\-\-assigned_to\fP ASSIGNED_TO
.sp
Bug assignee
.SS \fB\-q, \-\-qa_contact\fP
.sp
\fBSyntax:\fP \fB\-\-qa_contact\fP QA_CONTACT
.sp
QA contact
.SS \fB\-f, \-\-flag\fP
.sp
\fBSyntax:\fP \fB\-\-flag\fP FLAG
.sp
Set or unset a flag. For example, to set a flag named devel_ack, do
\-\-flag devel_ack+ Unset a flag with the \(aqX\(aq value, like \-\-flag
needinfoX
.SS \fB\-\-tags\fP
.sp
\fBSyntax:\fP \fB\-\-tags\fP TAG
.sp
Set (personal) tags field
.SS \fB\-w, \-\-whiteboard\fP
.sp
\fBSyntax:\fP \fB\-\-whiteboard\fP WHITEBOARD
.sp
Whiteboard field
.SS \fB\-\-devel_whiteboard\fP
.sp
\fBSyntax:\fP \fB\-\-devel_whiteboard\fP DEVEL_WHITEBOARD
.sp
RHBZ devel whiteboard field
.SS \fB\-\-internal_whiteboard\fP
.sp
\fBSyntax:\fP \fB\-\-internal_whiteboard\fP INTERNAL_WHITEBOARD
.sp
RHBZ internal whiteboard field
.SS \fB\-\-qa_whiteboard\fP
.sp
\fBSyntax:\fP \fB\-\-qa_whiteboard\fP QA_WHITEBOARD
.sp
RHBZ QA whiteboard field
.SS \fB\-F, \-\-fixed_in\fP
.sp
\fBSyntax:\fP \fB\-\-fixed_in\fP FIXED_IN
.sp
RHBZ \(aqFixed in version\(aq field
.SS \fB\-\-field\fP
.sp
\fBSyntax:\fP \fB\-\-field\fP FIELD=VALUE
.sp
Manually specify a bugzilla API field. FIELD is the raw name used
by the bugzilla instance. For example if your bugzilla instance has a
custom field cf_my_field, do: \-\-field cf_my_field=VALUE
.SS \fB\-\-field\-json\fP
.sp
\fBSyntax:\fP \fB\-\-field\-json\fP JSONSTRING
.sp
Specify \-\-field data as a JSON string. Example:
\-\-field\-json \(aq{\(dqcf_my_field\(dq: \(dqVALUE\(dq, \(dqcf_array_field\(dq: [1, 2]}\(aq
.SH OUTPUT OPTIONS
.sp
These options are shared by several commands, for tweaking the text
output of the command results.
.SS \fB\-\-full\fP
.sp
\fBSyntax:\fP \fB\-\-full\fP
.sp
output detailed bug info
.SS \fB\-i, \-\-ids\fP
.sp
\fBSyntax:\fP \fB\-\-ids\fP
.sp
output only bug IDs
.SS \fB\-e, \-\-extra\fP
.sp
\fBSyntax:\fP \fB\-\-extra\fP
.sp
output additional bug information (keywords, Whiteboards, etc.)
.SS \fB\-\-oneline\fP
.sp
\fBSyntax:\fP \fB\-\-oneline\fP
.sp
one line summary of the bug (useful for scripts)
.SS \fB\-\-json\fP
.sp
\fBSyntax:\fP \fB\-\-json\fP
.sp
output bug contents in JSON format
.SS \fB\-\-includefield\fP
.sp
\fBSyntax:\fP \fB\-\-includefield\fP
.sp
Pass the field name to bugzilla include_fields list.
Only the fields passed to include_fields are returned
by the bugzilla server.
This can be specified multiple times.
.SS \fB\-\-extrafield\fP
.sp
\fBSyntax:\fP \fB\-\-extrafield\fP
.sp
Pass the field name to bugzilla extra_fields list.
When used with \-\-json this can be used to request
bugzilla to return values for non\-default fields.
This can be specified multiple times.
.SS \fB\-\-excludefield\fP
.sp
\fBSyntax:\fP \fB\-\-excludefield\fP
.sp
Pass the field name to bugzilla exclude_fields list.
When used with \-\-json this can be used to request
bugzilla to not return values for a field.
This can be specified multiple times.
.SS \fB\-\-raw\fP
.sp
\fBSyntax:\fP \fB\-\-raw\fP
.sp
raw output of the bugzilla contents. This format is unstable and
difficult to parse. Please use the \fB\-\-json\fP instead if you want
maximum output from the \fIbugzilla\fP
.SS \fB\-\-outputformat\fP
.sp
\fBSyntax:\fP \fB\-\-outputformat\fP OUTPUTFORMAT
.sp
Print output in the form given. You can use RPM\-style tags that match
bug fields, e.g.: \(aq%{id}: %{summary}\(aq.
.sp
The output of the bugzilla tool should NEVER BE PARSED unless you are
using a custom \-\-outputformat. For everything else, just don\(aqt parse it,
the formats are not stable and are subject to change.
.sp
\-\-outputformat allows printing arbitrary bug data in a user preferred
format. For example, to print a returned bug ID, component, and product,
separated with ::, do:
.sp
\-\-outputformat \(dq%{id}::%{component}::%{product}\(dq
.sp
The fields (like \(aqid\(aq, \(aqcomponent\(aq, etc.) are the names of the values
returned by bugzilla\(aqs API. To see a list of all fields,
check the API documentation in the \(aqSEE ALSO\(aq section. Alternatively,
run a \(aqbugzilla \-\-debug query ...\(aq and look at the key names returned in
the query results. Also, in most cases, using the name of the associated
command line switch should work, like \-\-bug_status becomes
%{bug_status}, etc.
.SH ‘QUERY’ SPECIFIC OPTIONS
.sp
Certain options can accept a comma separated list to query multiple
values, including \-\-status, \-\-component, \-\-product, \-\-version, \-\-id.
.sp
Note: querying via explicit command line options will only get you so
far. See the \-\-from\-url option for a way to use powerful Web UI queries
from the command line.
.SS \fB\-b, \-\-bug_id, \-\-id\fP
.sp
\fBSyntax:\fP \fB\-\-id\fP ID
.sp
specify individual bugs by IDs, separated with commas
.SS \fB\-r, \-\-reporter\fP
.sp
\fBSyntax:\fP \fB\-\-reporter\fP REPORTER
.sp
Email: search reporter email for given address
.SS \fB\-\-quicksearch\fP
.sp
\fBSyntax:\fP \fB\-\-quicksearch\fP QUICKSEARCH
.sp
Search using bugzilla\(aqs quicksearch functionality.
.SS \fB\-\-savedsearch\fP
.sp
\fBSyntax:\fP \fB\-\-savedsearch\fP SAVEDSEARCH
.sp
Name of a bugzilla saved search. If you don\(aqt own this saved search,
you must passed \-\-savedsearch_sharer_id.
.SS \fB\-\-savedsearch\-sharer\-id\fP
.sp
\fBSyntax:\fP \fB\-\-savedsearch\-sharer\-id\fP SAVEDSEARCH_SHARER_ID
.sp
Owner ID of the \-\-savedsearch. You can get this ID from the URL
bugzilla generates when running the saved search from the web UI.
.SS \fB\-\-from\-url\fP
.sp
\fBSyntax:\fP \fB\-\-from\-url\fP WEB_QUERY_URL
.sp
Make a working query via bugzilla\(aqs \(aqAdvanced search\(aq web UI, grab
the url from your browser (the string with query.cgi or buglist.cgi
in it), and \-\-from\-url will run it via the bugzilla API. Don\(aqt forget
to quote the string! This only works for Bugzilla 5 and Red Hat
bugzilla
.SH ‘MODIFY’ SPECIFIC OPTIONS
.sp
Fields that take multiple values have a special input format.
.nf
Append: \fI\%\-\-cc=foo@example.com\fP
Overwrite: \fI\%\-\-cc==foo@example.com\fP
Remove: \fI\%\-\-cc=\-foo@example.com\fP
.fi
.sp
.sp
Options that accept this format: \-\-cc, \-\-blocked, \-\-dependson, \-\-groups,
\-\-tags, whiteboard fields.
.SS \fB\-k, \-\-close RESOLUTION\fP
.sp
\fBSyntax:\fP \fBRESOLUTION\fP
.sp
Close with the given resolution (WONTFIX, NOTABUG, etc.)
.SS \fB\-d, \-\-dupeid\fP
.sp
\fBSyntax:\fP \fB\-\-dupeid\fP ORIGINAL
.sp
ID of original bug. Implies \-\-close DUPLICATE
.SS \fB\-\-private\fP
.sp
\fBSyntax:\fP \fB\-\-private\fP
.sp
Mark new comment as private
.SS \fB\-\-reset\-assignee\fP
.sp
\fBSyntax:\fP \fB\-\-reset\-assignee\fP
.sp
Reset assignee to component default
.SS \fB\-\-reset\-qa\-contact\fP
.sp
\fBSyntax:\fP \fB\-\-reset\-qa\-contact\fP
.sp
Reset QA contact to component default
.SS \fB\-\-minor\-update\fP
.sp
\fBSyntax:\fP \fB\-\-minor\-update\fP
.sp
Request bugzilla to not send any email about this change
.SH ‘NEW’ SPECIFIC OPTIONS
.SS \fB\-\-private\fP
.sp
\fBSyntax:\fP \fB\-\-private\fP
.sp
Mark new comment as private
.SH ‘ATTACH’ OPTIONS
.SS \fB\-f, \-\-file\fP
.sp
\fBSyntax:\fP \fB\-\-file\fP FILENAME
.sp
File to attach, or filename for data provided on stdin
.SS \fB\-d, \-\-description\fP
.sp
\fBSyntax:\fP \fB\-\-description\fP DESCRIPTION
.sp
A short description of the file being attached
.SS \fB\-t, \-\-type\fP
.sp
\fBSyntax:\fP \fB\-\-type\fP MIMETYPE
.sp
Mime\-type for the file being attached
.SS \fB\-g, \-\-get\fP
.sp
\fBSyntax:\fP \fB\-\-get\fP ATTACHID
.sp
Download the attachment with the given ID
.SS \fB\-\-getall\fP
.sp
\fBSyntax:\fP \fB\-\-getall\fP BUGID
.sp
Download all attachments on the given bug
.SS \fB\-\-ignore\-obsolete\fP
.sp
\fBSyntax:\fP \fB\-\-ignore\-obsolete\fP
.sp
Do not download attachments marked as obsolete.
.SS \fB\-l, \-\-comment\fP
.sp
\fBSyntax:\fP \fB\-\-comment\fP COMMENT
.sp
Add comment with attachment
.SH ‘INFO’ OPTIONS
.SS \fB\-p, \-\-products\fP
.sp
\fBSyntax:\fP \fB\-\-products\fP
.sp
Get a list of products
.SS \fB\-c, \-\-components\fP
.sp
\fBSyntax:\fP \fB\-\-components\fP PRODUCT
.sp
List the components in the given product
.SS \fB\-o, \-\-component_owners\fP
.sp
\fBSyntax:\fP \fB\-\-component_owners\fP PRODUCT
.sp
List components (and their owners)
.SS \fB\-v, \-\-versions\fP
.sp
\fBSyntax:\fP \fB\-\-versions\fP PRODUCT
.sp
List the versions for the given product
.SS \fB\-\-active\-components\fP
.sp
\fBSyntax:\fP \fB\-\-active\-components\fP
.sp
Only show active components. Combine with \-\-components*
.SH BUGZILLARC CONFIG FILE
.sp
Both \fBbugzilla\fP and the python\-bugzilla library will read
a \fBbugzillarc\fP config file if it is present in the following
locations:
.INDENT 0.0
.IP \(bu 2
/etc/bugzillarc
.IP \(bu 2
~/.bugzillarc
.IP \(bu 2
~/.config/python\-bugzilla/bugzillarc
.UNINDENT
.sp
The contents of the files are processed and merged together
in the order they are listed above.
.sp
The main usage for \fBbugzillarc\fP is to store API keys for your
bugzilla URLs:
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
[bugzilla.example.com]
api_key=INSERT\-YOUR\-API\-KEY\-HERE
[bugzilla.redhat.com]
api_key=MY\-REDHAT\-API\-KEY\-BLAH
.ft P
.fi
.UNINDENT
.UNINDENT
.sp
The sections must be hostnames. Other values that can be
set per hostname section are
.INDENT 0.0
.IP \(bu 2
\fBuser\fP: default auth username
.IP \(bu 2
\fBpassword\fP: default auth password
.IP \(bu 2
\fBcert\fP: default client side certificate
.UNINDENT
.sp
A \fB[DEFAULTS]\fP section is also accepted, which takes the following
values:
.INDENT 0.0
.IP \(bu 2
\fBurl\fP: default bugzilla URL
.UNINDENT
.SH AUTHENTICATION CACHE AND API KEYS
.sp
Some command usage will require an active login to the bugzilla
instance. For example, if the bugzilla instance has some private bugs,
those bugs will be missing from \(aqquery\(aq output if you do not have an
active login.
.sp
If you are connecting to a bugzilla 5.0 or later instance, the best
option is to use bugzilla API keys. From the bugzilla web UI, log in,
navigate to Preferences\->API Keys, and generate a key (it will be a long
string of characters and numbers).
.sp
Then use \(aqbugzilla \-\-bugzilla URL login \-\-api\-key\(aq, which will ask
for the API key, and save it to \fBbugzillarc\fP for you.
.sp
For older bugzilla instances, you will need to cache a login token
with the \(dqlogin\(dq subcommand or the \(dq\-\-login\(dq argument.
.sp
Additionally, the \-\-no\-cache\-credentials option will tell the bugzilla
tool to \fInot\fP save or use any authentication cache, including the
\fBbugzillarc\fP file.
.SH EXAMPLES
.nf
bugzilla query \-\-bug_id 62037
bugzilla query \-\-version 15 \-\-component python\-bugzilla
bugzilla login
bugzilla new \-p Fedora \-v rawhide \-c python\-bugzilla \e
.in +2
\-\-summary \(dqpython\-bugzilla causes headaches\(dq \e
\-\-comment \(dqpython\-bugzilla made my brain hurt when I used it.\(dq
.in -2
bugzilla attach \-\-file ~/Pictures/cam1.jpg \-\-desc \(dqme, in pain\(dq
$BUGID
bugzilla attach \-\-getall $BUGID
bugzilla modify \-\-close NOTABUG \-\-comment \(dqActually, you\(aqre
hungover.\(dq $BUGID
.fi
.sp
.SH EXIT STATUS
.sp
\fBbugzilla\fP normally returns 0 if the requested command was successful.
Otherwise, exit status is 1 if \fBbugzilla\fP is interrupted by the user
(or a login attempt fails), 2 if a socket error occurs (e.g. TCP
connection timeout), and 3 if the Bugzilla server throws an error.
.SH BUGS
.sp
Please report any bugs as github issues at
\fI\%https://github.com/python\-bugzilla/python\-bugzilla\fP
.SH SEE ALSO
.sp
\fI\%https://bugzilla.readthedocs.io/en/latest/api/index.html\fP
.sp
\fI\%https://bugzilla.redhat.com/docs/en/html/api/core/v1/bug.html\fP
.\" Generated by docutils manpage writer.
.
0707010000002B000081A400000000000000000000000166EC671500003F7A000000000000000000000000000000000000003E00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/man/bugzilla.rst========
bugzilla
========
-----------------------------------------------
command line tool for interacting with Bugzilla
-----------------------------------------------
:Manual section: 1
:Manual group: User Commands
SYNOPSIS
========
**bugzilla** [*options*] [*command*] [*command-options*]
DESCRIPTION
===========
**bugzilla** is a command line tool for interacting with a Bugzilla
instance over REST or XMLRPC.
|
| *command* is one of:
| * login - log into the given bugzilla instance
| * new - create a new bug
| * query - search for bugs matching given criteria
| * modify - modify existing bugs
| * attach - attach files to existing bugs, or get attachments
| * info - get info about the given bugzilla instance
GLOBAL OPTIONS
==============
``--help, -h``
^^^^^^^^^^^^^^
**Syntax:** ``-h``
show this help message and exit
``--bugzilla``
^^^^^^^^^^^^^^
**Syntax:** ``--bugzilla`` BUGZILLA
The bugzilla URL. Full API URLs are typically like:
|
| * https://bugzilla.example.com/xmlrpc.cgi # XMLRPC API
| * https://bugzilla.example.com/rest/ # REST API
|
If a non-specific URL is passed, like 'bugzilla.redhat.com', **bugzilla**
will try to probe whether the expected XMLRPC or REST path is available,
preferring XMLRPC for backwards compatibility.
The default URL https://bugzilla.redhat.com
``--nosslverify``
^^^^^^^^^^^^^^^^^
**Syntax:** ``--nosslverify``
Don't error on invalid bugzilla SSL certificate
``--cert``
^^^^^^^^^^
**Syntax:** ``--cert`` CERTFILE
client side certificate file needed by the webserver.
``--login``
^^^^^^^^^^^
**Syntax:** ``--login``
Run interactive "login" before performing the specified command.
``--username``
^^^^^^^^^^^^^^
**Syntax:** ``--username`` USERNAME
Log in with this username
``--password``
^^^^^^^^^^^^^^
**Syntax:** ``--password`` PASSWORD
Log in with this password
``--restrict-login``
^^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--restrict-login``
The session (login token) will be restricted to the current IP
address.
``--ensure-logged-in``
^^^^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--ensure-logged-in``
Raise an error if we aren't logged in to bugzilla. Consider using
this if you are depending on cached credentials, to ensure that when
they expire the tool errors, rather than subtly change output.
``--no-cache-credentials``
^^^^^^^^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--no-cache-credentials``
Don't save any bugzilla tokens to disk, and don't use any
pre-existing credentials.
``--tokenfile``
^^^^^^^^^^^^^^^
**Syntax:** ``--tokenfile`` TOKENFILE
token file to use for bugzilla authentication
``--verbose``
^^^^^^^^^^^^^
**Syntax:** ``--verbose``
give more info about what's going on
``--debug``
^^^^^^^^^^^
**Syntax:** ``--debug``
output bunches of debugging info
``--version``
^^^^^^^^^^^^^
**Syntax:** ``--version``
show program's version number and exit
Standard bugzilla options
=========================
These options are shared by some combination of the 'new', 'query', and
'modify' sub commands. Not every option works for each command though.
``-p, --product``
^^^^^^^^^^^^^^^^^
**Syntax:** ``--product`` PRODUCT
Product name
``-v, --version``
^^^^^^^^^^^^^^^^^
**Syntax:** ``--version`` VERSION
Product version
``-c, --component``
^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--component`` COMPONENT
Component name
``-s, --summary``
^^^^^^^^^^^^^^^^^
**Syntax:** ``--summary`` SUMMARY
Bug summary
``-l, --comment``
^^^^^^^^^^^^^^^^^
**Syntax:** ``--comment`` DESCRIPTION
Set initial bug comment/description
``--comment-tag``
^^^^^^^^^^^^^^^^^
**Syntax:** ``--comment-tag`` TAG
Comment tag for the new comment
``--sub-component``
^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--sub-component`` SUB_COMPONENT
RHBZ sub component name
``-o, --os``
^^^^^^^^^^^^
**Syntax:** ``--os`` OS
Operating system
``--arch``
^^^^^^^^^^
**Syntax:** ``--arch`` ARCH
Arch this bug occurs on
``-x, --severity``
^^^^^^^^^^^^^^^^^^
**Syntax:** ``--severity`` SEVERITY
Bug severity
``-z, --priority``
^^^^^^^^^^^^^^^^^^
**Syntax:** ``--priority`` PRIORITY
Bug priority
``--alias``
^^^^^^^^^^^
**Syntax:** ``--alias`` ALIAS
Bug alias (name)
``-s, --status``
^^^^^^^^^^^^^^^^
**Syntax:** ``--status`` STATUS
Bug status (NEW, ASSIGNED, etc.)
``-u, --url``
^^^^^^^^^^^^^
**Syntax:** ``--url`` URL
URL for further bug info
``-m --target_milestone``
^^^^^^^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--target_milestone`` TARGET_MILESTONE
Target milestone
``--target_release``
^^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--target_release`` TARGET_RELEASE
RHBZ Target release
``--blocked``
^^^^^^^^^^^^^
**Syntax:** ``...]``
Bug IDs that this bug blocks
``--dependson``
^^^^^^^^^^^^^^^
**Syntax:** ``...]``
Bug IDs that this bug depends on
``--keywords``
^^^^^^^^^^^^^^
**Syntax:** ``...]``
Bug keywords
``--groups``
^^^^^^^^^^^^
**Syntax:** ``...]``
Which user groups can view this bug
``--cc``
^^^^^^^^
**Syntax:** ``...]``
CC list
``-a, --assignee, --assigned_to``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--assigned_to`` ASSIGNED_TO
Bug assignee
``-q, --qa_contact``
^^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--qa_contact`` QA_CONTACT
QA contact
``-f, --flag``
^^^^^^^^^^^^^^
**Syntax:** ``--flag`` FLAG
Set or unset a flag. For example, to set a flag named devel_ack, do
--flag devel_ack+ Unset a flag with the 'X' value, like --flag
needinfoX
``--tags``
^^^^^^^^^^
**Syntax:** ``--tags`` TAG
Set (personal) tags field
``-w, --whiteboard``
^^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--whiteboard`` WHITEBOARD
Whiteboard field
``--devel_whiteboard``
^^^^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--devel_whiteboard`` DEVEL_WHITEBOARD
RHBZ devel whiteboard field
``--internal_whiteboard``
^^^^^^^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--internal_whiteboard`` INTERNAL_WHITEBOARD
RHBZ internal whiteboard field
``--qa_whiteboard``
^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--qa_whiteboard`` QA_WHITEBOARD
RHBZ QA whiteboard field
``-F, --fixed_in``
^^^^^^^^^^^^^^^^^^
**Syntax:** ``--fixed_in`` FIXED_IN
RHBZ 'Fixed in version' field
``--field``
^^^^^^^^^^^
**Syntax:** ``--field`` FIELD=VALUE
Manually specify a bugzilla API field. FIELD is the raw name used
by the bugzilla instance. For example if your bugzilla instance has a
custom field cf_my_field, do: --field cf_my_field=VALUE
``--field-json``
^^^^^^^^^^^^^^^^
**Syntax:** ``--field-json`` JSONSTRING
Specify --field data as a JSON string. Example:
--field-json '{"cf_my_field": "VALUE", "cf_array_field": [1, 2]}'
Output options
==============
These options are shared by several commands, for tweaking the text
output of the command results.
``--full``
^^^^^^^^^^
**Syntax:** ``--full``
output detailed bug info
``-i, --ids``
^^^^^^^^^^^^^
**Syntax:** ``--ids``
output only bug IDs
``-e, --extra``
^^^^^^^^^^^^^^^
**Syntax:** ``--extra``
output additional bug information (keywords, Whiteboards, etc.)
``--oneline``
^^^^^^^^^^^^^
**Syntax:** ``--oneline``
one line summary of the bug (useful for scripts)
``--json``
^^^^^^^^^^
**Syntax:** ``--json``
output bug contents in JSON format
``--includefield``
^^^^^^^^^^^^^^^^^^
**Syntax:** ``--includefield``
Pass the field name to bugzilla include_fields list.
Only the fields passed to include_fields are returned
by the bugzilla server.
This can be specified multiple times.
``--extrafield``
^^^^^^^^^^^^^^^^
**Syntax:** ``--extrafield``
Pass the field name to bugzilla extra_fields list.
When used with --json this can be used to request
bugzilla to return values for non-default fields.
This can be specified multiple times.
``--excludefield``
^^^^^^^^^^^^^^^^^^
**Syntax:** ``--excludefield``
Pass the field name to bugzilla exclude_fields list.
When used with --json this can be used to request
bugzilla to not return values for a field.
This can be specified multiple times.
``--raw``
^^^^^^^^^
**Syntax:** ``--raw``
raw output of the bugzilla contents. This format is unstable and
difficult to parse. Please use the ``--json`` instead if you want
maximum output from the `bugzilla`
``--outputformat``
^^^^^^^^^^^^^^^^^^
**Syntax:** ``--outputformat`` OUTPUTFORMAT
Print output in the form given. You can use RPM-style tags that match
bug fields, e.g.: '%{id}: %{summary}'.
The output of the bugzilla tool should NEVER BE PARSED unless you are
using a custom --outputformat. For everything else, just don't parse it,
the formats are not stable and are subject to change.
--outputformat allows printing arbitrary bug data in a user preferred
format. For example, to print a returned bug ID, component, and product,
separated with ::, do:
--outputformat "%{id}::%{component}::%{product}"
The fields (like 'id', 'component', etc.) are the names of the values
returned by bugzilla's API. To see a list of all fields,
check the API documentation in the 'SEE ALSO' section. Alternatively,
run a 'bugzilla --debug query ...' and look at the key names returned in
the query results. Also, in most cases, using the name of the associated
command line switch should work, like --bug_status becomes
%{bug_status}, etc.
‘query’ specific options
========================
Certain options can accept a comma separated list to query multiple
values, including --status, --component, --product, --version, --id.
Note: querying via explicit command line options will only get you so
far. See the --from-url option for a way to use powerful Web UI queries
from the command line.
``-b, --bug_id, --id``
^^^^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--id`` ID
specify individual bugs by IDs, separated with commas
``-r, --reporter``
^^^^^^^^^^^^^^^^^^
**Syntax:** ``--reporter`` REPORTER
Email: search reporter email for given address
``--quicksearch``
^^^^^^^^^^^^^^^^^
**Syntax:** ``--quicksearch`` QUICKSEARCH
Search using bugzilla's quicksearch functionality.
``--savedsearch``
^^^^^^^^^^^^^^^^^
**Syntax:** ``--savedsearch`` SAVEDSEARCH
Name of a bugzilla saved search. If you don't own this saved search,
you must passed --savedsearch_sharer_id.
``--savedsearch-sharer-id``
^^^^^^^^^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--savedsearch-sharer-id`` SAVEDSEARCH_SHARER_ID
Owner ID of the --savedsearch. You can get this ID from the URL
bugzilla generates when running the saved search from the web UI.
``--from-url``
^^^^^^^^^^^^^^
**Syntax:** ``--from-url`` WEB_QUERY_URL
Make a working query via bugzilla's 'Advanced search' web UI, grab
the url from your browser (the string with query.cgi or buglist.cgi
in it), and --from-url will run it via the bugzilla API. Don't forget
to quote the string! This only works for Bugzilla 5 and Red Hat
bugzilla
‘modify’ specific options
=========================
Fields that take multiple values have a special input format.
| Append: --cc=foo@example.com
| Overwrite: --cc==foo@example.com
| Remove: --cc=-foo@example.com
Options that accept this format: --cc, --blocked, --dependson, --groups,
--tags, whiteboard fields.
``-k, --close RESOLUTION``
^^^^^^^^^^^^^^^^^^^^^^^^^^
**Syntax:** ``RESOLUTION``
Close with the given resolution (WONTFIX, NOTABUG, etc.)
``-d, --dupeid``
^^^^^^^^^^^^^^^^
**Syntax:** ``--dupeid`` ORIGINAL
ID of original bug. Implies --close DUPLICATE
``--private``
^^^^^^^^^^^^^
**Syntax:** ``--private``
Mark new comment as private
``--reset-assignee``
^^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--reset-assignee``
Reset assignee to component default
``--reset-qa-contact``
^^^^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--reset-qa-contact``
Reset QA contact to component default
``--minor-update``
^^^^^^^^^^^^^^^^^^
**Syntax:** ``--minor-update``
Request bugzilla to not send any email about this change
‘new’ specific options
======================
``--private``
^^^^^^^^^^^^^
**Syntax:** ``--private``
Mark new comment as private
‘attach’ options
================
``-f, --file``
^^^^^^^^^^^^^^
**Syntax:** ``--file`` FILENAME
File to attach, or filename for data provided on stdin
``-d, --description``
^^^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--description`` DESCRIPTION
A short description of the file being attached
``-t, --type``
^^^^^^^^^^^^^^
**Syntax:** ``--type`` MIMETYPE
Mime-type for the file being attached
``-g, --get``
^^^^^^^^^^^^^
**Syntax:** ``--get`` ATTACHID
Download the attachment with the given ID
``--getall``
^^^^^^^^^^^^
**Syntax:** ``--getall`` BUGID
Download all attachments on the given bug
``--ignore-obsolete``
^^^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--ignore-obsolete``
Do not download attachments marked as obsolete.
``-l, --comment``
^^^^^^^^^^^^^^^^^
**Syntax:** ``--comment`` COMMENT
Add comment with attachment
‘info’ options
==============
``-p, --products``
^^^^^^^^^^^^^^^^^^
**Syntax:** ``--products``
Get a list of products
``-c, --components``
^^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--components`` PRODUCT
List the components in the given product
``-o, --component_owners``
^^^^^^^^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--component_owners`` PRODUCT
List components (and their owners)
``-v, --versions``
^^^^^^^^^^^^^^^^^^
**Syntax:** ``--versions`` PRODUCT
List the versions for the given product
``--active-components``
^^^^^^^^^^^^^^^^^^^^^^^
**Syntax:** ``--active-components``
Only show active components. Combine with --components*
``bugzillarc`` CONFIG FILE
==========================
Both ``bugzilla`` and the python-bugzilla library will read
a ``bugzillarc`` config file if it is present in the following
locations:
- /etc/bugzillarc
- ~/.bugzillarc
- ~/.config/python-bugzilla/bugzillarc
The contents of the files are processed and merged together
in the order they are listed above.
The main usage for ``bugzillarc`` is to store API keys for your
bugzilla URLs:
::
[bugzilla.example.com]
api_key=INSERT-YOUR-API-KEY-HERE
[bugzilla.redhat.com]
api_key=MY-REDHAT-API-KEY-BLAH
The sections must be hostnames. Other values that can be
set per hostname section are
- ``user``: default auth username
- ``password``: default auth password
- ``cert``: default client side certificate
A ``[DEFAULTS]`` section is also accepted, which takes the following
values:
- ``url``: default bugzilla URL
AUTHENTICATION CACHE AND API KEYS
=================================
Some command usage will require an active login to the bugzilla
instance. For example, if the bugzilla instance has some private bugs,
those bugs will be missing from 'query' output if you do not have an
active login.
If you are connecting to a bugzilla 5.0 or later instance, the best
option is to use bugzilla API keys. From the bugzilla web UI, log in,
navigate to Preferences->API Keys, and generate a key (it will be a long
string of characters and numbers).
Then use 'bugzilla --bugzilla URL login --api-key', which will ask
for the API key, and save it to ``bugzillarc`` for you.
For older bugzilla instances, you will need to cache a login token
with the "login" subcommand or the "--login" argument.
Additionally, the --no-cache-credentials option will tell the bugzilla
tool to *not* save or use any authentication cache, including the
``bugzillarc`` file.
EXAMPLES
========
| bugzilla query --bug_id 62037
|
| bugzilla query --version 15 --component python-bugzilla
|
| bugzilla login
|
| bugzilla new -p Fedora -v rawhide -c python-bugzilla \\
| --summary "python-bugzilla causes headaches" \\
| --comment "python-bugzilla made my brain hurt when I used it."
|
| bugzilla attach --file ~/Pictures/cam1.jpg --desc "me, in pain"
| $BUGID
|
| bugzilla attach --getall $BUGID
|
| bugzilla modify --close NOTABUG --comment "Actually, you're
| hungover." $BUGID
EXIT STATUS
===========
**bugzilla** normally returns 0 if the requested command was successful.
Otherwise, exit status is 1 if **bugzilla** is interrupted by the user
(or a login attempt fails), 2 if a socket error occurs (e.g. TCP
connection timeout), and 3 if the Bugzilla server throws an error.
BUGS
====
Please report any bugs as github issues at
https://github.com/python-bugzilla/python-bugzilla
SEE ALSO
========
https://bugzilla.readthedocs.io/en/latest/api/index.html
https://bugzilla.redhat.com/docs/en/html/api/core/v1/bug.html
0707010000002C000081A400000000000000000000000166EC67150000059E000000000000000000000000000000000000004200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/python-bugzilla.specName: python-bugzilla
Version: 3.3.0
Release: 1%{?dist}
Summary: Python library for interacting with Bugzilla
License: GPLv2+
URL: https://github.com/python-bugzilla/python-bugzilla
Source0: https://github.com/python-bugzilla/python-bugzilla/archive/v%{version}/%{name}-%{version}.tar.gz
BuildArch: noarch
BuildRequires: python3-devel
BuildRequires: python3-requests
BuildRequires: python3-setuptools
BuildRequires: python3-pytest
%global _description\
python-bugzilla is a python library for interacting with bugzilla instances\
over XMLRPC or REST.\
%description %_description
%package -n python3-bugzilla
Summary: %summary
Requires: python3-requests
%{?python_provide:%python_provide python3-bugzilla}
Obsoletes: python-bugzilla < %{version}-%{release}
Obsoletes: python2-bugzilla < %{version}-%{release}
%description -n python3-bugzilla %_description
%package cli
Summary: Command line tool for interacting with Bugzilla
Requires: python3-bugzilla = %{version}-%{release}
%description cli
This package includes the 'bugzilla' command-line tool for interacting with bugzilla. Uses the python-bugzilla API
%prep
%setup -q
%install
%{__python3} setup.py install -O1 --root %{buildroot}
%check
pytest-3
%files -n python3-bugzilla
%doc COPYING README.md NEWS.md
%{python3_sitelib}/*
%files cli
%{_bindir}/bugzilla
%{_mandir}/man1/bugzilla.1.gz
0707010000002D000081A400000000000000000000000166EC671500000009000000000000000000000000000000000000003E00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/requirements.txtrequests
0707010000002E000081ED00000000000000000000000166EC671500001131000000000000000000000000000000000000003600000000python-bugzilla-3.2.0+git.1726768917.5eedea3/setup.py#!/usr/bin/env python3
import glob
import os
import shutil
import subprocess
import sys
import setuptools
def get_version():
f = open("bugzilla/apiversion.py")
for line in f:
if line.startswith('version = '):
return eval(line.split('=')[-1]) # pylint: disable=eval-used
class PylintCommand(setuptools.Command):
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
import pylint.lint
import pycodestyle
files = (["bugzilla-cli", "bugzilla", "setup.py"] +
glob.glob("examples/*.py") +
glob.glob("tests/*.py"))
output_format = sys.stdout.isatty() and "colorized" or "text"
print("running pycodestyle")
style_guide = pycodestyle.StyleGuide(
config_file='tox.ini',
format="pylint",
paths=files,
)
report = style_guide.check_files()
if style_guide.options.count:
sys.stderr.write(str(report.total_errors) + '\n')
print("running pylint")
pylint_opts = [
"--rcfile", ".pylintrc",
"--output-format=%s" % output_format,
]
pylint.lint.Run(files + pylint_opts)
class RPMCommand(setuptools.Command):
description = ("Build src and binary rpms and output them "
"in the source directory")
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
self.run_command('sdist')
srcdir = os.path.dirname(__file__)
cmd = [
"rpmbuild", "-ta",
"--define", "_rpmdir %s" % srcdir,
"--define", "_srcrpmdir %s" % srcdir,
"--define", "_specdir /tmp",
"dist/python-bugzilla-%s.tar.gz" % get_version(),
]
subprocess.check_call(cmd)
class ManCommand(setuptools.Command):
description = ("Regenerate manpages from rst")
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def _make_man_pages(self):
rstbin = shutil.which("rst2man")
if not rstbin:
rstbin = shutil.which("rst2man.py")
if not rstbin:
raise RuntimeError("Didn't find rst2man or rst2man.py")
for path in glob.glob("man/*.rst"):
base = os.path.basename(path)
appname = os.path.splitext(base)[0]
newpath = os.path.join(os.path.dirname(path),
appname + ".1")
print("Generating %s" % newpath)
out = subprocess.check_output([rstbin, path])
open(newpath, "wb").write(out)
self.distribution.data_files.append(
('share/man/man1', (newpath,)))
def run(self):
self._make_man_pages()
def _parse_requirements(fname):
ret = []
for line in open(fname).readlines():
if not line or line.startswith("#"):
continue
ret.append(line)
return ret
setuptools.setup(
name='python-bugzilla',
version=get_version(),
description='Library and command line tool for interacting with Bugzilla',
license="GPLv2",
url='https://github.com/python-bugzilla/python-bugzilla',
classifiers=[
'Topic :: Software Development :: Libraries :: Python Modules',
'Intended Audience :: Developers',
'License :: OSI Approved :: '
'GNU General Public License v2 or later (GPLv2+)',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
],
packages=['bugzilla'],
data_files=[('share/man/man1', ['man/bugzilla.1'])],
entry_points={'console_scripts': ['bugzilla = bugzilla._cli:cli']},
install_requires=_parse_requirements("requirements.txt"),
tests_require=_parse_requirements("test-requirements.txt"),
cmdclass={
"regenerate_manpages": ManCommand,
"pylint": PylintCommand,
"rpm": RPMCommand,
},
)
0707010000002F000081A400000000000000000000000166EC671500000056000000000000000000000000000000000000004300000000python-bugzilla-3.2.0+git.1726768917.5eedea3/test-requirements.txt# additional packages needed for testing
pytest
pylint<3.1
pycodestyle<2.12
responses
07070100000030000041ED00000000000000000000000266EC671500000000000000000000000000000000000000000000003300000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests07070100000031000081A400000000000000000000000166EC671500000171000000000000000000000000000000000000003F00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/__init__.py# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import os
class _CLICONFIG(object):
def __init__(self):
self.REDHAT_URL = None
self.REGENERATE_OUTPUT = False
self.ONLY_REST = False
self.ONLY_XMLRPC = False
CLICONFIG = _CLICONFIG()
os.environ["__BUGZILLA_UNITTEST"] = "1"
07070100000032000081A400000000000000000000000166EC6715000015CC000000000000000000000000000000000000003F00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/conftest.py# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import locale
import logging
import os
import re
import pytest
import responses
import tests
import tests.utils
import bugzilla
# pytest plugin adding custom options. Hooks are documented here:
# https://docs.pytest.org/en/latest/writing_plugins.html
def pytest_addoption(parser):
parser.addoption("--ro-integration", action="store_true", default=False,
help="Run readonly tests against local Bugzilla instance.")
parser.addoption("--ro-functional", action="store_true", default=False,
help=("Run readonly functional tests against actual "
"bugzilla instances. This will be very slow."))
parser.addoption("--rw-functional", action="store_true", default=False,
help=("Run read/write functional tests against actual bugzilla "
"instances. As of now this only runs against "
"bugzilla.stage.redhat.com, which requires an RH "
"bugzilla account with cached login creds. This will "
"also be very slow."))
parser.addoption("--redhat-url",
help="Redhat bugzilla URL to use for ro/rw_functional tests")
parser.addoption("--pybz-debug", action="store_true", default=False,
help=("Enable python-bugzilla debug output. This may break "
"output comparison tests."))
parser.addoption("--regenerate-output",
action="store_true", default=False,
help=("Force regeneration of generated test output"))
parser.addoption("--only-rest", action="store_true", default=False)
parser.addoption("--only-xmlrpc", action="store_true", default=False)
def pytest_ignore_collect(collection_path, config):
has_ro = config.getoption("--ro-functional")
has_ro_i = config.getoption("--ro-integration")
has_rw = config.getoption("--rw-functional")
base = os.path.basename(str(collection_path))
is_ro = base == "test_ro_functional.py"
is_ro_i = "tests/integration/ro" in str(collection_path)
is_rw = base == "test_rw_functional.py"
if is_ro_i and not has_ro_i:
return True
if is_ro and not has_ro:
return True
if is_rw and not has_rw:
return True
def pytest_configure(config):
# Needed for test reproducibility on any system not using a UTF-8 locale
locale.setlocale(locale.LC_ALL, "C")
for loc in ["C.UTF-8", "C.utf8", "UTF-8", "en_US.UTF-8"]:
try:
locale.setlocale(locale.LC_CTYPE, loc)
break
except locale.Error:
pass
else:
raise locale.Error("No UTF-8 locale found")
if config.getoption("--redhat-url"):
tests.CLICONFIG.REDHAT_URL = config.getoption("--redhat-url")
if config.getoption("--pybz-debug"):
logging.getLogger(bugzilla.__name__).setLevel(logging.DEBUG)
os.environ["__BUGZILLA_UNITTEST_DEBUG"] = "1"
if config.getoption("--regenerate-output"):
tests.CLICONFIG.REGENERATE_OUTPUT = config.getoption(
"--regenerate-output")
if config.getoption("--only-rest"):
tests.CLICONFIG.ONLY_REST = True
if config.getoption("--only-xmlrpc"):
tests.CLICONFIG.ONLY_XMLRPC = True
if (config.getoption("--ro-functional") or
config.getoption("--rw-functional")):
config.option.verbose = 2
def pytest_generate_tests(metafunc):
"""
If a test requests the 'backends' fixture, run that test with both
force_rest=True and force_xmlrpc=True Bugzilla options
"""
if 'backends' in metafunc.fixturenames:
values = []
ids = []
if not tests.CLICONFIG.ONLY_REST:
values.append({"force_xmlrpc": True})
ids.append("XMLRPC")
if not tests.CLICONFIG.ONLY_XMLRPC:
values.append({"force_rest": True})
ids.append("REST")
metafunc.parametrize("backends", values, ids=ids, scope="session")
@pytest.fixture
def run_cli(capsys, monkeypatch):
"""
Custom pytest fixture to pass a function for testing
a bugzilla cli command.
"""
def _do_run(*args, **kwargs):
return tests.utils.do_run_cli(capsys, monkeypatch, *args, **kwargs)
return _do_run
@pytest.fixture
def mocked_responses():
"""
Mock responses
* Quickly return error responses
* Pass through requests to live instances
* Provide an incorrect XMLRPC response
"""
passthrough = ()
status_pattern = re.compile(r"https://httpstat.us/(?P<status>\d+).*")
def status_callback(request):
match = status_pattern.match(request.url)
status_code = 400
if match:
status_code = int(match.group("status"))
return status_code, {}, "<html><body><h1>Lorem ipsum</h1></body></html>"
test_url = os.getenv("BUGZILLA_URL")
if test_url:
passthrough += (test_url, )
with responses.RequestsMock(passthru_prefixes=passthrough,
assert_all_requests_are_fired=False) as mock:
mock.add_callback(
method=responses.GET,
url=status_pattern,
callback=status_callback
)
mock.add_callback(
method=responses.POST,
url=status_pattern,
callback=status_callback
)
mock.add(
method=responses.POST,
url="https://example.com/#xmlrpc",
status=200,
body="This is no XML"
)
yield mock
07070100000033000041ED00000000000000000000000266EC671500000000000000000000000000000000000000000000003800000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data07070100000034000041ED00000000000000000000000266EC671500000000000000000000000000000000000000000000004200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/authfiles07070100000035000081A400000000000000000000000166EC671500000026000000000000000000000000000000000000005800000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/authfiles/output-bugzillarc.txt[example.com]
api_key = TEST-API-KEY
07070100000036000081A400000000000000000000000166EC671500000025000000000000000000000000000000000000005300000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/authfiles/output-token.txt[example.com]
token = MY-FAKE-TOKEN
07070100000037000081A400000000000000000000000166EC67150000056E000000000000000000000000000000000000004B00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/bz-attach-get1.txt--- base.py.old 2010-12-16 12:15:09.932010659 +0100
+++ base.py 2010-12-16 16:04:18.995185933 +0100
@@ -19,6 +19,8 @@
import tempfile
import logging
import locale
+import email.header
+import re
log = logging.getLogger('bugzilla')
@@ -677,10 +679,17 @@
# RFC 2183 defines the content-disposition header, if you're curious
disp = att.headers['content-disposition'].split(';')
[filename_parm] = [i for i in disp if i.strip().startswith('filename=')]
- (dummy,filename) = filename_parm.split('=')
- # RFC 2045/822 defines the grammar for the filename value, but
- # I think we just need to remove the quoting. I hope.
- att.name = filename.strip('"')
+ (dummy,filename) = filename_parm.split('=',1)
+ # RFC 2045/822 defines the grammar for the filename value
+ filename = filename.strip('"')
+ # email.header.decode_header cannot handle strings not ending with '?=',
+ # so let's transform one =?...?= part at a time
+ while True:
+ match = re.search("=\?.*?\?=", filename)
+ if match is None:
+ break
+ filename = filename[:match.start()] + email.header.decode_header(match.group(0))[0][0] + filename[match.end():]
+ att.name = filename
# Hooray, now we have a file-like object with .read() and .name
return att
07070100000038000041ED00000000000000000000000266EC671500000000000000000000000000000000000000000000004200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput07070100000039000081A400000000000000000000000166EC671500000021000000000000000000000000000000000000006200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_info_components-active.txtbackend/kernel
client-interfaces
0707010000003A000081A400000000000000000000000166EC67150000003E000000000000000000000000000000000000006200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_info_components-owners.txtclient-interfaces: Fake Guy
configuration: ANother fake dude!
0707010000003B000081A400000000000000000000000166EC671500000021000000000000000000000000000000000000005B00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_info_components.txtbackend/kernel
client-interfaces
0707010000003C000081A400000000000000000000000166EC67150000001E000000000000000000000000000000000000005900000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_info_products.txtProd 1 Test
test-fake-product
0707010000003D000081A400000000000000000000000166EC671500000010000000000000000000000000000000000000005900000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_info_versions.txt7.1
fooversion!
0707010000003E000081A400000000000000000000000166EC671500000025000000000000000000000000000000000000006B00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_interactive_login_apikey_rcfile.txt[example.com]
api_key = MY-FAKE-KEY
0707010000003F000081A400000000000000000000000166EC6715000007B6000000000000000000000000000000000000005700000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_json_xmlrpc.txt{
"bugs": [
{
"binarytest": "LS0tIGJhc2UucHkub2xkCTIwMTAtMTItMTYgMTI6MTU6MDkuOTMyMDEwNjU5ICswMTAwCisrKyBiYXNlLnB5CTIwMTAtMTItMTYgMTY6MDQ6MTguOTk1MTg1OTMzICswMTAwCkBAIC0xOSw2ICsxOSw4IEBACiBpbXBvcnQgdGVtcGZpbGUKIGltcG9ydCBsb2dnaW5nCiBpbXBvcnQgbG9jYWxlCitpbXBvcnQgZW1haWwuaGVhZGVyCitpbXBvcnQgcmUKIAogbG9nID0gbG9nZ2luZy5nZXRMb2dnZXIoJ2J1Z3ppbGxhJykKIApAQCAtNjc3LDEwICs2NzksMTcgQEAKICAgICAgICAgIyBSRkMgMjE4MyBkZWZpbmVzIHRoZSBjb250ZW50LWRpc3Bvc2l0aW9uIGhlYWRlciwgaWYgeW91J3JlIGN1cmlvdXMKICAgICAgICAgZGlzcCA9IGF0dC5oZWFkZXJzWydjb250ZW50LWRpc3Bvc2l0aW9uJ10uc3BsaXQoJzsnKQogICAgICAgICBbZmlsZW5hbWVfcGFybV0gPSBbaSBmb3IgaSBpbiBkaXNwIGlmIGkuc3RyaXAoKS5zdGFydHN3aXRoKCdmaWxlbmFtZT0nKV0KLSAgICAgICAgKGR1bW15LGZpbGVuYW1lKSA9IGZpbGVuYW1lX3Bhcm0uc3BsaXQoJz0nKQotICAgICAgICAjIFJGQyAyMDQ1LzgyMiBkZWZpbmVzIHRoZSBncmFtbWFyIGZvciB0aGUgZmlsZW5hbWUgdmFsdWUsIGJ1dAotICAgICAgICAjIEkgdGhpbmsgd2UganVzdCBuZWVkIHRvIHJlbW92ZSB0aGUgcXVvdGluZy4gSSBob3BlLgotICAgICAgICBhdHQubmFtZSA9IGZpbGVuYW1lLnN0cmlwKCciJykKKyAgICAgICAgKGR1bW15LGZpbGVuYW1lKSA9IGZpbGVuYW1lX3Bhcm0uc3BsaXQoJz0nLDEpCisgICAgICAgICMgUkZDIDIwNDUvODIyIGRlZmluZXMgdGhlIGdyYW1tYXIgZm9yIHRoZSBmaWxlbmFtZSB2YWx1ZQorICAgICAgICBmaWxlbmFtZSA9IGZpbGVuYW1lLnN0cmlwKCciJykKKyAgICAgICAgIyBlbWFpbC5oZWFkZXIuZGVjb2RlX2hlYWRlciBjYW5ub3QgaGFuZGxlIHN0cmluZ3Mgbm90IGVuZGluZyB3aXRoICc/PScsCisgICAgICAgICMgc28gbGV0J3MgdHJhbnNmb3JtIG9uZSA9Py4uLj89IHBhcnQgYXQgYSB0aW1lCisgICAgICAgIHdoaWxlIFRydWU6CisgICAgICAgICAgICBtYXRjaCA9IHJlLnNlYXJjaCgiPVw/Lio/XD89IiwgZmlsZW5hbWUpCisgICAgICAgICAgICBpZiBtYXRjaCBpcyBOb25lOgorICAgICAgICAgICAgICAgIGJyZWFrCisgICAgICAgICAgICBmaWxlbmFtZSA9IGZpbGVuYW1lWzptYXRjaC5zdGFydCgpXSArIGVtYWlsLmhlYWRlci5kZWNvZGVfaGVhZGVyKG1hdGNoLmdyb3VwKDApKVswXVswXSArIGZpbGVuYW1lW21hdGNoLmVuZCgpOl0KKyAgICAgICAgYXR0Lm5hbWUgPSBmaWxlbmFtZQogICAgICAgICAjIEhvb3JheSwgbm93IHdlIGhhdmUgYSBmaWxlLWxpa2Ugb2JqZWN0IHdpdGggLnJlYWQoKSBhbmQgLm5hbWUKICAgICAgICAgcmV0dXJuIGF0dAogCg==",
"id": 1165434,
"timetest": "2018-12-09T19:12:12Z"
}
]
}
07070100000040000081A400000000000000000000000166EC67150000005A000000000000000000000000000000000000005000000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_new1.txt#1694158 CLOSED - crobinso@redhat.com - python-bugzilla test bug for API minor_update
07070100000041000081A400000000000000000000000166EC67150000005A000000000000000000000000000000000000005000000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_new2.txt#1694158 CLOSED - crobinso@redhat.com - python-bugzilla test bug for API minor_update
07070100000042000081A400000000000000000000000166EC67150000000E000000000000000000000000000000000000005600000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_query1-ids.txt508645
668543
07070100000043000081A400000000000000000000000166EC6715000000D9000000000000000000000000000000000000005700000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_query1-rhbz.txt#508645 NEW - Libvirt Maintainers - RFE: qemu: Support a managed autoconnect mode for host USB devices
#668543 NEW - Cole Robinson - RFE: warn users at guest start if networks/storage pools are inactive
07070100000044000081A400000000000000000000000166EC6715000000D9000000000000000000000000000000000000005200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_query1.txt#508645 NEW - Libvirt Maintainers - RFE: qemu: Support a managed autoconnect mode for host USB devices
#668543 NEW - Cole Robinson - RFE: warn users at guest start if networks/storage pools are inactive
07070100000045000081A400000000000000000000000166EC67150000006C000000000000000000000000000000000000005300000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_query10.txt#1165434 CLOSED - lvm-team@redhat.com - LVM mirrored root can deadlock dmeventd if a mirror leg is lost
07070100000046000081A400000000000000000000000166EC6715000000D9000000000000000000000000000000000000005700000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_query2-rhbz.txt#508645 NEW - Libvirt Maintainers - RFE: qemu: Support a managed autoconnect mode for host USB devices
#668543 NEW - Cole Robinson - RFE: warn users at guest start if networks/storage pools are inactive
07070100000047000081A400000000000000000000000166EC671500000864000000000000000000000000000000000000005200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_query2.txtBugzilla 1165434:
ATTRIBUTE[actual_time]: 0.0
ATTRIBUTE[alias]: []
ATTRIBUTE[assigned_to]: lvm-team@redhat.com
ATTRIBUTE[assigned_to_detail]: 'DICT SCRUBBED'
ATTRIBUTE[autorefresh]: False
ATTRIBUTE[blocks]: [123456]
ATTRIBUTE[cc]: ['example@redhat.com', 'example2@redhat.com']
ATTRIBUTE[cc_detail]: ['DICT SCRUBBED']
ATTRIBUTE[cf_build_id]:
ATTRIBUTE[cf_conditional_nak]: []
ATTRIBUTE[cf_cust_facing]: ---
ATTRIBUTE[cf_devel_whiteboard]: somedeveltag,someothertag
ATTRIBUTE[cf_doc_type]: Bug Fix
ATTRIBUTE[cf_environment]:
ATTRIBUTE[cf_fixed_in]:
ATTRIBUTE[cf_internal_whiteboard]: someinternal TAG
ATTRIBUTE[cf_last_closed]: 2016-03-03T22:15:07
ATTRIBUTE[cf_partner]: []
ATTRIBUTE[cf_pgm_internal]:
ATTRIBUTE[cf_pm_score]: 0
ATTRIBUTE[cf_qa_whiteboard]: foo bar baz
ATTRIBUTE[cf_qe_conditional_nak]: []
ATTRIBUTE[cf_release_notes]:
ATTRIBUTE[cf_target_upstream_version]:
ATTRIBUTE[cf_verified]: []
ATTRIBUTE[classification]: Red Hat
ATTRIBUTE[comments]: ['DICT SCRUBBED']
ATTRIBUTE[depends_on]: [112233]
ATTRIBUTE[docs_contact]:
ATTRIBUTE[estimated_time]: 0.0
ATTRIBUTE[external_bugs]: ['DICT SCRUBBED']
ATTRIBUTE[flags]: ['DICT SCRUBBED']
ATTRIBUTE[groups]: ['somegroup']
ATTRIBUTE[id]: 1165434
ATTRIBUTE[is_cc_accessible]: True
ATTRIBUTE[is_confirmed]: True
ATTRIBUTE[is_creator_accessible]: True
ATTRIBUTE[is_open]: False
ATTRIBUTE[keywords]: ['key1', 'keyword2', 'Security']
ATTRIBUTE[last_change_time]: 2018-12-09T19:12:12
ATTRIBUTE[op_sys]: Linux
ATTRIBUTE[platform]: All
ATTRIBUTE[priority]: medium
ATTRIBUTE[product]: Red Hat Enterprise Linux 5
ATTRIBUTE[qa_contact]: mspqa-list@redhat.com
ATTRIBUTE[qa_contact_detail]: 'DICT SCRUBBED'
ATTRIBUTE[remaining_time]: 0.0
ATTRIBUTE[resolution]: WONTFIX
ATTRIBUTE[see_also]: []
ATTRIBUTE[severity]: medium
ATTRIBUTE[status]: CLOSED
ATTRIBUTE[sub_components]: 'DICT SCRUBBED'
ATTRIBUTE[summary]: LVM mirrored root can deadlock dmeventd if a mirror leg is lost
ATTRIBUTE[tags]: []
ATTRIBUTE[target_milestone]: rc
ATTRIBUTE[target_release]: ['---']
ATTRIBUTE[url]:
ATTRIBUTE[version]: ['5.8']
ATTRIBUTE[weburl]: https:///show_bug.cgi?id=1165434
ATTRIBUTE[whiteboard]: genericwhiteboard
07070100000048000081A400000000000000000000000166EC67150000035C000000000000000000000000000000000000005200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_query3.txt:::genericwhiteboard:qe_test_coverage?,release?,pm_ack?,devel_ack?,qa_ack+,needinfo?:hello@example.com::?::
* 2014-11-19T00:26:50 - example@redhat.com:
Description of problem:
Version-Release number of selected component (if applicable):
kernel-2.6.18-308.el5
device-mapper-multipath-0.4.7-48.el5
device-mapper-1.02.67-2.el5
device-mapper-1.02.67-2.el5
device-mapper-event-1.02.67-2.el5
* 2014-11-19T00:47:57 - example@redhat.com:
We can see that there is a dmeventd task that has sent data over a socket and is waiting for the peer to respond:
crash> bt
any interaction with the filesystem until it has issued the suspend command to convert the mirror device to a linear device.
* 2014-11-19T01:53:53 - example@redhat.com:
Test text
::
External bug: https://bugzilla.gnome.org/show_bug.cgi?id=703421
External bug: https://bugs.launchpad.net/bugs/1203576
07070100000049000081A400000000000000000000000166EC67150000033C000000000000000000000000000000000000005200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_query4.txt#1165434 CLOSED - lvm-team@redhat.com - LVM mirrored root can deadlock dmeventd if a mirror leg is lost
Component:
CC: example@redhat.com,example2@redhat.com
Blocked: 123456
Depends: 112233
* 2014-11-19T00:26:50 - example@redhat.com:
Description of problem:
Version-Release number of selected component (if applicable):
kernel-2.6.18-308.el5
device-mapper-multipath-0.4.7-48.el5
device-mapper-1.02.67-2.el5
device-mapper-1.02.67-2.el5
device-mapper-event-1.02.67-2.el5
* 2014-11-19T00:47:57 - example@redhat.com:
We can see that there is a dmeventd task that has sent data over a socket and is waiting for the peer to respond:
crash> bt
any interaction with the filesystem until it has issued the suspend command to convert the mirror device to a linear device.
* 2014-11-19T01:53:53 - example@redhat.com:
Test text
0707010000004A000081A400000000000000000000000166EC6715000000DE000000000000000000000000000000000000005200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_query5.txt#1165434 CLOSED - lvm-team@redhat.com - LVM mirrored root can deadlock dmeventd if a mirror leg is lost
+Keywords: key1,keyword2,Security
+QA Whiteboard:
+Status Whiteboard: genericwhiteboard
+Devel Whiteboard:
0707010000004B000081A400000000000000000000000166EC671500000078000000000000000000000000000000000000005200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_query6.txt#1165434 CLOSED lvm-team@redhat.com [rc] qe_test_coverage?,release?,pm_ack?,devel_ack?,qa_ack+,needinfo? CVE-1234-5678
0707010000004C000081A400000000000000000000000166EC67150000006C000000000000000000000000000000000000005200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_query7.txt#1165434 CLOSED - lvm-team@redhat.com - LVM mirrored root can deadlock dmeventd if a mirror leg is lost
0707010000004D000081A400000000000000000000000166EC671500001CCE000000000000000000000000000000000000005200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_query8.txt{
"bugs": [
{
"actual_time": 0.0,
"alias": [],
"assigned_to": "lvm-team@redhat.com",
"assigned_to_detail": {
"email": "lvm-team@redhat.com",
"id": 206817,
"name": "lvm-team@redhat.com",
"real_name": "LVM and device-mapper development team"
},
"blocks": [
123456
],
"cc": [
"example@redhat.com",
"example2@redhat.com"
],
"cc_detail": [
{
"email": "example@redhat.com",
"id": 123456,
"name": "example@redhat.com",
"real_name": "Example user"
},
{
"email": "example2@redhat.com",
"id": 123457,
"name": "heinzm@redhat.com",
"real_name": "Example2 user"
}
],
"cf_build_id": "",
"cf_conditional_nak": [],
"cf_cust_facing": "---",
"cf_devel_whiteboard": "somedeveltag,someothertag",
"cf_doc_type": "Bug Fix",
"cf_environment": "",
"cf_fixed_in": "",
"cf_internal_whiteboard": "someinternal TAG",
"cf_last_closed": "2016-03-03T22:15:07",
"cf_partner": [],
"cf_pgm_internal": "",
"cf_pm_score": "0",
"cf_qa_whiteboard": "foo bar baz",
"cf_qe_conditional_nak": [],
"cf_release_notes": "",
"cf_target_upstream_version": "",
"cf_verified": [],
"classification": "Red Hat",
"comments": [
{
"bug_id": 1165434,
"count": 0,
"creation_time": "2014-11-19T00:26:50",
"creator": "example@redhat.com",
"creator_id": 276776,
"id": 7685441,
"is_private": false,
"tags": [],
"text": "Description of problem:\nVersion-Release number of selected component (if applicable):\nkernel-2.6.18-308.el5\ndevice-mapper-multipath-0.4.7-48.el5\ndevice-mapper-1.02.67-2.el5\ndevice-mapper-1.02.67-2.el5\ndevice-mapper-event-1.02.67-2.el5\n",
"time": "2014-11-19T00:26:50"
},
{
"bug_id": 1165434,
"count": 1,
"creation_time": "2014-11-19T00:47:57",
"creator": "example@redhat.com",
"creator_id": 276776,
"id": 7685467,
"is_private": false,
"tags": [],
"text": "We can see that there is a dmeventd task that has sent data over a socket and is waiting for the peer to respond:\n\ncrash> bt\nany interaction with the filesystem until it has issued the suspend command to convert the mirror device to a linear device.",
"time": "2014-11-19T00:47:57"
},
{
"bug_id": 1165434,
"count": 2,
"creation_time": "2014-11-19T01:53:53",
"creator": "example@redhat.com",
"creator_id": 156796,
"id": 7685595,
"is_private": false,
"tags": [],
"text": "Test text",
"time": "2014-11-19T01:53:53"
}
],
"depends_on": [
112233
],
"docs_contact": "",
"estimated_time": 0.0,
"external_bugs": [
{
"bug_id": 989253,
"ext_bz_bug_id": "703421",
"ext_bz_id": 3,
"ext_description": "None",
"ext_priority": "None",
"ext_status": "None",
"id": 115528,
"type": {
"can_get": true,
"can_send": false,
"description": "GNOME Bugzilla",
"full_url": "https://bugzilla.gnome.org/show_bug.cgi?id=%id%",
"id": 3,
"must_send": false,
"send_once": false,
"type": "Bugzilla",
"url": "https://bugzilla.gnome.org"
}
},
{
"bug_id": 989253,
"ext_bz_bug_id": "1203576",
"ext_bz_id": 29,
"ext_description": "None",
"ext_priority": "None",
"ext_status": "None",
"id": 115527,
"type": {
"can_get": false,
"can_send": false,
"description": "Launchpad",
"full_url": "https://bugs.launchpad.net/bugs/%id%",
"id": 29,
"must_send": false,
"send_once": false,
"type": "None",
"url": "https://bugs.launchpad.net/bugs"
}
}
],
"flags": [
{
"creation_date": "2019-11-15T21:57:21Z",
"id": 4302313,
"is_active": 1,
"modification_date": "2019-11-15T21:57:21Z",
"name": "qe_test_coverage",
"setter": "pm-rhel@redhat.com",
"status": "?",
"type_id": 318
},
{
"creation_date": "2018-12-25T16:47:43Z",
"id": 3883137,
"is_active": 1,
"modification_date": "2018-12-25T16:47:43Z",
"name": "release",
"setter": "rule-engine@redhat.com",
"status": "?",
"type_id": 1197
},
{
"creation_date": "2018-12-25T16:47:38Z",
"id": 3883134,
"is_active": 1,
"modification_date": "2018-12-25T16:47:38Z",
"name": "pm_ack",
"setter": "example3@redhat.com",
"status": "?",
"type_id": 11
},
{
"creation_date": "2018-12-25T16:47:38Z",
"id": 3883135,
"is_active": 1,
"modification_date": "2018-12-25T16:47:38Z",
"name": "devel_ack",
"setter": "example2@redhat.com",
"status": "?",
"type_id": 10
},
{
"creation_date": "2018-12-25T16:47:38Z",
"id": 3883136,
"is_active": 1,
"modification_date": "2019-04-28T02:07:03Z",
"name": "qa_ack",
"setter": "example@redhat.com",
"status": "+",
"type_id": 9
},
{
"creation_date": "2019-03-29T06:50:01Z",
"id": 3999302,
"is_active": 1,
"modification_date": "2019-03-29T06:50:01Z",
"name": "needinfo",
"requestee": "hello@example.com",
"setter": "example@redhat.com",
"status": "?",
"type_id": 1164
}
],
"groups": [
"somegroup"
],
"id": 1165434,
"is_cc_accessible": true,
"is_confirmed": true,
"is_creator_accessible": true,
"is_open": false,
"keywords": [
"key1",
"keyword2",
"Security"
],
"last_change_time": "2018-12-09T19:12:12",
"op_sys": "Linux",
"platform": "All",
"priority": "medium",
"product": "Red Hat Enterprise Linux 5",
"qa_contact": "mspqa-list@redhat.com",
"qa_contact_detail": {
"email": "mspqa-list@redhat.com",
"id": 164197,
"name": "mspqa-list@redhat.com",
"real_name": "Cluster QE"
},
"remaining_time": 0.0,
"resolution": "WONTFIX",
"see_also": [],
"severity": "medium",
"status": "CLOSED",
"sub_components": {
"lvm2": [
"dmeventd (RHEL5)"
]
},
"summary": "LVM mirrored root can deadlock dmeventd if a mirror leg is lost",
"tags": [],
"target_milestone": "rc",
"target_release": [
"---"
],
"url": "",
"version": [
"5.8"
],
"whiteboard": "genericwhiteboard"
}
]
}
0707010000004E000081A400000000000000000000000166EC671500001D06000000000000000000000000000000000000005200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/test_query9.txt{
"bugs": [
{
"actual_time": 0.0,
"alias": [],
"assigned_to": "lvm-team@redhat.com",
"assigned_to_detail": {
"email": "lvm-team@redhat.com",
"id": 206817,
"name": "lvm-team@redhat.com",
"real_name": "LVM and device-mapper development team"
},
"blocks": [
123456
],
"cc": [
"example@redhat.com",
"example2@redhat.com"
],
"cc_detail": [
{
"email": "example@redhat.com",
"id": 123456,
"name": "example@redhat.com",
"real_name": "Example user"
},
{
"email": "example2@redhat.com",
"id": 123457,
"name": "heinzm@redhat.com",
"real_name": "Example2 user"
}
],
"cf_build_id": "",
"cf_conditional_nak": [],
"cf_cust_facing": "---",
"cf_doc_type": "Bug Fix",
"cf_environment": "",
"cf_last_closed": "2016-03-03T22:15:07",
"cf_partner": [],
"cf_pgm_internal": "",
"cf_pm_score": "0",
"cf_qe_conditional_nak": [],
"cf_release_notes": "",
"cf_target_upstream_version": "",
"cf_verified": [],
"classification": "Red Hat",
"comments": [
{
"bug_id": 1165434,
"count": 0,
"creation_time": "2014-11-19T00:26:50",
"creator": "example@redhat.com",
"creator_id": 276776,
"id": 7685441,
"is_private": false,
"tags": [],
"text": "Description of problem:\nVersion-Release number of selected component (if applicable):\nkernel-2.6.18-308.el5\ndevice-mapper-multipath-0.4.7-48.el5\ndevice-mapper-1.02.67-2.el5\ndevice-mapper-1.02.67-2.el5\ndevice-mapper-event-1.02.67-2.el5\n",
"time": "2014-11-19T00:26:50"
},
{
"bug_id": 1165434,
"count": 1,
"creation_time": "2014-11-19T00:47:57",
"creator": "example@redhat.com",
"creator_id": 276776,
"id": 7685467,
"is_private": false,
"tags": [],
"text": "We can see that there is a dmeventd task that has sent data over a socket and is waiting for the peer to respond:\n\ncrash> bt\nany interaction with the filesystem until it has issued the suspend command to convert the mirror device to a linear device.",
"time": "2014-11-19T00:47:57"
},
{
"bug_id": 1165434,
"count": 2,
"creation_time": "2014-11-19T01:53:53",
"creator": "example@redhat.com",
"creator_id": 156796,
"id": 7685595,
"is_private": false,
"tags": [],
"text": "Test text",
"time": "2014-11-19T01:53:53"
}
],
"depends_on": [
112233
],
"devel_whiteboard": "somedeveltag,someothertag",
"docs_contact": "",
"estimated_time": 0.0,
"external_bugs": [
{
"bug_id": 989253,
"ext_bz_bug_id": "703421",
"ext_bz_id": 3,
"ext_description": "None",
"ext_priority": "None",
"ext_status": "None",
"id": 115528,
"type": {
"can_get": true,
"can_send": false,
"description": "GNOME Bugzilla",
"full_url": "https://bugzilla.gnome.org/show_bug.cgi?id=%id%",
"id": 3,
"must_send": false,
"send_once": false,
"type": "Bugzilla",
"url": "https://bugzilla.gnome.org"
}
},
{
"bug_id": 989253,
"ext_bz_bug_id": "1203576",
"ext_bz_id": 29,
"ext_description": "None",
"ext_priority": "None",
"ext_status": "None",
"id": 115527,
"type": {
"can_get": false,
"can_send": false,
"description": "Launchpad",
"full_url": "https://bugs.launchpad.net/bugs/%id%",
"id": 29,
"must_send": false,
"send_once": false,
"type": "None",
"url": "https://bugs.launchpad.net/bugs"
}
}
],
"fixed_in": "",
"flags": [
{
"creation_date": "2019-11-15T21:57:21Z",
"id": 4302313,
"is_active": 1,
"modification_date": "2019-11-15T21:57:21Z",
"name": "qe_test_coverage",
"setter": "pm-rhel@redhat.com",
"status": "?",
"type_id": 318
},
{
"creation_date": "2018-12-25T16:47:43Z",
"id": 3883137,
"is_active": 1,
"modification_date": "2018-12-25T16:47:43Z",
"name": "release",
"setter": "rule-engine@redhat.com",
"status": "?",
"type_id": 1197
},
{
"creation_date": "2018-12-25T16:47:38Z",
"id": 3883134,
"is_active": 1,
"modification_date": "2018-12-25T16:47:38Z",
"name": "pm_ack",
"setter": "example3@redhat.com",
"status": "?",
"type_id": 11
},
{
"creation_date": "2018-12-25T16:47:38Z",
"id": 3883135,
"is_active": 1,
"modification_date": "2018-12-25T16:47:38Z",
"name": "devel_ack",
"setter": "example2@redhat.com",
"status": "?",
"type_id": 10
},
{
"creation_date": "2018-12-25T16:47:38Z",
"id": 3883136,
"is_active": 1,
"modification_date": "2019-04-28T02:07:03Z",
"name": "qa_ack",
"setter": "example@redhat.com",
"status": "+",
"type_id": 9
},
{
"creation_date": "2019-03-29T06:50:01Z",
"id": 3999302,
"is_active": 1,
"modification_date": "2019-03-29T06:50:01Z",
"name": "needinfo",
"requestee": "hello@example.com",
"setter": "example@redhat.com",
"status": "?",
"type_id": 1164
}
],
"groups": [
"somegroup"
],
"id": 1165434,
"internal_whiteboard": "someinternal TAG",
"is_cc_accessible": true,
"is_confirmed": true,
"is_creator_accessible": true,
"is_open": false,
"keywords": [
"key1",
"keyword2",
"Security"
],
"last_change_time": "2018-12-09T19:12:12",
"op_sys": "Linux",
"platform": "All",
"priority": "medium",
"product": "Red Hat Enterprise Linux 5",
"qa_contact": "mspqa-list@redhat.com",
"qa_contact_detail": {
"email": "mspqa-list@redhat.com",
"id": 164197,
"name": "mspqa-list@redhat.com",
"real_name": "Cluster QE"
},
"qa_whiteboard": "foo bar baz",
"remaining_time": 0.0,
"resolution": "WONTFIX",
"see_also": [],
"severity": "medium",
"status": "CLOSED",
"sub_component": "dmeventd (RHEL5)",
"sub_components": {
"lvm2": [
"dmeventd (RHEL5)"
]
},
"summary": "LVM mirrored root can deadlock dmeventd if a mirror leg is lost",
"tags": [],
"target_milestone": "rc",
"target_release": [
"---"
],
"url": "",
"version": "5.8",
"versions": [
"5.8"
],
"whiteboard": "genericwhiteboard"
}
]
}
0707010000004F000081A400000000000000000000000166EC671500000025000000000000000000000000000000000000005000000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/clioutput/tokenfile.txt[example.com]
token = my-fake-token
07070100000050000081A400000000000000000000000166EC67150000000C000000000000000000000000000000000000004C00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/components_file.txtfoo
bar
baz
07070100000051000041ED00000000000000000000000266EC671500000000000000000000000000000000000000000000004100000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs07070100000052000081A400000000000000000000000166EC67150000009B000000000000000000000000000000000000006200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_api_attachments_create1.txt([123456],
'STRIPPED-BY-TESTSUITE',
{'content_type': 'text/plain',
'file_name': 'bz-attach-get1.txt',
'is_private': True,
'summary': 'some desc'})
07070100000053000081A400000000000000000000000166EC6715000000A1000000000000000000000000000000000000006000000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_api_component_create1.txt{'default_assignee': 'foo@example.com',
'default_cc': 'foo3@example.com',
'default_qa_contact': 'foo2@example.com',
'is_active': 0,
'product': 'fooproduct'}
07070100000054000081A400000000000000000000000166EC6715000000B9000000000000000000000000000000000000006000000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_api_component_update1.txt{'names': [{'component': 'foocomponent', 'product': 'fooproduct'}],
'updates': {'blaharg': 'blahval',
'default_assignee': 'foo@example.com',
'is_active': 0}}
07070100000055000081A400000000000000000000000166EC671500000033000000000000000000000000000000000000005700000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_api_getbugs1.txt([], ['CVE-1234-5678'], {'exclude_fields': 'foo'})
07070100000056000081A400000000000000000000000166EC671500000033000000000000000000000000000000000000005700000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_api_getbugs2.txt(['123456'], ['CVE-1234-FAKE'], {'permissive': 1})
07070100000057000081A400000000000000000000000166EC67150000002F000000000000000000000000000000000000005A00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_api_groups_get1.txt{'membership': False, 'names': ['TestGroups']}
07070100000058000081A400000000000000000000000166EC67150000002D000000000000000000000000000000000000005A00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_api_groups_get2.txt{'membership': True, 'names': ['TestGroup']}
07070100000059000081A400000000000000000000000166EC671500000022000000000000000000000000000000000000005500000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_api_login1.txt{'login': None, 'password': None}
0707010000005A000081A400000000000000000000000166EC671500000024000000000000000000000000000000000000005500000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_api_login2.txt{'login': 'FOO', 'password': 'BAR'}
0707010000005B000081A400000000000000000000000166EC671500000010000000000000000000000000000000000000005C00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_api_products_get1.txt{'ids': [1, 7]}
0707010000005C000081A400000000000000000000000166EC671500000057000000000000000000000000000000000000005C00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_api_products_get2.txt{'include_fields': ['name', 'id', 'components.name'],
'names': ['test-fake-product']}
0707010000005D000081A400000000000000000000000166EC671500000051000000000000000000000000000000000000005C00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_api_products_get3.txt{'include_fields': ['name', 'id', 'components'], 'names': ['test-fake-product']}
0707010000005E000081A400000000000000000000000166EC671500000032000000000000000000000000000000000000005C00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_api_products_get4.txt{'exclude_fields': ['product.foo'], 'ids': ['7']}
0707010000005F000081A400000000000000000000000166EC671500000044000000000000000000000000000000000000005C00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_api_products_get5.txt{'include_fields': ['name', 'id', 'components.name'], 'names': [0]}
07070100000060000081A400000000000000000000000166EC67150000004C000000000000000000000000000000000000005B00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_api_users_create.txt{'email': 'example1@example.com', 'name': 'fooname', 'password': 'foopass'}
07070100000061000081A400000000000000000000000166EC671500000024000000000000000000000000000000000000005900000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_api_users_get1.txt{'names': ['example2@example.com']}
07070100000062000081A400000000000000000000000166EC671500000024000000000000000000000000000000000000005900000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_api_users_get2.txt{'names': ['example1@example.com']}
07070100000063000081A400000000000000000000000166EC671500000024000000000000000000000000000000000000005900000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_api_users_get3.txt{'match': ['example1@example.com']}
07070100000064000081A400000000000000000000000166EC671500000046000000000000000000000000000000000000005C00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_api_users_update1.txt{'groups': {'remove': ['fedora_contrib']}, 'names': ['example name']}
07070100000065000081A400000000000000000000000166EC6715000000E7000000000000000000000000000000000000005200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_attach1.txt(['123456'],
'STRIPPED-BY-TESTSUITE',
{'comment': 'some comment to go with it',
'content_type': 'text/x-patch',
'file_name': 'bz-attach-get1.txt',
'is_patch': True,
'is_private': True,
'summary': 'bz-attach-get1.txt'})
07070100000066000081A400000000000000000000000166EC671500000099000000000000000000000000000000000000005200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_attach2.txt(['123456'],
'STRIPPED-BY-TESTSUITE',
{'content_type': 'text/plain',
'file_name': 'fake-file-name.txt',
'summary': 'Some attachment description'})
07070100000067000081A400000000000000000000000166EC671500000114000000000000000000000000000000000000005200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_attach3.txt(['123456'],
'STRIPPED-BY-TESTSUITE',
{'content_type': 'text/plain',
'file_name': 'bz-attach-get1.txt',
'flags': [{'name': 'review',
'requestee': 'crobinso@redhat.com',
'status': '-'}],
'is_obsolete': '1',
'summary': 'bz-attach-get1.txt'})
07070100000068000081A400000000000000000000000166EC671500000011000000000000000000000000000000000000005600000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_attach_get1.txt(['112233'], {})
07070100000069000081A400000000000000000000000166EC671500000011000000000000000000000000000000000000005600000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_attach_get2.txt(['663674'], {})
0707010000006A000081A400000000000000000000000166EC67150000000D000000000000000000000000000000000000005B00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_attachments_get1.txt(502352, {})
0707010000006B000081A400000000000000000000000166EC671500000043000000000000000000000000000000000000005E00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_attachments_getall1.txt([123456], {'exclude_fields': ['bar'], 'include_fields': ['foo']})
0707010000006C000081A400000000000000000000000166EC671500000054000000000000000000000000000000000000005E00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_attachments_update1.txt([112233],
{'flags': [{'is_patch': True, 'name': 'needinfo', 'value': 'foobar'}]})
0707010000006D000081A400000000000000000000000166EC671500000010000000000000000000000000000000000000005B00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_bug_api_comments.txt([1165434], {})
0707010000006E000081A400000000000000000000000166EC67150000000F000000000000000000000000000000000000006200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_bug_api_get_attachments.txt([663674], {})
0707010000006F000081A400000000000000000000000166EC671500000010000000000000000000000000000000000000005A00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_bug_api_history.txt([1165434], {})
07070100000070000081A400000000000000000000000166EC67150000005A000000000000000000000000000000000000006000000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_bug_apis_addcc_update.txt([1165434],
{'cc': {'add': ['foo2@example.com']}, 'comment': {'comment': 'foocomment'}})
07070100000071000081A400000000000000000000000166EC67150000004A000000000000000000000000000000000000006500000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_bug_apis_addcomment_update.txt([1165434], {'comment': {'comment': 'test comment', 'is_private': True}})
07070100000072000081A400000000000000000000000166EC67150000009B000000000000000000000000000000000000006000000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_bug_apis_close_update.txt([1165434],
{'cf_fixed_in': '1.2.3.4.5',
'comment': {'comment': 'foocomment2'},
'dupe_of': 123456,
'resolution': 'UPSTREAM',
'status': 'CLOSED'})
07070100000073000081A400000000000000000000000166EC67150000005D000000000000000000000000000000000000006300000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_bug_apis_deletecc_update.txt([1165434],
{'cc': {'remove': ['foo2@example.com']}, 'comment': {'comment': 'foocomment'}})
07070100000074000081A400000000000000000000000166EC67150000007C000000000000000000000000000000000000006600000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_bug_apis_setassignee_update.txt([1165434],
{'assigned_to': 'foo@example.com',
'comment': {'comment': 'foocomment'},
'qa_contact': 'bar@example.com'})
07070100000075000081A400000000000000000000000166EC67150000005B000000000000000000000000000000000000006400000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_bug_apis_setstatus_update.txt([1165434],
{'comment': {'comment': 'foocomment', 'is_private': True}, 'status': 'POST'})
07070100000076000081A400000000000000000000000166EC671500000044000000000000000000000000000000000000006600000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_bug_apis_updateflags_update.txt([1165434], {'flags': [{'name': 'someflag', 'status': 'someval'}]})
07070100000077000081A400000000000000000000000166EC671500000036000000000000000000000000000000000000005500000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_bug_fields.txt{'include_fields': ['name'], 'names': ['bug_status']}
07070100000078000081A400000000000000000000000166EC6715000001A6000000000000000000000000000000000000005B00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_externalbugs_add.txt{'bug_ids': [1234, 5678],
'external_bugs': [{'ext_bz_bug_id': 'externalid',
'ext_description': 'link to launchpad',
'ext_priority': 'bigly',
'ext_status': 'CLOSED',
'ext_type_description': 'some-bug-add-description',
'ext_type_id': 'launchpad',
'ext_type_url': 'https://example.com/launchpad/1234'}]}
07070100000079000081A400000000000000000000000166EC6715000000A4000000000000000000000000000000000000005E00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_externalbugs_remove.txt{'bug_ids': ['blah'],
'ext_bz_bug_id': ['99999'],
'ext_type_description': 'foo-desc',
'ext_type_id': 'footype',
'ext_type_url': 'foo-url',
'ids': ['remove1']}
0707010000007A000081A400000000000000000000000166EC67150000014D000000000000000000000000000000000000005E00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_externalbugs_update.txt{'bug_ids': ['some', 'bug', 'id'],
'ext_bz_bug_id': ['externalid-update'],
'ext_description': 'link to mozilla',
'ext_priority': 'like, really bigly',
'ext_status': 'OPEN',
'ext_type_description': 'some-bug-update',
'ext_type_id': 'mozilla',
'ext_type_url': 'https://mozilla.foo/bar/5678',
'ids': ['external1', 'external2']}
0707010000007B000081A400000000000000000000000166EC671500000172000000000000000000000000000000000000005800000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_getbug_query9.txt([1165434],
[],
{'exclude_fields': ['excludeme'],
'extra_fields': ['extrame1',
'extrame2',
'comments',
'description',
'external_bugs',
'flags',
'sub_components',
'tags'],
'include_fields': ['foo', 'bar', 'id'],
'permissive': 1})
0707010000007C000081A400000000000000000000000166EC67150000006F000000000000000000000000000000000000006100000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_info_components-active.txt{'include_fields': ['name', 'id', 'components.name', 'components.is_active'],
'names': ['test-fake-product']}
0707010000007D000081A400000000000000000000000166EC6715000000B5000000000000000000000000000000000000006100000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_info_components-owners.txt{'include_fields': ['name',
'id',
'components.name',
'components.default_assigned_to'],
'names': ['test-fake-product']}
0707010000007E000081A400000000000000000000000166EC671500000057000000000000000000000000000000000000005A00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_info_components.txt{'include_fields': ['name', 'id', 'components.name'],
'names': ['test-fake-product']}
0707010000007F000081A400000000000000000000000166EC671500000032000000000000000000000000000000000000005800000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_info_products.txt{'ids': [1, 7], 'include_fields': ['name', 'id']}
07070100000080000081A400000000000000000000000166EC67150000004F000000000000000000000000000000000000005800000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_info_versions.txt{'include_fields': ['name', 'id', 'versions'], 'names': ['test-fake-product']}
07070100000081000081A400000000000000000000000166EC67150000002E000000000000000000000000000000000000005C00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_interactive_login.txt{'login': 'fakeuser', 'password': 'fakepass'}
07070100000082000081A400000000000000000000000166EC67150000003C000000000000000000000000000000000000005900000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_login-restrict.txt{'login': 'FOO', 'password': 'BAR', 'restrict_login': True}
07070100000083000081A400000000000000000000000166EC671500000024000000000000000000000000000000000000005000000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_login.txt{'login': 'FOO', 'password': 'BAR'}
07070100000084000081A400000000000000000000000166EC671500000048000000000000000000000000000000000000005200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_modify1.txt(['123456', '1234567'], {'component': 'NEWCOMP', 'status': 'ASSIGNED'})
07070100000085000081A400000000000000000000000166EC67150000017E000000000000000000000000000000000000005200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_modify2.txt(['123456'],
{'blocks': {'set': ['123456', '445566']},
'comment': {'comment': 'some example comment', 'is_private': True},
'component': 'NEWCOMP',
'dupe_of': 555666,
'flags': [{'name': '-needinfo,+somethingels', 'status': 'e'}],
'groups': {'remove': ['BAR']},
'keywords': {'add': ['FOO']},
'resolution': 'DUPLICATE',
'status': 'CLOSED',
'whiteboard': 'thisone'})
07070100000086000081A400000000000000000000000166EC671500000042000000000000000000000000000000000000005700000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_modify3-tags.txt(['1165434'], {'tags': {'add': ['addtag'], 'remove': ['rmtag']}})
07070100000087000081A400000000000000000000000166EC6715000000B5000000000000000000000000000000000000005200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_modify3.txt([1165434],
{'cf_devel_whiteboard': 'somedeveltag,someothertag devel-duh',
'cf_internal_whiteboard': 'someinternal TAG internal-hey bar',
'cf_qa_whiteboard': 'bar baz yo-qa'})
07070100000088000081A400000000000000000000000166EC67150000007B000000000000000000000000000000000000005200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_modify4.txt(['1165434'],
{'cf_fixed_in': 'foofixedin',
'component': 'lvm2',
'sub_components': {'lvm2': ['some-sub-component']}})
07070100000089000081A400000000000000000000000166EC6715000003C3000000000000000000000000000000000000005200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_modify5.txt(['1165434'],
{'alias': 'fooalias',
'assigned_to': 'foo@example.com',
'bar': 'foo',
'blocks': {'add': ['1234'], 'remove': ['1235'], 'set': []},
'cc': {'add': ['+bar@example.com'], 'remove': ['steve@example.com']},
'cf_blah': {'1': 2},
'cf_devel_whiteboard': 'DEVBOARD',
'cf_internal_whiteboard': 'INTBOARD',
'cf_qa_whiteboard': 'QABOARD',
'cf_verified': ['Tested'],
'comment_tags': ['FOOTAG'],
'depends_on': {'add': ['2234'], 'remove': ['2235'], 'set': []},
'groups': {'add': ['foogroup']},
'keywords': {'add': ['newkeyword'], 'remove': ['byekeyword'], 'set': []},
'minor_update': True,
'op_sys': 'windows',
'platform': 'mips',
'priority': 'high',
'product': 'newproduct',
'qa_contact': 'qa@example.com',
'reset_assigned_to': True,
'reset_qa_contact': True,
'severity': 'low',
'summary': 'newsummary',
'target_milestone': 'beta',
'target_release': '1.2.4',
'url': 'https://example.com',
'version': '1.2.3'})
0707010000008A000081A400000000000000000000000166EC671500000160000000000000000000000000000000000000004F00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_new1.txt{'blocks': ['12345', '6789'],
'cc': ['foo@example.com', 'bar@example.com'],
'comment_is_private': True,
'component': 'FOOCOMP',
'depends_on': ['dependme'],
'description': 'This is the first comment!\nWith newline & stuff.',
'groups': ['FOOGROUP', 'BARGROUP'],
'keywords': ['ADDKEY'],
'product': 'FOOPROD',
'summary': 'Hey this is the title!'}
0707010000008B000081A400000000000000000000000166EC6715000002CE000000000000000000000000000000000000004F00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_new2.txt{'alias': 'somealias',
'assigned_to': 'foo@example.com',
'blocks': ['12345', '6789'],
'cc': ['foo@example.com', 'bar@example.com'],
'cf_blah': {'1': 2},
'cf_verified': ['Tested'],
'comment_is_private': True,
'comment_tags': ['FOO'],
'component': 'FOOCOMP',
'depends_on': ['dependme'],
'description': 'This is the first comment!\nWith newline & stuff.',
'foo': 'bar',
'groups': ['FOOGROUP', 'BARGROUP'],
'keywords': ['ADDKEY'],
'op_sys': 'linux',
'platform': 'mips',
'priority': 'low',
'product': 'FOOPROD',
'qa_contact': 'qa@example.com',
'severity': 'high',
'sub_components': {'FOOCOMP': ['FOOCOMP']},
'summary': 'Hey this is the title!',
'url': 'https://some.example.com',
'version': '5.6.7'}
0707010000008C000081A400000000000000000000000166EC671500000067000000000000000000000000000000000000005500000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_query1-ids.txt{'component': ['foo', 'bar'],
'id': ['1234', '2480'],
'include_fields': ['id'],
'product': ['foo']}
0707010000008D000081A400000000000000000000000166EC67150000023D000000000000000000000000000000000000005600000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_query1-rhbz.txt{'cc': ['foo@example.com'],
'component': ['foo', 'bar'],
'field0-0-0': 'keywords',
'field1-0-0': 'cf_fixed_in',
'field2-0-0': 'cf_qa_whiteboard',
'id': ['1234', '2480'],
'include_fields': ['assigned_to', 'id', 'status', 'summary'],
'longdesc': 'some comment string',
'longdesc_type': 'allwordssubstr',
'product': ['foo'],
'qa_contact': 'qa@example.com',
'query_format': 'advanced',
'type0-0-0': 'substring',
'type1-0-0': 'substring',
'type2-0-0': 'substring',
'value0-0-0': 'fribkeyword',
'value1-0-0': 'amifixed',
'value2-0-0': 'some-example-whiteboard'}
0707010000008E000081A400000000000000000000000166EC67150000008B000000000000000000000000000000000000005100000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_query1.txt{'component': ['foo', 'bar'],
'id': ['1234', '2480'],
'include_fields': ['assigned_to', 'id', 'status', 'summary'],
'product': ['foo']}
0707010000008F000081A400000000000000000000000166EC67150000044B000000000000000000000000000000000000005200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_query10.txt{'alias': 'somealias',
'assigned_to': 'bar@example.com',
'field0-0-0': 'keywords',
'field1-0-0': 'blocked',
'field2-0-0': 'dependson',
'field3-0-0': 'bug_file_loc',
'field4-0-0': 'cf_fixed_in',
'field5-0-0': 'flagtypes.name',
'field6-0-0': 'status_whiteboard',
'field7-0-0': 'cf_devel_whiteboard',
'include_fields': ['assigned_to', 'id', 'status', 'summary'],
'priority': ['wibble'],
'query_format': 'advanced',
'quicksearch': '1',
'reporter': 'me@example.com',
'savedsearch': '2',
'sharer_id': '3',
'short_desc': 'search summary',
'sub_components': ['FOOCOMP'],
'tag': ['+foo'],
'target_milestone': 'bar',
'target_release': 'foo',
'type0-0-0': 'substring',
'type1-0-0': 'substring',
'type2-0-0': 'substring',
'type3-0-0': 'sometype',
'type4-0-0': 'substring',
'type5-0-0': 'substring',
'type6-0-0': 'substring',
'type7-0-0': 'substring',
'value0-0-0': 'FOO',
'value1-0-0': '12345',
'value2-0-0': '23456',
'value3-0-0': 'https://example.com',
'value4-0-0': '5.5.5',
'value5-0-0': 'needinfo',
'value6-0-0': 'FOO',
'value7-0-0': 'DEVBOARD',
'version': ['5.6.7']}
07070100000090000081A400000000000000000000000166EC6715000000A5000000000000000000000000000000000000005600000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_query2-rhbz.txt{'email1': ['foo@example.com'],
'emailcc1': True,
'emailtype1': 'BAR',
'include_fields': ['assigned_to', 'id', 'status', 'summary'],
'query_format': 'advanced'}
07070100000091000081A400000000000000000000000166EC67150000002E000000000000000000000000000000000000005100000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_query2.txt{'id': ['1165434'], 'include_fields': ['id']}
07070100000092000081A400000000000000000000000166EC671500000147000000000000000000000000000000000000005100000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_query3.txt{'bug_severity': ['sev1', 'sev2'],
'include_fields': ['bar',
'comments',
'devel_whiteboard',
'external_bugs',
'flags',
'flags_requestee',
'foo',
'whiteboard',
'id']}
07070100000093000081A400000000000000000000000166EC6715000001D0000000000000000000000000000000000000005100000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_query4.txt{'bug_status': ['NEW',
'ASSIGNED',
'NEEDINFO',
'ON_DEV',
'MODIFIED',
'POST',
'REOPENED'],
'include_fields': ['assigned_to',
'blocks',
'cc',
'comments',
'component',
'depends_on',
'id',
'status',
'summary']}
07070100000094000081A400000000000000000000000166EC67150000016E000000000000000000000000000000000000005100000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_query5.txt{'bug_status': ['ASSIGNED', 'ON_QA', 'FAILS_QA', 'PASSES_QA'],
'component': ['foo', 'bar', 'baz'],
'include_fields': ['assigned_to',
'devel_whiteboard',
'id',
'keywords',
'qa_whiteboard',
'status',
'summary',
'whiteboard']}
07070100000095000081A400000000000000000000000166EC67150000018B000000000000000000000000000000000000005100000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_query6.txt{'BAR': 'WIBBLE',
'FOO': '1',
'bug_status': ['VERIFIED', 'RELEASE_PENDING', 'CLOSED'],
'cf_blah': {'1': 2},
'cf_verified': ['Tested'],
'include_fields': ['assigned_to',
'blocks',
'component',
'flags',
'keywords',
'status',
'target_milestone',
'id']}
07070100000096000081A400000000000000000000000166EC6715000001C2000000000000000000000000000000000000005100000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_query7.txt{'bug_status': ['NEW',
'ASSIGNED',
'MODIFIED',
'ON_DEV',
'ON_QA',
'VERIFIED',
'FAILS_QA',
'RELEASE_PENDING',
'POST'],
'classification': 'Fedora',
'component': 'virt-manager',
'include_fields': ['assigned_to', 'id', 'status', 'summary'],
'order': 'bug_status,bug_id',
'product': 'Fedora',
'query_format': 'advanced'}
07070100000097000081A400000000000000000000000166EC67150000002E000000000000000000000000000000000000005100000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_query8.txt{'id': ['1165434'], 'include_fields': ['id']}
07070100000098000081A400000000000000000000000166EC67150000002E000000000000000000000000000000000000005100000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_query9.txt{'id': ['1165434'], 'include_fields': ['id']}
07070100000099000081A400000000000000000000000166EC671500000013000000000000000000000000000000000000005B00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_query_cve_getbug.txt([123456], [], {})
0707010000009A000081A400000000000000000000000166EC671500000040000000000000000000000000000000000000005700000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockargs/test_update_flags.txt([12345, 6789], {'flags': {'name': 'needinfo', 'status': '?'}})
0707010000009B000041ED00000000000000000000000266EC671500000000000000000000000000000000000000000000004300000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockreturn0707010000009C000081A400000000000000000000000166EC671500000362000000000000000000000000000000000000005800000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockreturn/test_attach_get1.txt{'attachments': {'502352': {'bug_id': 663674,
'content_type': 'text/plain',
'creation_time': '2011-06-01T18:57:50Z',
'creator': 'example',
'data': 'SG9vcmF5IGZvciBNZXRlb3JvbG9naWNrw6kgenByw6F2eSA1XzA0LnBkZiEK',
'file_name': 'Klíč memorial test file.txt',
'flags': [],
'id': 502352,
'is_obsolete': 0,
'is_patch': 0,
'is_private': 0,
'last_change_time': '2011-06-01T18:57:50Z',
'size': 45,
'summary': 'An empty test file with a utf-8 '
'filename'}},
'bugs': {}}
0707010000009D000081A400000000000000000000000166EC671500001647000000000000000000000000000000000000005800000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockreturn/test_attach_get2.txt{'attachments': {},
'bugs': {'663674': [{'bug_id': 663674,
'content_type': 'application/octet-stream',
'creation_time': '2010-12-16T15:28:01Z',
'creator': 'example',
'data': 'LS0tIGJhc2UucHkub2xkCTIwMTAtMTItMTYgMTI6MTU6MDkuOTMyMDEwNjU5ICswMTAwCisrKyBiYXNlLnB5CTIwMTAtMTItMTYgMTY6MDQ6MTguOTk1MTg1OTMzICswMTAwCkBAIC0xOSw2ICsxOSw4IEBACiBpbXBvcnQgdGVtcGZpbGUKIGltcG9ydCBsb2dnaW5nCiBpbXBvcnQgbG9jYWxlCitpbXBvcnQgZW1haWwuaGVhZGVyCitpbXBvcnQgcmUKIAogbG9nID0gbG9nZ2luZy5nZXRMb2dnZXIoJ2J1Z3ppbGxhJykKIApAQCAtNjc3LDEwICs2NzksMTcgQEAKICAgICAgICAgIyBSRkMgMjE4MyBkZWZpbmVzIHRoZSBjb250ZW50LWRpc3Bvc2l0aW9uIGhlYWRlciwgaWYgeW91J3JlIGN1cmlvdXMKICAgICAgICAgZGlzcCA9IGF0dC5oZWFkZXJzWydjb250ZW50LWRpc3Bvc2l0aW9uJ10uc3BsaXQoJzsnKQogICAgICAgICBbZmlsZW5hbWVfcGFybV0gPSBbaSBmb3IgaSBpbiBkaXNwIGlmIGkuc3RyaXAoKS5zdGFydHN3aXRoKCdmaWxlbmFtZT0nKV0KLSAgICAgICAgKGR1bW15LGZpbGVuYW1lKSA9IGZpbGVuYW1lX3Bhcm0uc3BsaXQoJz0nKQotICAgICAgICAjIFJGQyAyMDQ1LzgyMiBkZWZpbmVzIHRoZSBncmFtbWFyIGZvciB0aGUgZmlsZW5hbWUgdmFsdWUsIGJ1dAotICAgICAgICAjIEkgdGhpbmsgd2UganVzdCBuZWVkIHRvIHJlbW92ZSB0aGUgcXVvdGluZy4gSSBob3BlLgotICAgICAgICBhdHQubmFtZSA9IGZpbGVuYW1lLnN0cmlwKCciJykKKyAgICAgICAgKGR1bW15LGZpbGVuYW1lKSA9IGZpbGVuYW1lX3Bhcm0uc3BsaXQoJz0nLDEpCisgICAgICAgICMgUkZDIDIwNDUvODIyIGRlZmluZXMgdGhlIGdyYW1tYXIgZm9yIHRoZSBmaWxlbmFtZSB2YWx1ZQorICAgICAgICBmaWxlbmFtZSA9IGZpbGVuYW1lLnN0cmlwKCciJykKKyAgICAgICAgIyBlbWFpbC5oZWFkZXIuZGVjb2RlX2hlYWRlciBjYW5ub3QgaGFuZGxlIHN0cmluZ3Mgbm90IGVuZGluZyB3aXRoICc/PScsCisgICAgICAgICMgc28gbGV0J3MgdHJhbnNmb3JtIG9uZSA9Py4uLj89IHBhcnQgYXQgYSB0aW1lCisgICAgICAgIHdoaWxlIFRydWU6CisgICAgICAgICAgICBtYXRjaCA9IHJlLnNlYXJjaCgiPVw/Lio/XD89IiwgZmlsZW5hbWUpCisgICAgICAgICAgICBpZiBtYXRjaCBpcyBOb25lOgorICAgICAgICAgICAgICAgIGJyZWFrCisgICAgICAgICAgICBmaWxlbmFtZSA9IGZpbGVuYW1lWzptYXRjaC5zdGFydCgpXSArIGVtYWlsLmhlYWRlci5kZWNvZGVfaGVhZGVyKG1hdGNoLmdyb3VwKDApKVswXVswXSArIGZpbGVuYW1lW21hdGNoLmVuZCgpOl0KKyAgICAgICAgYXR0Lm5hbWUgPSBmaWxlbmFtZQogICAgICAgICAjIEhvb3JheSwgbm93IHdlIGhhdmUgYSBmaWxlLWxpa2Ugb2JqZWN0IHdpdGggLnJlYWQoKSBhbmQgLm5hbWUKICAgICAgICAgcmV0dXJuIGF0dAogCg==',
'file_name': 'bugzilla-filename.patch',
'flags': [],
'id': 469147,
'is_obsolete': 1,
'is_patch': 0,
'is_private': 0,
'last_change_time': '2010-12-21T17:57:36Z',
'size': 1390,
'summary': 'Proposed patch'},
{'bug_id': 663674,
'content_type': 'text/plain',
'creation_time': '2010-12-21T17:57:36Z',
'creator': 'example',
'data': 'LS0tIC91c3IvbGliL3B5dGhvbjIuNy9zaXRlLXBhY2thZ2VzL2J1Z3ppbGxhL2Jhc2UucHkub3JpZwkyMDEwLTEyLTIxIDEzOjA1OjI5LjcyNzE4OTE0MSArMDEwMAorKysgL3Vzci9saWIvcHl0aG9uMi43L3NpdGUtcGFja2FnZXMvYnVnemlsbGEvYmFzZS5weQkyMDEwLTEyLTIxIDE4OjQ4OjMxLjU5NDgwMjMwNSArMDEwMApAQCAtMTksNiArMTksOCBAQAogaW1wb3J0IHRlbXBmaWxlCiBpbXBvcnQgbG9nZ2luZwogaW1wb3J0IGxvY2FsZQoraW1wb3J0IHJlCitpbXBvcnQgZW1haWwuaGVhZGVyCiAKIGxvZyA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCdidWd6aWxsYScpCiAKQEAgLTY3NywxMCArNjc5LDEzIEBACiAgICAgICAgICMgUkZDIDIxODMgZGVmaW5lcyB0aGUgY29udGVudC1kaXNwb3NpdGlvbiBoZWFkZXIsIGlmIHlvdSdyZSBjdXJpb3VzCiAgICAgICAgIGRpc3AgPSBhdHQuaGVhZGVyc1snY29udGVudC1kaXNwb3NpdGlvbiddLnNwbGl0KCc7JykKICAgICAgICAgW2ZpbGVuYW1lX3Bhcm1dID0gW2kgZm9yIGkgaW4gZGlzcCBpZiBpLnN0cmlwKCkuc3RhcnRzd2l0aCgnZmlsZW5hbWU9JyldCi0gICAgICAgIChkdW1teSxmaWxlbmFtZSkgPSBmaWxlbmFtZV9wYXJtLnNwbGl0KCc9JykKLSAgICAgICAgIyBSRkMgMjA0NS84MjIgZGVmaW5lcyB0aGUgZ3JhbW1hciBmb3IgdGhlIGZpbGVuYW1lIHZhbHVlLCBidXQKLSAgICAgICAgIyBJIHRoaW5rIHdlIGp1c3QgbmVlZCB0byByZW1vdmUgdGhlIHF1b3RpbmcuIEkgaG9wZS4KLSAgICAgICAgYXR0Lm5hbWUgPSBmaWxlbmFtZS5zdHJpcCgnIicpCisgICAgICAgIChkdW1teSxmaWxlbmFtZSkgPSBmaWxlbmFtZV9wYXJtLnNwbGl0KCc9JywgMSkKKyAgICAgICAgIyBSRkMgMjA0NS84MjIgZGVmaW5lcyB0aGUgZ3JhbW1hciBmb3IgdGhlIGZpbGVuYW1lIHZhbHVlCisgICAgICAgIGZpbGVuYW1lID0gZmlsZW5hbWUuc3RyaXAoJyInKQorICAgICAgICAjIEluIGNhc2UgdGhlIGZpbGVuYW1lIGlzIG5vdCBjb21wbGlhbnQgd2l0aCB0aGUgc3RhbmRhcmQsIGxldCdzIG1ha2UKKyAgICAgICAgIyBpdCBjb3JyZWN0LgorICAgICAgICBmaWVsZHMgPSBlbWFpbC5oZWFkZXIuZGVjb2RlX2hlYWRlcihyZS5zdWIoJyg9XD8oW15cP10qXD8pezN9PSknLCAnIFxcMSAnLCBmaWxlbmFtZSkpCisgICAgICAgIGF0dC5uYW1lID0gJycuam9pbihmaWVsZFsxXSBhbmQgZmllbGRbMF0uZGVjb2RlKGZpZWxkWzFdKSBvciBmaWVsZFswXSBmb3IgZmllbGQgaW4gZmllbGRzKQogICAgICAgICAjIEhvb3JheSwgbm93IHdlIGhhdmUgYSBmaWxlLWxpa2Ugb2JqZWN0IHdpdGggLnJlYWQoKSBhbmQgLm5hbWUKICAgICAgICAgcmV0dXJuIGF0dAogCg==',
'file_name': 'bugzilla-filename-2.patch',
'flags': [],
'id': 470041,
'is_obsolete': 0,
'is_patch': 1,
'is_private': 0,
'last_change_time': '2010-12-21T17:57:36Z',
'size': 1351,
'summary': 'Better proposed patch'},
{'bug_id': 663674,
'content_type': 'text/plain',
'creation_time': '2011-06-01T18:57:50Z',
'creator': 'example',
'data': 'SG9vcmF5IGZvciBNZXRlb3JvbG9naWNrw6kgenByw6F2eSA1XzA0LnBkZiEK',
'file_name': 'Klíč memorial test file.txt',
'flags': [],
'id': 502352,
'is_obsolete': 0,
'is_patch': 0,
'is_private': 0,
'last_change_time': '2011-06-01T18:57:50Z',
'size': 45,
'summary': 'An empty test file with a utf-8 filename'}]}}
0707010000009E000081A400000000000000000000000166EC67150000255B000000000000000000000000000000000000005700000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockreturn/test_bug_fields.txt# bugzilla.redhat.com 2020-01-10 with {"names": "bug_status"}
{
"fields": [
{
"display_name": "Status",
"id": 2,
"is_custom": False,
"is_mandatory": False,
"is_on_bug_entry": False,
"name": "bug_status",
"type": 2,
"values": [
{
"can_change_to": [
{
"comment_required": False,
"name": "NEW"
},
{
"comment_required": False,
"name": "ASSIGNED"
}
],
"is_open": True,
"sort_key": 0,
"sortkey": 0,
"visibility_values": []
},
{
"can_change_to": [
{
"comment_required": False,
"name": "ASSIGNED"
},
{
"comment_required": False,
"name": "POST"
},
{
"comment_required": False,
"name": "MODIFIED"
},
{
"comment_required": False,
"name": "ON_DEV"
},
{
"comment_required": False,
"name": "ON_QA"
},
{
"comment_required": False,
"name": "VERIFIED"
},
{
"comment_required": False,
"name": "RELEASE_PENDING"
},
{
"comment_required": False,
"name": "CLOSED"
}
],
"is_open": True,
"name": "NEW",
"sort_key": 10,
"sortkey": 10,
"visibility_values": []
},
{
"can_change_to": [
{
"comment_required": False,
"name": "NEW"
},
{
"comment_required": False,
"name": "POST"
},
{
"comment_required": False,
"name": "MODIFIED"
},
{
"comment_required": False,
"name": "ON_DEV"
},
{
"comment_required": False,
"name": "ON_QA"
},
{
"comment_required": False,
"name": "VERIFIED"
},
{
"comment_required": False,
"name": "RELEASE_PENDING"
},
{
"comment_required": False,
"name": "CLOSED"
}
],
"is_open": True,
"name": "ASSIGNED",
"sort_key": 20,
"sortkey": 20,
"visibility_values": []
},
{
"can_change_to": [
{
"comment_required": False,
"name": "NEW"
},
{
"comment_required": False,
"name": "ASSIGNED"
},
{
"comment_required": False,
"name": "MODIFIED"
},
{
"comment_required": False,
"name": "ON_DEV"
},
{
"comment_required": False,
"name": "ON_QA"
},
{
"comment_required": False,
"name": "VERIFIED"
},
{
"comment_required": False,
"name": "RELEASE_PENDING"
},
{
"comment_required": False,
"name": "CLOSED"
}
],
"is_open": True,
"name": "POST",
"sort_key": 30,
"sortkey": 30,
"visibility_values": []
},
{
"can_change_to": [
{
"comment_required": False,
"name": "NEW"
},
{
"comment_required": False,
"name": "ASSIGNED"
},
{
"comment_required": False,
"name": "POST"
},
{
"comment_required": False,
"name": "ON_DEV"
},
{
"comment_required": False,
"name": "ON_QA"
},
{
"comment_required": False,
"name": "VERIFIED"
},
{
"comment_required": False,
"name": "RELEASE_PENDING"
},
{
"comment_required": False,
"name": "CLOSED"
}
],
"is_open": True,
"name": "MODIFIED",
"sort_key": 40,
"sortkey": 40,
"visibility_values": []
},
{
"can_change_to": [
{
"comment_required": False,
"name": "NEW"
},
{
"comment_required": False,
"name": "ASSIGNED"
},
{
"comment_required": False,
"name": "POST"
},
{
"comment_required": False,
"name": "MODIFIED"
},
{
"comment_required": False,
"name": "ON_QA"
},
{
"comment_required": False,
"name": "VERIFIED"
},
{
"comment_required": False,
"name": "RELEASE_PENDING"
},
{
"comment_required": False,
"name": "CLOSED"
}
],
"is_open": True,
"name": "ON_DEV",
"sort_key": 50,
"sortkey": 50,
"visibility_values": []
},
{
"can_change_to": [
{
"comment_required": False,
"name": "NEW"
},
{
"comment_required": False,
"name": "ASSIGNED"
},
{
"comment_required": False,
"name": "POST"
},
{
"comment_required": False,
"name": "MODIFIED"
},
{
"comment_required": False,
"name": "ON_DEV"
},
{
"comment_required": False,
"name": "VERIFIED"
},
{
"comment_required": False,
"name": "RELEASE_PENDING"
},
{
"comment_required": False,
"name": "CLOSED"
}
],
"is_open": True,
"name": "ON_QA",
"sort_key": 60,
"sortkey": 60,
"visibility_values": []
},
{
"can_change_to": [
{
"comment_required": False,
"name": "NEW"
},
{
"comment_required": False,
"name": "ASSIGNED"
},
{
"comment_required": False,
"name": "POST"
},
{
"comment_required": False,
"name": "MODIFIED"
},
{
"comment_required": False,
"name": "ON_DEV"
},
{
"comment_required": False,
"name": "ON_QA"
},
{
"comment_required": False,
"name": "RELEASE_PENDING"
},
{
"comment_required": False,
"name": "CLOSED"
}
],
"is_open": True,
"name": "VERIFIED",
"sort_key": 70,
"sortkey": 70,
"visibility_values": []
},
{
"can_change_to": [
{
"comment_required": False,
"name": "NEW"
},
{
"comment_required": False,
"name": "ASSIGNED"
},
{
"comment_required": False,
"name": "POST"
},
{
"comment_required": False,
"name": "MODIFIED"
},
{
"comment_required": False,
"name": "ON_DEV"
},
{
"comment_required": False,
"name": "ON_QA"
},
{
"comment_required": False,
"name": "VERIFIED"
},
{
"comment_required": False,
"name": "CLOSED"
}
],
"is_open": True,
"name": "RELEASE_PENDING",
"sort_key": 80,
"sortkey": 80,
"visibility_values": []
},
{
"can_change_to": [
{
"comment_required": False,
"name": "NEW"
},
{
"comment_required": False,
"name": "ASSIGNED"
},
{
"comment_required": False,
"name": "POST"
},
{
"comment_required": False,
"name": "MODIFIED"
},
{
"comment_required": False,
"name": "ON_QA"
}
],
"is_open": False,
"name": "CLOSED",
"sort_key": 90,
"sortkey": 90,
"visibility_values": []
}
],
"visibility_values": []
}
]
}
0707010000009F000081A400000000000000000000000166EC67150000099D000000000000000000000000000000000000005300000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockreturn/test_getbug.txt{'bugs': [{
'actual_time': 0.0,
'alias': [],
'assigned_to': 'crobinso@redhat.com',
'assigned_to_detail': {'email': 'crobinso@redhat.com',
'id': 199727,
'name': 'crobinso@redhat.com',
'real_name': 'Cole Robinson'},
'blocks': [],
'cc': ['crobinso@redhat.com'],
'cc_detail': [{'email': 'crobinso@redhat.com',
'id': 199727,
'name': 'crobinso@redhat.com',
'real_name': 'Cole Robinson'}],
'cf_build_id': '',
'cf_conditional_nak': [],
'cf_cust_facing': '---',
'cf_devel_whiteboard': '',
'cf_doc_type': 'If docs needed, set a value',
'cf_environment': '',
'cf_fixed_in': '',
'cf_internal_whiteboard': '',
'cf_last_closed': '2019-03-29T16:39:27',
'cf_partner': [],
'cf_pgm_internal': '',
'cf_pm_score': '0',
'cf_qa_whiteboard': '',
'cf_qe_conditional_nak': [],
'cf_release_notes': '',
'cf_verified': [],
'classification': 'Fedora',
'component': ['python-bugzilla'],
'creation_time': '2019-03-29T16:39:01',
'creator': 'crobinso@redhat.com',
'creator_detail': {'email': 'crobinso@redhat.com',
'id': 199727,
'name': 'crobinso@redhat.com',
'real_name': 'Cole Robinson'},
'depends_on': [],
'docs_contact': '',
'estimated_time': 0.0,
'groups': [],
'id': 1694158,
'is_cc_accessible': True,
'is_confirmed': True,
'is_creator_accessible': True,
'is_open': False,
'keywords': [],
'last_change_time': '2019-03-29T16:57:48',
'op_sys': 'Unspecified',
'platform': 'Unspecified',
'priority': 'unspecified',
'product': 'Fedora',
'qa_contact': 'extras-qa@fedoraproject.org',
'qa_contact_detail': {'email': 'extras-qa@fedoraproject.org',
'id': 171387,
'name': 'extras-qa@fedoraproject.org',
'real_name': 'Fedora Extras Quality '
'Assurance'},
'remaining_time': 0.0,
'resolution': 'NOTABUG',
'see_also': [],
'severity': 'unspecified',
'status': 'CLOSED',
'summary': 'python-bugzilla test bug for API minor_update',
'target_milestone': '---',
'target_release': ['---'],
'update_token': '1578493259-h3_XQLcFwQkxxRzj5fTivx_wB8OizN7dPUyU_iJ59Bc',
'url': '',
'version': ['30'],
'whiteboard': ''}],
'faults': []}
070701000000A0000081A400000000000000000000000166EC671500001E08000000000000000000000000000000000000005800000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockreturn/test_getbug_rhel.txt{'faults': [],
"bugs" : [{
'actual_time': 0.0,
'alias': [],
'assigned_to': 'lvm-team@redhat.com',
'assigned_to_detail': {'email': 'lvm-team@redhat.com',
'id': 206817,
'name': 'lvm-team@redhat.com',
'real_name': 'LVM and device-mapper development team'},
'blocks': [123456],
'cc': ['example@redhat.com',
'example2@redhat.com'],
'cc_detail': [{'email': 'example@redhat.com',
'id': 123456,
'name': 'example@redhat.com',
'real_name': 'Example user'},
{'email': 'example2@redhat.com',
'id': 123457,
'name': 'heinzm@redhat.com',
'real_name': 'Example2 user'}],
'cf_build_id': '',
'cf_conditional_nak': [],
'cf_cust_facing': '---',
'cf_devel_whiteboard': 'somedeveltag,someothertag',
'cf_doc_type': 'Bug Fix',
'cf_environment': '',
'cf_fixed_in': '',
'cf_internal_whiteboard': 'someinternal TAG',
'cf_last_closed': '2016-03-03T22:15:07',
'cf_partner': [],
'cf_pgm_internal': '',
'cf_pm_score': '0',
'cf_qa_whiteboard': 'foo bar baz',
'cf_qe_conditional_nak': [],
'cf_release_notes': '',
'cf_target_upstream_version': '',
'cf_verified': [],
'classification': 'Red Hat',
'comments': [{'bug_id': 1165434,
'count': 0,
'creation_time': '2014-11-19T00:26:50',
'creator': 'example@redhat.com',
'creator_id': 276776,
'id': 7685441,
'is_private': False,
'tags': [],
'text': 'Description of problem:\n'
'Version-Release number of selected component (if '
'applicable):\n'
'kernel-2.6.18-308.el5\n'
'device-mapper-multipath-0.4.7-48.el5\n'
'device-mapper-1.02.67-2.el5\n'
'device-mapper-1.02.67-2.el5\n'
'device-mapper-event-1.02.67-2.el5\n',
'time': '2014-11-19T00:26:50'},
{'bug_id': 1165434,
'count': 1,
'creation_time': '2014-11-19T00:47:57',
'creator': 'example@redhat.com',
'creator_id': 276776,
'id': 7685467,
'is_private': False,
'tags': [],
'text': 'We can see that there is a dmeventd task that has sent '
'data over a socket and is waiting for the peer to '
'respond:\n'
'\n'
'crash> bt\n'
'any interaction with the filesystem until it has '
'issued the suspend command to convert the mirror '
'device to a linear device.',
'time': '2014-11-19T00:47:57'},
{'bug_id': 1165434,
'count': 2,
'creation_time': '2014-11-19T01:53:53',
'creator': 'example@redhat.com',
'creator_id': 156796,
'id': 7685595,
'is_private': False,
'tags': [],
'text': 'Test text',
'time': '2014-11-19T01:53:53'}],
'depends_on': [112233],
'docs_contact': '',
'estimated_time': 0.0,
'external_bugs': [{'bug_id': 989253,
'ext_bz_bug_id': '703421',
'ext_bz_id': 3,
'ext_description': 'None',
'ext_priority': 'None',
'ext_status': 'None',
'id': 115528,
'type': {'can_get': True,
'can_send': False,
'description': 'GNOME Bugzilla',
'full_url': 'https://bugzilla.gnome.org/show_bug.cgi?id=%id%',
'id': 3,
'must_send': False,
'send_once': False,
'type': 'Bugzilla',
'url': 'https://bugzilla.gnome.org'}},
{'bug_id': 989253,
'ext_bz_bug_id': '1203576',
'ext_bz_id': 29,
'ext_description': 'None',
'ext_priority': 'None',
'ext_status': 'None',
'id': 115527,
'type': {'can_get': False,
'can_send': False,
'description': 'Launchpad',
'full_url': 'https://bugs.launchpad.net/bugs/%id%',
'id': 29,
'must_send': False,
'send_once': False,
'type': 'None',
'url': 'https://bugs.launchpad.net/bugs'}}],
'flags': [{'creation_date': '2019-11-15T21:57:21Z',
'id': 4302313,
'is_active': 1,
'modification_date': '2019-11-15T21:57:21Z',
'name': 'qe_test_coverage',
'setter': 'pm-rhel@redhat.com',
'status': '?',
'type_id': 318},
{'creation_date': '2018-12-25T16:47:43Z',
'id': 3883137,
'is_active': 1,
'modification_date': '2018-12-25T16:47:43Z',
'name': 'release',
'setter': 'rule-engine@redhat.com',
'status': '?',
'type_id': 1197},
{'creation_date': '2018-12-25T16:47:38Z',
'id': 3883134,
'is_active': 1,
'modification_date': '2018-12-25T16:47:38Z',
'name': 'pm_ack',
'setter': 'example3@redhat.com',
'status': '?',
'type_id': 11},
{'creation_date': '2018-12-25T16:47:38Z',
'id': 3883135,
'is_active': 1,
'modification_date': '2018-12-25T16:47:38Z',
'name': 'devel_ack',
'setter': 'example2@redhat.com',
'status': '?',
'type_id': 10},
{'creation_date': '2018-12-25T16:47:38Z',
'id': 3883136,
'is_active': 1,
'modification_date': '2019-04-28T02:07:03Z',
'name': 'qa_ack',
'setter': 'example@redhat.com',
'status': '+',
'type_id': 9},
{'creation_date': '2019-03-29T06:50:01Z',
'id': 3999302,
'is_active': 1,
'modification_date': '2019-03-29T06:50:01Z',
'name': 'needinfo',
'setter': 'example@redhat.com',
'requestee': 'hello@example.com',
'status': '?',
'type_id': 1164}],
'groups': ["somegroup"],
'id': 1165434,
'is_cc_accessible': True,
'is_confirmed': True,
'is_creator_accessible': True,
'is_open': False,
'keywords': ["key1", "keyword2", "Security"],
'last_change_time': '2018-12-09T19:12:12',
'op_sys': 'Linux',
'platform': 'All',
'priority': 'medium',
'product': 'Red Hat Enterprise Linux 5',
'qa_contact': 'mspqa-list@redhat.com',
'qa_contact_detail': {'email': 'mspqa-list@redhat.com',
'id': 164197,
'name': 'mspqa-list@redhat.com',
'real_name': 'Cluster QE'},
'remaining_time': 0.0,
'resolution': 'WONTFIX',
'see_also': [],
'severity': 'medium',
'status': 'CLOSED',
'sub_components': {'lvm2': ['dmeventd (RHEL5)']},
'summary': 'LVM mirrored root can deadlock dmeventd if a mirror leg is lost',
'tags': [],
'target_milestone': 'rc',
'target_release': ['---'],
'url': '',
'version': ['5.8'],
'whiteboard': 'genericwhiteboard'}]}
070701000000A1000081A400000000000000000000000166EC671500000247000000000000000000000000000000000000005300000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockreturn/test_query1.txt{'bugs': [{'assigned_to_detail': {'real_name': 'Libvirt Maintainers', 'email': 'libvirt-maint', 'name': 'libvirt-maint', 'id': 311982}, 'summary': 'RFE: qemu: Support a managed autoconnect mode for host USB devices', 'status': 'NEW', 'assigned_to': 'Libvirt Maintainers', 'id': 508645}, {'assigned_to_detail': {'real_name': 'Cole Robinson', 'email': 'crobinso', 'name': 'crobinso', 'id': 199727}, 'summary': 'RFE: warn users at guest start if networks/storage pools are inactive', 'status': 'NEW', 'assigned_to': 'Cole Robinson', 'id': 668543}], 'limit': 0, 'FOOFAKEVALUE': 'hello'}
070701000000A2000081A400000000000000000000000166EC671500000BD0000000000000000000000000000000000000005D00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/data/mockreturn/test_query_cve_getbug.txt{'bugs': [{'actual_time': 0.0,
'alias': ["CVE-1234-5678"],
'assigned_to': 'crobinso@redhat.com',
'assigned_to_detail': {'email': 'crobinso@redhat.com',
'id': 199727,
'name': 'crobinso@redhat.com',
'real_name': 'Cole Robinson'},
'blocks': [],
'cc': ['crobinso@redhat.com'],
'cc_detail': [{'email': 'crobinso@redhat.com',
'id': 199727,
'name': 'crobinso@redhat.com',
'real_name': 'Cole Robinson'}],
'cf_build_id': '',
'cf_conditional_nak': [],
'cf_cust_facing': '---',
'cf_devel_whiteboard': '',
'cf_doc_type': 'If docs needed, set a value',
'cf_environment': '',
'cf_fixed_in': '',
'cf_internal_whiteboard': '',
'cf_last_closed': '2019-03-29T16:39:27',
'cf_partner': [],
'cf_pgm_internal': '',
'cf_pm_score': '0',
'cf_qa_whiteboard': '',
'cf_qe_conditional_nak': [],
'cf_release_notes': '',
'cf_verified': [],
'classification': 'Fedora',
'component': ['python-bugzilla'],
'creation_time': '2019-03-29T16:39:01',
'creator': 'crobinso@redhat.com',
'creator_detail': {'email': 'crobinso@redhat.com',
'id': 199727,
'name': 'crobinso@redhat.com',
'real_name': 'Cole Robinson'},
'depends_on': [],
'docs_contact': '',
'estimated_time': 0.0,
'groups': [],
'id': 123456,
'is_cc_accessible': True,
'is_confirmed': True,
'is_creator_accessible': True,
'is_open': False,
'keywords': [],
'last_change_time': '2019-03-29T16:57:48',
'op_sys': 'Unspecified',
'platform': 'Unspecified',
'priority': 'unspecified',
'product': 'Fedora',
'qa_contact': 'extras-qa@fedoraproject.org',
'qa_contact_detail': {'email': 'extras-qa@fedoraproject.org',
'id': 171387,
'name': 'extras-qa@fedoraproject.org',
'real_name': 'Fedora Extras Quality '
'Assurance'},
'remaining_time': 0.0,
'resolution': 'NOTABUG',
'see_also': [],
'severity': 'unspecified',
'status': 'CLOSED',
'summary': 'python-bugzilla test bug for API minor_update',
'target_milestone': '---',
'target_release': ['---'],
'update_token': '1578493259-h3_XQLcFwQkxxRzj5fTivx_wB8OizN7dPUyU_iJ59Bc',
'url': '',
'version': ['30'],
'whiteboard': ''}],
'faults': []}
070701000000A3000041ED00000000000000000000000266EC671500000000000000000000000000000000000000000000003F00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/integration070701000000A4000081A400000000000000000000000166EC671500000121000000000000000000000000000000000000004B00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/integration/__init__.pyimport os
TEST_URL = os.getenv("BUGZILLA_URL", "http://localhost")
TEST_OWNER = "andreas@hasenkopf.xyz"
TEST_PRODUCTS = {"Red Hat Enterprise Linux 9",
"SUSE Linux Enterprise Server 15 SP6",
"TestProduct"}
TEST_SUSE_COMPONENTS = {"Containers", "Kernel"}
070701000000A5000081A400000000000000000000000166EC671500000D7F000000000000000000000000000000000000004E00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/integration/ro_api_test.py# Ignoring pytest-related warnings:
# pylint: disable=redefined-outer-name,unused-argument
import pytest
from bugzilla import BugzillaError
from ..utils import open_bz
from . import TEST_URL, TEST_PRODUCTS, TEST_SUSE_COMPONENTS, TEST_OWNER
def test_rest_xmlrpc_detection(mocked_responses):
# The default: use XMLRPC
bz = open_bz(url=TEST_URL)
assert bz.is_xmlrpc()
assert "/xmlrpc.cgi" in bz.url
# See /rest in the URL, so use REST
bz = open_bz(url=TEST_URL + "/rest")
assert bz.is_rest()
with pytest.raises(BugzillaError) as e:
dummy = bz._proxy # pylint: disable=protected-access
assert "raw XMLRPC access is not provided" in str(e)
# See /xmlrpc.cgi in the URL, so use XMLRPC
bz = open_bz(url=TEST_URL + "/xmlrpc.cgi")
assert "/xmlrpc.cgi" in bz.url
assert bz.is_xmlrpc()
assert bz._proxy # pylint: disable=protected-access
def test_apikey_error_scraping(mocked_responses):
# Ensure the API key does not leak into any requests exceptions
fakekey = "FOOBARMYKEY"
with pytest.raises(Exception) as e:
open_bz("https://httpstat.us/400&foo",
force_xmlrpc=True, api_key=fakekey)
assert "Client Error" in str(e.value)
assert fakekey not in str(e.value)
with pytest.raises(Exception) as e:
open_bz("https://httpstat.us/400&foo",
force_rest=True, api_key=fakekey)
assert "Client Error" in str(e.value)
assert fakekey not in str(e.value)
def test_xmlrpc_bad_url(mocked_responses):
with pytest.raises(BugzillaError) as e:
open_bz(url="https://example.com/#xmlrpc", force_xmlrpc=True)
assert "URL may not be an XMLRPC URL" in str(e)
def test_get_products(mocked_responses, backends):
bz = open_bz(url=TEST_URL, **backends)
assert len(bz.products) == 3
assert {p["name"] for p in bz.products} == TEST_PRODUCTS
rhel = next(p for p in bz.products if p["id"] == 2)
assert {v["name"] for v in rhel["versions"]} == {"9.0", "9.1", "unspecified"}
def test_get_components(mocked_responses, backends):
bz = open_bz(url=TEST_URL, **backends)
components = bz.getcomponents(product="SUSE Linux Enterprise Server 15 SP6")
assert len(components) == 2
assert set(components) == TEST_SUSE_COMPONENTS
def test_get_component_detail(mocked_responses, backends):
bz = open_bz(url=TEST_URL, **backends)
component = bz.getcomponentdetails(product="Red Hat Enterprise Linux 9",
component="python-bugzilla")
assert component["id"] == 2
assert component["default_assigned_to"] == TEST_OWNER
def test_query(mocked_responses, backends):
bz = open_bz(url=TEST_URL, **backends)
query = bz.build_query(product="Red Hat Enterprise Linux 9", component="python-bugzilla")
bugs = bz.query(query=query)
assert len(bugs) == 1
assert bugs[0].id == 2
assert bugs[0].summary == "Expect the Spanish inquisition"
bz = open_bz(url=TEST_URL, **backends)
query = bz.build_query(product="SUSE Linux Enterprise Server 15 SP6")
bugs = bz.query(query=query)
assert len(bugs) == 1
assert bugs[0].id == 1
assert bugs[0].whiteboard == "AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:L"
def test_get_bug_alias(mocked_responses, backends):
bz = open_bz(url=TEST_URL, **backends)
bug = bz.getbug("FOO-1")
assert bug.id == 1
assert bug.summary == "ZeroDivisionError in function foo_bar()"
070701000000A6000081A400000000000000000000000166EC6715000006B9000000000000000000000000000000000000004E00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/integration/ro_cli_test.py# Ignoring pytest-related warnings:
# pylint: disable=unused-argument
from ..utils import open_bz
from . import TEST_URL, TEST_PRODUCTS, TEST_SUSE_COMPONENTS, TEST_OWNER
def test_get_products(mocked_responses, run_cli, backends):
bz = open_bz(url=TEST_URL, **backends)
out = run_cli("bugzilla info --products", bzinstance=bz)
assert len(out.strip().split("\n")) == 3
for product in TEST_PRODUCTS:
assert product in out
def test_get_components(mocked_responses, run_cli, backends):
bz = open_bz(url=TEST_URL, **backends)
out = run_cli("bugzilla info --components 'SUSE Linux Enterprise Server 15 SP6'", bzinstance=bz)
assert len(out.strip().split("\n")) == 2
for comp in TEST_SUSE_COMPONENTS:
assert comp in out
def test_get_component_owners(mocked_responses, run_cli, backends):
bz = open_bz(url=TEST_URL, **backends)
out = run_cli("bugzilla info --component_owners 'SUSE Linux Enterprise Server 15 SP6'",
bzinstance=bz)
assert TEST_OWNER in out
def test_get_versions(mocked_responses, run_cli, backends):
bz = open_bz(url=TEST_URL, **backends)
out = run_cli("bugzilla info --versions 'Red Hat Enterprise Linux 9'", bzinstance=bz)
versions = set(out.strip().split("\n"))
assert versions == {"unspecified", "9.0", "9.1"}
def test_query(mocked_responses, run_cli, backends):
bz = open_bz(url=TEST_URL, **backends)
out = run_cli("bugzilla query --product 'Red Hat Enterprise Linux 9' "
"--component 'python-bugzilla'", bzinstance=bz)
lines = out.strip().splitlines()
assert len(lines) == 1
assert lines[0].startswith("#2")
assert "Expect the Spanish inquisition" in lines[0]
070701000000A7000081A400000000000000000000000166EC671500001235000000000000000000000000000000000000004200000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/mockbackend.py# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import inspect
import bugzilla
from bugzilla._backendbase import _BackendBase
import tests.utils
# pylint: disable=abstract-method,arguments-differ
class BackendMock(_BackendBase):
_version = None
def bugzilla_version(self):
return {"version": self._version}
def __helper(self, args):
# Grab the calling function name and use it to generate
# input and output variable names for the class. So if this
# is called from bug_get, we look for:
# self._bug_get_args
# self._bug_get_return
prevfuncname = inspect.stack()[1][3]
func_args = getattr(self, "_%s_args" % prevfuncname)
func_return = getattr(self, "_%s_return" % prevfuncname)
if isinstance(func_return, BaseException):
raise func_return
filename = None
expect_out = func_args
if isinstance(func_args, str):
filename = func_args
expect_out = None
# Hack to strip out attachment content from the generated
# test output, because it doesn't play well with the test
# suite running on python2
if "content-disposition" in str(args):
largs = list(args)
largs[1] = "STRIPPED-BY-TESTSUITE"
args = tuple(largs)
if filename or expect_out:
tests.utils.diff_compare(args, filename, expect_out)
if isinstance(func_return, dict):
return func_return
returnstr = open(tests.utils.tests_path(func_return)).read()
return eval(returnstr) # pylint: disable=eval-used
def bug_attachment_create(self, *args):
return self.__helper(args)
def bug_attachment_get(self, *args):
return self.__helper(args)
def bug_attachment_get_all(self, *args):
return self.__helper(args)
def bug_attachment_update(self, *args):
return self.__helper(args)
def bug_comments(self, *args):
return self.__helper(args)
def bug_create(self, *args):
return self.__helper(args)
def bug_history(self, *args):
return self.__helper(args)
def bug_get(self, *args):
return self.__helper(args)
def bug_fields(self, *args):
return self.__helper(args)
def bug_search(self, *args):
return self.__helper(args)
def bug_update(self, *args):
return self.__helper(args)
def bug_update_tags(self, *args):
return self.__helper(args)
def component_create(self, *args):
return self.__helper(args)
def component_get(self, *args):
return self.__helper(args)
def component_update(self, *args):
return self.__helper(args)
def group_get(self, *args):
return self.__helper(args)
def externalbugs_add(self, *args):
return self.__helper(args)
def externalbugs_update(self, *args):
return self.__helper(args)
def externalbugs_remove(self, *args):
return self.__helper(args)
def product_get(self, *args):
return self.__helper(args)
def product_get_accessible(self, *args):
return self.__helper(args)
def product_get_enterable(self, *args):
return self.__helper(args)
def product_get_selectable(self, *args):
return self.__helper(args)
def user_create(self, *args):
return self.__helper(args)
def user_get(self, *args):
return self.__helper(args)
def user_login(self, *args):
return self.__helper(args)
def user_logout(self, *args):
return self.__helper(args)
def user_update(self, *args):
return self.__helper(args)
def _make_backend_class(version="6.0.0", **kwargs):
class TmpBackendClass(BackendMock):
_version = version
for key, val in kwargs.items():
setattr(TmpBackendClass, "_%s" % key, val)
return TmpBackendClass
def make_bz(bz_kwargs=None, rhbz=False, **kwargs):
bz_kwargs = (bz_kwargs or {}).copy()
if "url" in bz_kwargs:
raise RuntimeError("Can't set 'url' in mock make_bz, use connect()")
if "use_creds" not in bz_kwargs:
bz_kwargs["use_creds"] = False
bz = bugzilla.Bugzilla(url=None, **bz_kwargs)
backendclass = _make_backend_class(**kwargs)
def _get_backend_class(url):
return backendclass, bugzilla.Bugzilla.fix_url(url)
# pylint: disable=protected-access
bz._get_backend_class = _get_backend_class
url = "https:///TESTSUITEMOCK"
if rhbz:
url += "?fakeredhat=bugzilla.redhat.com"
bz.connect(url)
return bz
070701000000A8000081A400000000000000000000000166EC671500000000000000000000000000000000000000000000004300000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/pycodestyle.cfg070701000000A9000041ED00000000000000000000000266EC671500000000000000000000000000000000000000000000003C00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/services070701000000AA000081A400000000000000000000000166EC671500000546000000000000000000000000000000000000004700000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/services/DockerfileFROM ubuntu:22.04
LABEL description="Bugzilla image for testing purposes"
ARG DEBIAN_FRONTEND=noninteractive
ENV TZ="Etc/UTC"
RUN apt update && \
apt install --no-install-recommends -q -y \
tzdata wget apache2 libcgi-pm-perl libdatetime-perl libdatetime-timezone-perl libdbi-perl \
libdbix-connector-perl libdigest-sha-perl libemail-address-perl libemail-mime-perl \
libemail-sender-perl libjson-xs-perl liblist-moreutils-perl libmath-random-isaac-perl \
libtemplate-perl libtimedate-perl liburi-perl libmariadb-dev-compat libdbd-mysql-perl \
libxmlrpc-lite-perl libsoap-lite-perl libapache2-mod-perl2 libtest-taint-perl \
libjson-rpc-perl && \
apt clean
RUN mkdir -p /var/www/webapps && \
wget https://ftp.mozilla.org/pub/mozilla.org/webtools/bugzilla-5.0.6.tar.gz \
-O /tmp/bugzilla-5.0.6.tar.gz&& \
tar xvzf /tmp/bugzilla-5.0.6.tar.gz && \
rm /tmp/bugzilla-5.0.6.tar.gz && \
mv /bugzilla-5.0.6/ /var/www/webapps/bugzilla/ && \
mkdir /var/www/webapps/bugzilla/data/
COPY bugzilla.conf /etc/apache2/sites-available/
COPY localconfig /var/www/webapps/bugzilla/
COPY params.json /var/www/webapps/bugzilla/data/
RUN a2dissite 000-default && \
a2ensite bugzilla && \
a2enmod cgi headers expires rewrite perl && \
/var/www/webapps/bugzilla/checksetup.pl
CMD apachectl -D FOREGROUND
070701000000AB000081A400000000000000000000000166EC6715000006CF000000000000000000000000000000000000004600000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/services/README.md# Working with the containerized Bugzilla instance
This document describes the steps for building a Bugzilla container image that can be used in the
GitHub Actions as a service and generating a database dump.
In the following examples, the use of `docker` is assumed. Commands for `podman` should be
identical.
## Build
```shell
$ docker network create --driver bridge local-bridge
$ docker run --rm -itd \
--env MARIADB_USER=bugs \
--env MARIADB_DATABASE=bugs \
--env MARIADB_PASSWORD=secret \
--env MARIADB_ROOT_PASSWORD=supersecret \
-p 3306:3306 \
--network local-bridge \
--name mariadb \
mariadb:latest
$ mariadb -u bugs -h 127.0.0.1 -P 3306 --password=secret bugs < bugs.sql
$ docker build --network local-bridge . -t ghcr.io/crazyscientist/bugzilla:test
```
For those, who can spot the _chicken and egg problem_: The first version of `bugs.sql` was
created after running the Bugzilla installer inside the container.
## Usage
Once built, you can follow the above instructions; instead of building
the image, you can run it:
```shell
docker run --rm -itd \
-p 8000:80 \
--network local-bridge \
ghcr.io/crazyscientist/bugzilla:test
```
## Test data
The test data used by the Bugzilla service in the integration test suite is stored in `bugs.sql`.
One can edit this file manually or follow the above instructions to start both a MariaDB and
Bugzilla container and edit the data in Bugzilla. Once done, one needs to dump the changed data into
the file again:
```shell
$ mariadb-dump -u bugs -h 127.0.0.1 -P 3306 --password=secret bugs > bugs.qql
```
## Testing
And now, you can run the integration tests against this instance:
```shell
BUGZILLA_URL=http://localhost:8000 pytest --ro-integration
```
070701000000AC000081A400000000000000000000000166EC671500051245000000000000000000000000000000000000004500000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/services/bugs.sql-- MariaDB dump 10.19 Distrib 10.6.12-MariaDB, for debian-linux-gnu (x86_64)
--
-- Host: 127.0.0.1 Database: bugs
-- ------------------------------------------------------
-- Server version 11.1.2-MariaDB-1:11.1.2+maria~ubu2204
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `attach_data`
--
DROP TABLE IF EXISTS `attach_data`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `attach_data` (
`id` mediumint(9) NOT NULL,
`thedata` longblob NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `fk_attach_data_id_attachments_attach_id` FOREIGN KEY (`id`) REFERENCES `attachments` (`attach_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci MAX_ROWS=100000 AVG_ROW_LENGTH=1000000;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `attach_data`
--
LOCK TABLES `attach_data` WRITE;
/*!40000 ALTER TABLE `attach_data` DISABLE KEYS */;
/*!40000 ALTER TABLE `attach_data` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `attachments`
--
DROP TABLE IF EXISTS `attachments`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `attachments` (
`attach_id` mediumint(9) NOT NULL AUTO_INCREMENT,
`bug_id` mediumint(9) NOT NULL,
`creation_ts` datetime NOT NULL,
`modification_time` datetime NOT NULL,
`description` tinytext NOT NULL,
`mimetype` tinytext NOT NULL,
`ispatch` tinyint(4) NOT NULL DEFAULT 0,
`filename` varchar(255) NOT NULL,
`submitter_id` mediumint(9) NOT NULL,
`isobsolete` tinyint(4) NOT NULL DEFAULT 0,
`isprivate` tinyint(4) NOT NULL DEFAULT 0,
PRIMARY KEY (`attach_id`),
KEY `attachments_bug_id_idx` (`bug_id`),
KEY `attachments_creation_ts_idx` (`creation_ts`),
KEY `attachments_modification_time_idx` (`modification_time`),
KEY `attachments_submitter_id_idx` (`submitter_id`,`bug_id`),
CONSTRAINT `fk_attachments_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_attachments_submitter_id_profiles_userid` FOREIGN KEY (`submitter_id`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `attachments`
--
LOCK TABLES `attachments` WRITE;
/*!40000 ALTER TABLE `attachments` DISABLE KEYS */;
/*!40000 ALTER TABLE `attachments` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `audit_log`
--
DROP TABLE IF EXISTS `audit_log`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `audit_log` (
`user_id` mediumint(9) DEFAULT NULL,
`class` varchar(255) NOT NULL,
`object_id` int(11) NOT NULL,
`field` varchar(64) NOT NULL,
`removed` mediumtext DEFAULT NULL,
`added` mediumtext DEFAULT NULL,
`at_time` datetime NOT NULL,
KEY `audit_log_class_idx` (`class`,`at_time`),
KEY `fk_audit_log_user_id_profiles_userid` (`user_id`),
CONSTRAINT `fk_audit_log_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `audit_log`
--
LOCK TABLES `audit_log` WRITE;
/*!40000 ALTER TABLE `audit_log` DISABLE KEYS */;
INSERT INTO `audit_log` VALUES (NULL,'Bugzilla::Field',1,'__create__',NULL,'bug_id','2023-09-20 13:12:34'),(NULL,'Bugzilla::Field',2,'__create__',NULL,'short_desc','2023-09-20 13:12:34'),(NULL,'Bugzilla::Field',3,'__create__',NULL,'classification','2023-09-20 13:12:34'),(NULL,'Bugzilla::Field',4,'__create__',NULL,'product','2023-09-20 13:12:34'),(NULL,'Bugzilla::Field',5,'__create__',NULL,'version','2023-09-20 13:12:34'),(NULL,'Bugzilla::Field',6,'__create__',NULL,'rep_platform','2023-09-20 13:12:34'),(NULL,'Bugzilla::Field',7,'__create__',NULL,'bug_file_loc','2023-09-20 13:12:34'),(NULL,'Bugzilla::Field',8,'__create__',NULL,'op_sys','2023-09-20 13:12:34'),(NULL,'Bugzilla::Field',9,'__create__',NULL,'bug_status','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',10,'__create__',NULL,'status_whiteboard','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',11,'__create__',NULL,'keywords','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',12,'__create__',NULL,'resolution','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',13,'__create__',NULL,'bug_severity','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',14,'__create__',NULL,'priority','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',15,'__create__',NULL,'component','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',16,'__create__',NULL,'assigned_to','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',17,'__create__',NULL,'reporter','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',18,'__create__',NULL,'qa_contact','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',19,'__create__',NULL,'assigned_to_realname','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',20,'__create__',NULL,'reporter_realname','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',21,'__create__',NULL,'qa_contact_realname','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',22,'__create__',NULL,'cc','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',23,'__create__',NULL,'dependson','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',24,'__create__',NULL,'blocked','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',25,'__create__',NULL,'attachments.description','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',26,'__create__',NULL,'attachments.filename','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',27,'__create__',NULL,'attachments.mimetype','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',28,'__create__',NULL,'attachments.ispatch','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',29,'__create__',NULL,'attachments.isobsolete','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',30,'__create__',NULL,'attachments.isprivate','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',31,'__create__',NULL,'attachments.submitter','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',32,'__create__',NULL,'target_milestone','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',33,'__create__',NULL,'creation_ts','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',34,'__create__',NULL,'delta_ts','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',35,'__create__',NULL,'longdesc','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',36,'__create__',NULL,'longdescs.isprivate','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',37,'__create__',NULL,'longdescs.count','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',38,'__create__',NULL,'alias','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',39,'__create__',NULL,'everconfirmed','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',40,'__create__',NULL,'reporter_accessible','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',41,'__create__',NULL,'cclist_accessible','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',42,'__create__',NULL,'bug_group','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',43,'__create__',NULL,'estimated_time','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',44,'__create__',NULL,'remaining_time','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',45,'__create__',NULL,'deadline','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',46,'__create__',NULL,'commenter','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',47,'__create__',NULL,'flagtypes.name','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',48,'__create__',NULL,'requestees.login_name','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',49,'__create__',NULL,'setters.login_name','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',50,'__create__',NULL,'work_time','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',51,'__create__',NULL,'percentage_complete','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',52,'__create__',NULL,'content','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',53,'__create__',NULL,'attach_data.thedata','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',54,'__create__',NULL,'owner_idle_time','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',55,'__create__',NULL,'see_also','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',56,'__create__',NULL,'tag','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',57,'__create__',NULL,'last_visit_ts','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',58,'__create__',NULL,'comment_tag','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',59,'__create__',NULL,'days_elapsed','2023-09-20 13:12:35'),(NULL,'Bugzilla::Classification',1,'__create__',NULL,'Unclassified','2023-09-20 13:12:35'),(NULL,'Bugzilla::Group',1,'__create__',NULL,'admin','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',2,'__create__',NULL,'tweakparams','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',3,'__create__',NULL,'editusers','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',4,'__create__',NULL,'creategroups','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',5,'__create__',NULL,'editclassifications','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',6,'__create__',NULL,'editcomponents','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',7,'__create__',NULL,'editkeywords','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',8,'__create__',NULL,'editbugs','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',9,'__create__',NULL,'canconfirm','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',10,'__create__',NULL,'bz_canusewhineatothers','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',11,'__create__',NULL,'bz_canusewhines','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',12,'__create__',NULL,'bz_sudoers','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',13,'__create__',NULL,'bz_sudo_protect','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',14,'__create__',NULL,'bz_quip_moderators','2023-09-20 13:12:40'),(NULL,'Bugzilla::User',1,'__create__',NULL,'andreas@hasenkopf.xyz','2023-09-20 13:12:55'),(NULL,'Bugzilla::Product',1,'__create__',NULL,'TestProduct','2023-09-20 13:12:55'),(NULL,'Bugzilla::Version',1,'__create__',NULL,'unspecified','2023-09-20 13:12:55'),(NULL,'Bugzilla::Milestone',1,'__create__',NULL,'---','2023-09-20 13:12:55'),(NULL,'Bugzilla::Component',1,'__create__',NULL,'TestComponent','2023-09-20 13:12:55'),(1,'Bugzilla::Product',2,'__create__',NULL,'Red Hat Enterprise Linux 9','2023-11-27 12:25:54'),(1,'Bugzilla::Version',2,'__create__',NULL,'unspecified','2023-11-27 12:25:54'),(1,'Bugzilla::Milestone',2,'__create__',NULL,'---','2023-11-27 12:25:54'),(1,'Bugzilla::Component',2,'__create__',NULL,'python-bugzilla','2023-11-27 12:25:54'),(1,'Bugzilla::Version',3,'__create__',NULL,'9.0','2023-11-27 12:26:06'),(1,'Bugzilla::Version',4,'__create__',NULL,'9.1','2023-11-27 12:26:14'),(1,'Bugzilla::Product',3,'__create__',NULL,'SUSE Linux Enterprise Server 15 SP6','2023-11-27 12:29:18'),(1,'Bugzilla::Version',5,'__create__',NULL,'unspecified','2023-11-27 12:29:18'),(1,'Bugzilla::Milestone',3,'__create__',NULL,'---','2023-11-27 12:29:18'),(1,'Bugzilla::Component',3,'__create__',NULL,'Kernel','2023-11-27 12:29:18'),(1,'Bugzilla::Component',4,'__create__',NULL,'Containers','2023-11-27 12:29:46');
/*!40000 ALTER TABLE `audit_log` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `bug_group_map`
--
DROP TABLE IF EXISTS `bug_group_map`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `bug_group_map` (
`bug_id` mediumint(9) NOT NULL,
`group_id` mediumint(9) NOT NULL,
UNIQUE KEY `bug_group_map_bug_id_idx` (`bug_id`,`group_id`),
KEY `bug_group_map_group_id_idx` (`group_id`),
CONSTRAINT `fk_bug_group_map_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_bug_group_map_group_id_groups_id` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `bug_group_map`
--
LOCK TABLES `bug_group_map` WRITE;
/*!40000 ALTER TABLE `bug_group_map` DISABLE KEYS */;
/*!40000 ALTER TABLE `bug_group_map` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `bug_see_also`
--
DROP TABLE IF EXISTS `bug_see_also`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `bug_see_also` (
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
`bug_id` mediumint(9) NOT NULL,
`value` varchar(255) NOT NULL,
`class` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY `bug_see_also_bug_id_idx` (`bug_id`,`value`),
CONSTRAINT `fk_bug_see_also_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `bug_see_also`
--
LOCK TABLES `bug_see_also` WRITE;
/*!40000 ALTER TABLE `bug_see_also` DISABLE KEYS */;
/*!40000 ALTER TABLE `bug_see_also` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `bug_severity`
--
DROP TABLE IF EXISTS `bug_severity`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `bug_severity` (
`id` smallint(6) NOT NULL AUTO_INCREMENT,
`value` varchar(64) NOT NULL,
`sortkey` smallint(6) NOT NULL DEFAULT 0,
`isactive` tinyint(4) NOT NULL DEFAULT 1,
`visibility_value_id` smallint(6) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `bug_severity_value_idx` (`value`),
KEY `bug_severity_sortkey_idx` (`sortkey`,`value`),
KEY `bug_severity_visibility_value_id_idx` (`visibility_value_id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `bug_severity`
--
LOCK TABLES `bug_severity` WRITE;
/*!40000 ALTER TABLE `bug_severity` DISABLE KEYS */;
INSERT INTO `bug_severity` VALUES (1,'blocker',100,1,NULL),(2,'critical',200,1,NULL),(3,'major',300,1,NULL),(4,'normal',400,1,NULL),(5,'minor',500,1,NULL),(6,'trivial',600,1,NULL),(7,'enhancement',700,1,NULL);
/*!40000 ALTER TABLE `bug_severity` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `bug_status`
--
DROP TABLE IF EXISTS `bug_status`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `bug_status` (
`id` smallint(6) NOT NULL AUTO_INCREMENT,
`value` varchar(64) NOT NULL,
`sortkey` smallint(6) NOT NULL DEFAULT 0,
`isactive` tinyint(4) NOT NULL DEFAULT 1,
`visibility_value_id` smallint(6) DEFAULT NULL,
`is_open` tinyint(4) NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `bug_status_value_idx` (`value`),
KEY `bug_status_sortkey_idx` (`sortkey`,`value`),
KEY `bug_status_visibility_value_id_idx` (`visibility_value_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `bug_status`
--
LOCK TABLES `bug_status` WRITE;
/*!40000 ALTER TABLE `bug_status` DISABLE KEYS */;
INSERT INTO `bug_status` VALUES (1,'UNCONFIRMED',100,1,NULL,1),(2,'CONFIRMED',200,1,NULL,1),(3,'IN_PROGRESS',300,1,NULL,1),(4,'RESOLVED',400,1,NULL,0),(5,'VERIFIED',500,1,NULL,0);
/*!40000 ALTER TABLE `bug_status` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `bug_tag`
--
DROP TABLE IF EXISTS `bug_tag`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `bug_tag` (
`bug_id` mediumint(9) NOT NULL,
`tag_id` mediumint(9) NOT NULL,
UNIQUE KEY `bug_tag_bug_id_idx` (`bug_id`,`tag_id`),
KEY `fk_bug_tag_tag_id_tag_id` (`tag_id`),
CONSTRAINT `fk_bug_tag_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_bug_tag_tag_id_tag_id` FOREIGN KEY (`tag_id`) REFERENCES `tag` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `bug_tag`
--
LOCK TABLES `bug_tag` WRITE;
/*!40000 ALTER TABLE `bug_tag` DISABLE KEYS */;
/*!40000 ALTER TABLE `bug_tag` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `bug_user_last_visit`
--
DROP TABLE IF EXISTS `bug_user_last_visit`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `bug_user_last_visit` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` mediumint(9) NOT NULL,
`bug_id` mediumint(9) NOT NULL,
`last_visit_ts` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `bug_user_last_visit_idx` (`user_id`,`bug_id`),
KEY `bug_user_last_visit_last_visit_ts_idx` (`last_visit_ts`),
KEY `fk_bug_user_last_visit_bug_id_bugs_bug_id` (`bug_id`),
CONSTRAINT `fk_bug_user_last_visit_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_bug_user_last_visit_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `bug_user_last_visit`
--
LOCK TABLES `bug_user_last_visit` WRITE;
/*!40000 ALTER TABLE `bug_user_last_visit` DISABLE KEYS */;
INSERT INTO `bug_user_last_visit` VALUES (1,1,1,'2023-11-27 15:53:08'),(2,1,2,'2023-11-27 15:38:47');
/*!40000 ALTER TABLE `bug_user_last_visit` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `bugs`
--
DROP TABLE IF EXISTS `bugs`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `bugs` (
`bug_id` mediumint(9) NOT NULL AUTO_INCREMENT,
`assigned_to` mediumint(9) NOT NULL,
`bug_file_loc` mediumtext NOT NULL DEFAULT '',
`bug_severity` varchar(64) NOT NULL,
`bug_status` varchar(64) NOT NULL,
`creation_ts` datetime DEFAULT NULL,
`delta_ts` datetime NOT NULL,
`short_desc` varchar(255) NOT NULL,
`op_sys` varchar(64) NOT NULL,
`priority` varchar(64) NOT NULL,
`product_id` smallint(6) NOT NULL,
`rep_platform` varchar(64) NOT NULL,
`reporter` mediumint(9) NOT NULL,
`version` varchar(64) NOT NULL,
`component_id` mediumint(9) NOT NULL,
`resolution` varchar(64) NOT NULL DEFAULT '',
`target_milestone` varchar(64) NOT NULL DEFAULT '---',
`qa_contact` mediumint(9) DEFAULT NULL,
`status_whiteboard` mediumtext NOT NULL DEFAULT '',
`lastdiffed` datetime DEFAULT NULL,
`everconfirmed` tinyint(4) NOT NULL,
`reporter_accessible` tinyint(4) NOT NULL DEFAULT 1,
`cclist_accessible` tinyint(4) NOT NULL DEFAULT 1,
`estimated_time` decimal(7,2) NOT NULL DEFAULT 0.00,
`remaining_time` decimal(7,2) NOT NULL DEFAULT 0.00,
`deadline` datetime DEFAULT NULL,
PRIMARY KEY (`bug_id`),
KEY `bugs_assigned_to_idx` (`assigned_to`),
KEY `bugs_creation_ts_idx` (`creation_ts`),
KEY `bugs_delta_ts_idx` (`delta_ts`),
KEY `bugs_bug_severity_idx` (`bug_severity`),
KEY `bugs_bug_status_idx` (`bug_status`),
KEY `bugs_op_sys_idx` (`op_sys`),
KEY `bugs_priority_idx` (`priority`),
KEY `bugs_product_id_idx` (`product_id`),
KEY `bugs_reporter_idx` (`reporter`),
KEY `bugs_version_idx` (`version`),
KEY `bugs_component_id_idx` (`component_id`),
KEY `bugs_resolution_idx` (`resolution`),
KEY `bugs_target_milestone_idx` (`target_milestone`),
KEY `bugs_qa_contact_idx` (`qa_contact`),
CONSTRAINT `fk_bugs_assigned_to_profiles_userid` FOREIGN KEY (`assigned_to`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE,
CONSTRAINT `fk_bugs_component_id_components_id` FOREIGN KEY (`component_id`) REFERENCES `components` (`id`) ON UPDATE CASCADE,
CONSTRAINT `fk_bugs_product_id_products_id` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON UPDATE CASCADE,
CONSTRAINT `fk_bugs_qa_contact_profiles_userid` FOREIGN KEY (`qa_contact`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE,
CONSTRAINT `fk_bugs_reporter_profiles_userid` FOREIGN KEY (`reporter`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `bugs`
--
LOCK TABLES `bugs` WRITE;
/*!40000 ALTER TABLE `bugs` DISABLE KEYS */;
INSERT INTO `bugs` VALUES (1,1,'','major','IN_PROGRESS','2023-11-27 15:35:33','2023-11-27 15:53:04','ZeroDivisionError in function foo_bar()','Linux','---',3,'PC',1,'unspecified',4,'','---',NULL,'AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:L','2023-11-27 15:53:04',1,1,1,0.00,0.00,NULL),(2,1,'','enhancement','CONFIRMED','2023-11-27 15:38:45','2023-11-27 15:38:45','Expect the Spanish inquisition','Linux','---',2,'PC',1,'9.1',2,'','---',NULL,'','2023-11-27 15:38:45',1,1,1,0.00,0.00,NULL);
/*!40000 ALTER TABLE `bugs` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `bugs_activity`
--
DROP TABLE IF EXISTS `bugs_activity`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `bugs_activity` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`bug_id` mediumint(9) NOT NULL,
`attach_id` mediumint(9) DEFAULT NULL,
`who` mediumint(9) NOT NULL,
`bug_when` datetime NOT NULL,
`fieldid` mediumint(9) NOT NULL,
`added` varchar(255) DEFAULT NULL,
`removed` varchar(255) DEFAULT NULL,
`comment_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `bugs_activity_bug_id_idx` (`bug_id`),
KEY `bugs_activity_who_idx` (`who`),
KEY `bugs_activity_bug_when_idx` (`bug_when`),
KEY `bugs_activity_fieldid_idx` (`fieldid`),
KEY `bugs_activity_added_idx` (`added`),
KEY `bugs_activity_removed_idx` (`removed`),
KEY `fk_bugs_activity_attach_id_attachments_attach_id` (`attach_id`),
KEY `fk_bugs_activity_comment_id_longdescs_comment_id` (`comment_id`),
CONSTRAINT `fk_bugs_activity_attach_id_attachments_attach_id` FOREIGN KEY (`attach_id`) REFERENCES `attachments` (`attach_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_bugs_activity_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_bugs_activity_comment_id_longdescs_comment_id` FOREIGN KEY (`comment_id`) REFERENCES `longdescs` (`comment_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_bugs_activity_fieldid_fielddefs_id` FOREIGN KEY (`fieldid`) REFERENCES `fielddefs` (`id`) ON UPDATE CASCADE,
CONSTRAINT `fk_bugs_activity_who_profiles_userid` FOREIGN KEY (`who`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `bugs_activity`
--
LOCK TABLES `bugs_activity` WRITE;
/*!40000 ALTER TABLE `bugs_activity` DISABLE KEYS */;
INSERT INTO `bugs_activity` VALUES (1,1,NULL,1,'2023-11-27 15:45:09',9,'IN_PROGRESS','CONFIRMED',NULL),(2,1,NULL,1,'2023-11-27 15:47:58',10,'AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:L','',NULL),(3,1,NULL,1,'2023-11-27 15:53:04',38,'FOO-1','',NULL);
/*!40000 ALTER TABLE `bugs_activity` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `bugs_aliases`
--
DROP TABLE IF EXISTS `bugs_aliases`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `bugs_aliases` (
`alias` varchar(40) NOT NULL,
`bug_id` mediumint(9) DEFAULT NULL,
UNIQUE KEY `bugs_aliases_alias_idx` (`alias`),
KEY `bugs_aliases_bug_id_idx` (`bug_id`),
CONSTRAINT `fk_bugs_aliases_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `bugs_aliases`
--
LOCK TABLES `bugs_aliases` WRITE;
/*!40000 ALTER TABLE `bugs_aliases` DISABLE KEYS */;
INSERT INTO `bugs_aliases` VALUES ('FOO-1',1);
/*!40000 ALTER TABLE `bugs_aliases` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `bugs_fulltext`
--
DROP TABLE IF EXISTS `bugs_fulltext`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `bugs_fulltext` (
`bug_id` mediumint(9) NOT NULL,
`short_desc` varchar(255) NOT NULL,
`comments` mediumtext DEFAULT NULL,
`comments_noprivate` mediumtext DEFAULT NULL,
PRIMARY KEY (`bug_id`),
FULLTEXT KEY `bugs_fulltext_short_desc_idx` (`short_desc`),
FULLTEXT KEY `bugs_fulltext_comments_idx` (`comments`),
FULLTEXT KEY `bugs_fulltext_comments_noprivate_idx` (`comments_noprivate`),
CONSTRAINT `fk_bugs_fulltext_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `bugs_fulltext`
--
LOCK TABLES `bugs_fulltext` WRITE;
/*!40000 ALTER TABLE `bugs_fulltext` DISABLE KEYS */;
INSERT INTO `bugs_fulltext` VALUES (1,'ZeroDivisionError in function foo_bar()','Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\nAt vero eos et accusam et justo duo dolores et ea rebum.\nStet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.','Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\nAt vero eos et accusam et justo duo dolores et ea rebum.\nStet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.'),(2,'Expect the Spanish inquisition','Nobody expects the Spanish Inquisition! \n\nOur chief weapon is surprise, surprise and fear, fear and surprise. \n\nOur two weapons are fear and surprise, and ruthless efficiency. \n\nOur three weapons are fear and surprise and ruthless efficiency and an almost fanatical dedication to the pope.','Nobody expects the Spanish Inquisition! \n\nOur chief weapon is surprise, surprise and fear, fear and surprise. \n\nOur two weapons are fear and surprise, and ruthless efficiency. \n\nOur three weapons are fear and surprise and ruthless efficiency and an almost fanatical dedication to the pope.');
/*!40000 ALTER TABLE `bugs_fulltext` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `bz_schema`
--
DROP TABLE IF EXISTS `bz_schema`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `bz_schema` (
`schema_data` longblob NOT NULL,
`version` decimal(3,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `bz_schema`
--
LOCK TABLES `bz_schema` WRITE;
/*!40000 ALTER TABLE `bz_schema` DISABLE KEYS */;
INSERT INTO `bz_schema` VALUES ('$VAR1 = {\n \'attach_data\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'attach_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'attachments\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'thedata\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'LONGBLOB\'\n }\n ]\n },\n \'attachments\' => {\n \'FIELDS\' => [\n \'attach_id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'creation_ts\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'modification_time\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'description\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'TINYTEXT\'\n },\n \'mimetype\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'TINYTEXT\'\n },\n \'ispatch\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'filename\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n },\n \'submitter_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'isobsolete\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'isprivate\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ],\n \'INDEXES\' => [\n \'attachments_bug_id_idx\',\n [\n \'bug_id\'\n ],\n \'attachments_creation_ts_idx\',\n [\n \'creation_ts\'\n ],\n \'attachments_modification_time_idx\',\n [\n \'modification_time\'\n ],\n \'attachments_submitter_id_idx\',\n [\n \'submitter_id\',\n \'bug_id\'\n ]\n ]\n },\n \'audit_log\' => {\n \'FIELDS\' => [\n \'user_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'SET NULL\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'class\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n },\n \'object_id\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT4\'\n },\n \'field\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'removed\',\n {\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'added\',\n {\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'at_time\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n }\n ],\n \'INDEXES\' => [\n \'audit_log_class_idx\',\n [\n \'class\',\n \'at_time\'\n ]\n ]\n },\n \'bug_group_map\' => {\n \'FIELDS\' => [\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'group_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'groups\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'bug_group_map_bug_id_idx\',\n {\n \'FIELDS\' => [\n \'bug_id\',\n \'group_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'bug_group_map_group_id_idx\',\n [\n \'group_id\'\n ]\n ]\n },\n \'bug_see_also\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n },\n \'class\',\n {\n \'DEFAULT\' => \'\\\'\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n }\n ],\n \'INDEXES\' => [\n \'bug_see_also_bug_id_idx\',\n {\n \'FIELDS\' => [\n \'bug_id\',\n \'value\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'bug_severity\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'SMALLSERIAL\'\n },\n \'value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'sortkey\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'visibility_value_id\',\n {\n \'TYPE\' => \'INT2\'\n }\n ],\n \'INDEXES\' => [\n \'bug_severity_value_idx\',\n {\n \'FIELDS\' => [\n \'value\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'bug_severity_sortkey_idx\',\n [\n \'sortkey\',\n \'value\'\n ],\n \'bug_severity_visibility_value_id_idx\',\n [\n \'visibility_value_id\'\n ]\n ]\n },\n \'bug_status\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'SMALLSERIAL\'\n },\n \'value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'sortkey\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'visibility_value_id\',\n {\n \'TYPE\' => \'INT2\'\n },\n \'is_open\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ],\n \'INDEXES\' => [\n \'bug_status_value_idx\',\n {\n \'FIELDS\' => [\n \'value\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'bug_status_sortkey_idx\',\n [\n \'sortkey\',\n \'value\'\n ],\n \'bug_status_visibility_value_id_idx\',\n [\n \'visibility_value_id\'\n ]\n ]\n },\n \'bug_tag\' => {\n \'FIELDS\' => [\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'tag_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'tag\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'bug_tag_bug_id_idx\',\n {\n \'FIELDS\' => [\n \'bug_id\',\n \'tag_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'bug_user_last_visit\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'INTSERIAL\'\n },\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'last_visit_ts\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n }\n ],\n \'INDEXES\' => [\n \'bug_user_last_visit_idx\',\n {\n \'FIELDS\' => [\n \'user_id\',\n \'bug_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'bug_user_last_visit_last_visit_ts_idx\',\n [\n \'last_visit_ts\'\n ]\n ]\n },\n \'bugs\' => {\n \'FIELDS\' => [\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'assigned_to\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'bug_file_loc\',\n {\n \'DEFAULT\' => \'\\\'\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'bug_severity\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'bug_status\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'creation_ts\',\n {\n \'TYPE\' => \'DATETIME\'\n },\n \'delta_ts\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'short_desc\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n },\n \'op_sys\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'priority\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'product_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'TABLE\' => \'products\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'rep_platform\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'reporter\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'version\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'component_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'TABLE\' => \'components\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'resolution\',\n {\n \'DEFAULT\' => \'\\\'\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'target_milestone\',\n {\n \'DEFAULT\' => \'\\\'---\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'qa_contact\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'status_whiteboard\',\n {\n \'DEFAULT\' => \'\\\'\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'lastdiffed\',\n {\n \'TYPE\' => \'DATETIME\'\n },\n \'everconfirmed\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'reporter_accessible\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'cclist_accessible\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'estimated_time\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'decimal(7,2)\'\n },\n \'remaining_time\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'decimal(7,2)\'\n },\n \'deadline\',\n {\n \'TYPE\' => \'DATETIME\'\n }\n ],\n \'INDEXES\' => [\n \'bugs_assigned_to_idx\',\n [\n \'assigned_to\'\n ],\n \'bugs_creation_ts_idx\',\n [\n \'creation_ts\'\n ],\n \'bugs_delta_ts_idx\',\n [\n \'delta_ts\'\n ],\n \'bugs_bug_severity_idx\',\n [\n \'bug_severity\'\n ],\n \'bugs_bug_status_idx\',\n [\n \'bug_status\'\n ],\n \'bugs_op_sys_idx\',\n [\n \'op_sys\'\n ],\n \'bugs_priority_idx\',\n [\n \'priority\'\n ],\n \'bugs_product_id_idx\',\n [\n \'product_id\'\n ],\n \'bugs_reporter_idx\',\n [\n \'reporter\'\n ],\n \'bugs_version_idx\',\n [\n \'version\'\n ],\n \'bugs_component_id_idx\',\n [\n \'component_id\'\n ],\n \'bugs_resolution_idx\',\n [\n \'resolution\'\n ],\n \'bugs_target_milestone_idx\',\n [\n \'target_milestone\'\n ],\n \'bugs_qa_contact_idx\',\n [\n \'qa_contact\'\n ]\n ]\n },\n \'bugs_activity\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'INTSERIAL\'\n },\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'attach_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'attach_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'attachments\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'who\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'bug_when\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'fieldid\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'TABLE\' => \'fielddefs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'added\',\n {\n \'TYPE\' => \'varchar(255)\'\n },\n \'removed\',\n {\n \'TYPE\' => \'varchar(255)\'\n },\n \'comment_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'comment_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'longdescs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT4\'\n }\n ],\n \'INDEXES\' => [\n \'bugs_activity_bug_id_idx\',\n [\n \'bug_id\'\n ],\n \'bugs_activity_who_idx\',\n [\n \'who\'\n ],\n \'bugs_activity_bug_when_idx\',\n [\n \'bug_when\'\n ],\n \'bugs_activity_fieldid_idx\',\n [\n \'fieldid\'\n ],\n \'bugs_activity_added_idx\',\n [\n \'added\'\n ],\n \'bugs_activity_removed_idx\',\n [\n \'removed\'\n ]\n ]\n },\n \'bugs_aliases\' => {\n \'FIELDS\' => [\n \'alias\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(40)\'\n },\n \'bug_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'bugs_aliases_bug_id_idx\',\n [\n \'bug_id\'\n ],\n \'bugs_aliases_alias_idx\',\n {\n \'FIELDS\' => [\n \'alias\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'bugs_fulltext\' => {\n \'FIELDS\' => [\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'short_desc\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n },\n \'comments\',\n {\n \'TYPE\' => \'LONGTEXT\'\n },\n \'comments_noprivate\',\n {\n \'TYPE\' => \'LONGTEXT\'\n }\n ],\n \'INDEXES\' => [\n \'bugs_fulltext_short_desc_idx\',\n {\n \'FIELDS\' => [\n \'short_desc\'\n ],\n \'TYPE\' => \'FULLTEXT\'\n },\n \'bugs_fulltext_comments_idx\',\n {\n \'FIELDS\' => [\n \'comments\'\n ],\n \'TYPE\' => \'FULLTEXT\'\n },\n \'bugs_fulltext_comments_noprivate_idx\',\n {\n \'FIELDS\' => [\n \'comments_noprivate\'\n ],\n \'TYPE\' => \'FULLTEXT\'\n }\n ]\n },\n \'bz_schema\' => {\n \'FIELDS\' => [\n \'schema_data\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'LONGBLOB\'\n },\n \'version\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'decimal(3,2)\'\n }\n ]\n },\n \'category_group_map\' => {\n \'FIELDS\' => [\n \'category_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'series_categories\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'group_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'groups\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'category_group_map_category_id_idx\',\n {\n \'FIELDS\' => [\n \'category_id\',\n \'group_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'cc\' => {\n \'FIELDS\' => [\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'who\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'cc_bug_id_idx\',\n {\n \'FIELDS\' => [\n \'bug_id\',\n \'who\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'cc_who_idx\',\n [\n \'who\'\n ]\n ]\n },\n \'classifications\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'SMALLSERIAL\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'description\',\n {\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'sortkey\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n }\n ],\n \'INDEXES\' => [\n \'classifications_name_idx\',\n {\n \'FIELDS\' => [\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'component_cc\' => {\n \'FIELDS\' => [\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'component_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'components\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'component_cc_user_id_idx\',\n {\n \'FIELDS\' => [\n \'component_id\',\n \'user_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'components\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'product_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'products\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'initialowner\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'initialqacontact\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'SET NULL\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'description\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ],\n \'INDEXES\' => [\n \'components_product_id_idx\',\n {\n \'FIELDS\' => [\n \'product_id\',\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'components_name_idx\',\n [\n \'name\'\n ]\n ]\n },\n \'dependencies\' => {\n \'FIELDS\' => [\n \'blocked\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'dependson\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'dependencies_blocked_idx\',\n {\n \'FIELDS\' => [\n \'blocked\',\n \'dependson\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'dependencies_dependson_idx\',\n [\n \'dependson\'\n ]\n ]\n },\n \'duplicates\' => {\n \'FIELDS\' => [\n \'dupe_of\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'dupe\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ]\n },\n \'email_bug_ignore\' => {\n \'FIELDS\' => [\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'email_bug_ignore_user_id_idx\',\n {\n \'FIELDS\' => [\n \'user_id\',\n \'bug_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'email_setting\' => {\n \'FIELDS\' => [\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'relationship\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT1\'\n },\n \'event\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT1\'\n }\n ],\n \'INDEXES\' => [\n \'email_setting_user_id_idx\',\n {\n \'FIELDS\' => [\n \'user_id\',\n \'relationship\',\n \'event\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'field_visibility\' => {\n \'FIELDS\' => [\n \'field_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'fielddefs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'value_id\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n }\n ],\n \'INDEXES\' => [\n \'field_visibility_field_id_idx\',\n {\n \'FIELDS\' => [\n \'field_id\',\n \'value_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'fielddefs\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'type\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'custom\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'description\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'TINYTEXT\'\n },\n \'long_desc\',\n {\n \'DEFAULT\' => \'\\\'\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n },\n \'mailhead\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'sortkey\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'obsolete\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'enter_bug\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'buglist\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'visibility_field_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'TABLE\' => \'fielddefs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'value_field_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'TABLE\' => \'fielddefs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'reverse_desc\',\n {\n \'TYPE\' => \'TINYTEXT\'\n },\n \'is_mandatory\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'is_numeric\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ],\n \'INDEXES\' => [\n \'fielddefs_name_idx\',\n {\n \'FIELDS\' => [\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'fielddefs_sortkey_idx\',\n [\n \'sortkey\'\n ],\n \'fielddefs_value_field_id_idx\',\n [\n \'value_field_id\'\n ],\n \'fielddefs_is_mandatory_idx\',\n [\n \'is_mandatory\'\n ]\n ]\n },\n \'flagexclusions\' => {\n \'FIELDS\' => [\n \'type_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'flagtypes\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'product_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'products\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'component_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'components\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'flagexclusions_type_id_idx\',\n {\n \'FIELDS\' => [\n \'type_id\',\n \'product_id\',\n \'component_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'flaginclusions\' => {\n \'FIELDS\' => [\n \'type_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'flagtypes\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'product_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'products\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'component_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'components\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'flaginclusions_type_id_idx\',\n {\n \'FIELDS\' => [\n \'type_id\',\n \'product_id\',\n \'component_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'flags\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'type_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'flagtypes\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'status\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'char(1)\'\n },\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'attach_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'attach_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'attachments\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'creation_date\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'modification_date\',\n {\n \'TYPE\' => \'DATETIME\'\n },\n \'setter_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'requestee_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'flags_bug_id_idx\',\n [\n \'bug_id\',\n \'attach_id\'\n ],\n \'flags_setter_id_idx\',\n [\n \'setter_id\'\n ],\n \'flags_requestee_id_idx\',\n [\n \'requestee_id\'\n ],\n \'flags_type_id_idx\',\n [\n \'type_id\'\n ]\n ]\n },\n \'flagtypes\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(50)\'\n },\n \'description\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'cc_list\',\n {\n \'TYPE\' => \'varchar(200)\'\n },\n \'target_type\',\n {\n \'DEFAULT\' => \'\\\'b\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'char(1)\'\n },\n \'is_active\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'is_requestable\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'is_requesteeble\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'is_multiplicable\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'sortkey\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'grant_group_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'SET NULL\',\n \'TABLE\' => \'groups\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'request_group_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'SET NULL\',\n \'TABLE\' => \'groups\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ]\n },\n \'group_control_map\' => {\n \'FIELDS\' => [\n \'group_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'groups\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'product_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'products\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'entry\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'membercontrol\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT1\'\n },\n \'othercontrol\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT1\'\n },\n \'canedit\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'editcomponents\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'editbugs\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'canconfirm\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ],\n \'INDEXES\' => [\n \'group_control_map_product_id_idx\',\n {\n \'FIELDS\' => [\n \'product_id\',\n \'group_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'group_control_map_group_id_idx\',\n [\n \'group_id\'\n ]\n ]\n },\n \'group_group_map\' => {\n \'FIELDS\' => [\n \'member_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'groups\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'grantor_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'groups\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'grant_type\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT1\'\n }\n ],\n \'INDEXES\' => [\n \'group_group_map_member_id_idx\',\n {\n \'FIELDS\' => [\n \'member_id\',\n \'grantor_id\',\n \'grant_type\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'groups\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n },\n \'description\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'isbuggroup\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'userregexp\',\n {\n \'DEFAULT\' => \'\\\'\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'TINYTEXT\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'icon_url\',\n {\n \'TYPE\' => \'TINYTEXT\'\n }\n ],\n \'INDEXES\' => [\n \'groups_name_idx\',\n {\n \'FIELDS\' => [\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'keyworddefs\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'SMALLSERIAL\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'description\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'MEDIUMTEXT\'\n }\n ],\n \'INDEXES\' => [\n \'keyworddefs_name_idx\',\n {\n \'FIELDS\' => [\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'keywords\' => {\n \'FIELDS\' => [\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'keywordid\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'keyworddefs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n }\n ],\n \'INDEXES\' => [\n \'keywords_bug_id_idx\',\n {\n \'FIELDS\' => [\n \'bug_id\',\n \'keywordid\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'keywords_keywordid_idx\',\n [\n \'keywordid\'\n ]\n ]\n },\n \'login_failure\' => {\n \'FIELDS\' => [\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'login_time\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'ip_addr\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(40)\'\n }\n ],\n \'INDEXES\' => [\n \'login_failure_user_id_idx\',\n [\n \'user_id\'\n ]\n ]\n },\n \'logincookies\' => {\n \'FIELDS\' => [\n \'cookie\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'varchar(16)\'\n },\n \'userid\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'ipaddr\',\n {\n \'TYPE\' => \'varchar(40)\'\n },\n \'lastused\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n }\n ],\n \'INDEXES\' => [\n \'logincookies_lastused_idx\',\n [\n \'lastused\'\n ]\n ]\n },\n \'longdescs\' => {\n \'FIELDS\' => [\n \'comment_id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'INTSERIAL\'\n },\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'who\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'bug_when\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'work_time\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'decimal(7,2)\'\n },\n \'thetext\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'LONGTEXT\'\n },\n \'isprivate\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'already_wrapped\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'type\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'extra_data\',\n {\n \'TYPE\' => \'varchar(255)\'\n }\n ],\n \'INDEXES\' => [\n \'longdescs_bug_id_idx\',\n [\n \'bug_id\',\n \'work_time\'\n ],\n \'longdescs_who_idx\',\n [\n \'who\',\n \'bug_id\'\n ],\n \'longdescs_bug_when_idx\',\n [\n \'bug_when\'\n ]\n ]\n },\n \'longdescs_tags\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'comment_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'comment_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'longdescs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT4\'\n },\n \'tag\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(24)\'\n }\n ],\n \'INDEXES\' => [\n \'longdescs_tags_idx\',\n {\n \'FIELDS\' => [\n \'comment_id\',\n \'tag\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'longdescs_tags_activity\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'comment_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'comment_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'longdescs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT4\'\n },\n \'who\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'bug_when\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'added\',\n {\n \'TYPE\' => \'varchar(24)\'\n },\n \'removed\',\n {\n \'TYPE\' => \'varchar(24)\'\n }\n ],\n \'INDEXES\' => [\n \'longdescs_tags_activity_bug_id_idx\',\n [\n \'bug_id\'\n ]\n ]\n },\n \'longdescs_tags_weights\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'tag\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(24)\'\n },\n \'weight\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'longdescs_tags_weights_tag_idx\',\n {\n \'FIELDS\' => [\n \'tag\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'mail_staging\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'INTSERIAL\'\n },\n \'message\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'LONGBLOB\'\n }\n ]\n },\n \'milestones\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'product_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'products\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'sortkey\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ],\n \'INDEXES\' => [\n \'milestones_product_id_idx\',\n {\n \'FIELDS\' => [\n \'product_id\',\n \'value\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'namedqueries\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'userid\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'query\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'LONGTEXT\'\n }\n ],\n \'INDEXES\' => [\n \'namedqueries_userid_idx\',\n {\n \'FIELDS\' => [\n \'userid\',\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'namedqueries_link_in_footer\' => {\n \'FIELDS\' => [\n \'namedquery_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'namedqueries\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'namedqueries_link_in_footer_id_idx\',\n {\n \'FIELDS\' => [\n \'namedquery_id\',\n \'user_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'namedqueries_link_in_footer_userid_idx\',\n [\n \'user_id\'\n ]\n ]\n },\n \'namedquery_group_map\' => {\n \'FIELDS\' => [\n \'namedquery_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'namedqueries\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'group_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'groups\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'namedquery_group_map_namedquery_id_idx\',\n {\n \'FIELDS\' => [\n \'namedquery_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'namedquery_group_map_group_id_idx\',\n [\n \'group_id\'\n ]\n ]\n },\n \'op_sys\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'SMALLSERIAL\'\n },\n \'value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'sortkey\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'visibility_value_id\',\n {\n \'TYPE\' => \'INT2\'\n }\n ],\n \'INDEXES\' => [\n \'op_sys_value_idx\',\n {\n \'FIELDS\' => [\n \'value\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'op_sys_sortkey_idx\',\n [\n \'sortkey\',\n \'value\'\n ],\n \'op_sys_visibility_value_id_idx\',\n [\n \'visibility_value_id\'\n ]\n ]\n },\n \'priority\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'SMALLSERIAL\'\n },\n \'value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'sortkey\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'visibility_value_id\',\n {\n \'TYPE\' => \'INT2\'\n }\n ],\n \'INDEXES\' => [\n \'priority_value_idx\',\n {\n \'FIELDS\' => [\n \'value\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'priority_sortkey_idx\',\n [\n \'sortkey\',\n \'value\'\n ],\n \'priority_visibility_value_id_idx\',\n [\n \'visibility_value_id\'\n ]\n ]\n },\n \'products\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'SMALLSERIAL\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'classification_id\',\n {\n \'DEFAULT\' => \'1\',\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'classifications\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'description\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => 1,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'defaultmilestone\',\n {\n \'DEFAULT\' => \'\\\'---\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'allows_unconfirmed\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ],\n \'INDEXES\' => [\n \'products_name_idx\',\n {\n \'FIELDS\' => [\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'profile_search\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'INTSERIAL\'\n },\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'bug_list\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'list_order\',\n {\n \'TYPE\' => \'MEDIUMTEXT\'\n }\n ],\n \'INDEXES\' => [\n \'profile_search_user_id_idx\',\n [\n \'user_id\'\n ]\n ]\n },\n \'profile_setting\' => {\n \'FIELDS\' => [\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'setting_name\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'name\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'setting\',\n \'created\' => 1\n },\n \'TYPE\' => \'varchar(32)\'\n },\n \'setting_value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(32)\'\n }\n ],\n \'INDEXES\' => [\n \'profile_setting_value_unique_idx\',\n {\n \'FIELDS\' => [\n \'user_id\',\n \'setting_name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'profiles\' => {\n \'FIELDS\' => [\n \'userid\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'login_name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n },\n \'cryptpassword\',\n {\n \'TYPE\' => \'varchar(128)\'\n },\n \'realname\',\n {\n \'DEFAULT\' => \'\\\'\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n },\n \'disabledtext\',\n {\n \'DEFAULT\' => \'\\\'\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'disable_mail\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'mybugslink\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'extern_id\',\n {\n \'TYPE\' => \'varchar(64)\'\n },\n \'is_enabled\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'last_seen_date\',\n {\n \'TYPE\' => \'DATETIME\'\n }\n ],\n \'INDEXES\' => [\n \'profiles_login_name_idx\',\n {\n \'FIELDS\' => [\n \'login_name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'profiles_extern_id_idx\',\n {\n \'FIELDS\' => [\n \'extern_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'profiles_activity\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'userid\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'who\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'profiles_when\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'fieldid\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'TABLE\' => \'fielddefs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'oldvalue\',\n {\n \'TYPE\' => \'TINYTEXT\'\n },\n \'newvalue\',\n {\n \'TYPE\' => \'TINYTEXT\'\n }\n ],\n \'INDEXES\' => [\n \'profiles_activity_userid_idx\',\n [\n \'userid\'\n ],\n \'profiles_activity_profiles_when_idx\',\n [\n \'profiles_when\'\n ],\n \'profiles_activity_fieldid_idx\',\n [\n \'fieldid\'\n ]\n ]\n },\n \'quips\' => {\n \'FIELDS\' => [\n \'quipid\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'userid\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'SET NULL\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'quip\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(512)\'\n },\n \'approved\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ]\n },\n \'rep_platform\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'SMALLSERIAL\'\n },\n \'value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'sortkey\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'visibility_value_id\',\n {\n \'TYPE\' => \'INT2\'\n }\n ],\n \'INDEXES\' => [\n \'rep_platform_value_idx\',\n {\n \'FIELDS\' => [\n \'value\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'rep_platform_sortkey_idx\',\n [\n \'sortkey\',\n \'value\'\n ],\n \'rep_platform_visibility_value_id_idx\',\n [\n \'visibility_value_id\'\n ]\n ]\n },\n \'reports\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'query\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'LONGTEXT\'\n }\n ],\n \'INDEXES\' => [\n \'reports_user_id_idx\',\n {\n \'FIELDS\' => [\n \'user_id\',\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'resolution\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'SMALLSERIAL\'\n },\n \'value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'sortkey\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'visibility_value_id\',\n {\n \'TYPE\' => \'INT2\'\n }\n ],\n \'INDEXES\' => [\n \'resolution_value_idx\',\n {\n \'FIELDS\' => [\n \'value\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'resolution_sortkey_idx\',\n [\n \'sortkey\',\n \'value\'\n ],\n \'resolution_visibility_value_id_idx\',\n [\n \'visibility_value_id\'\n ]\n ]\n },\n \'series\' => {\n \'FIELDS\' => [\n \'series_id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'creator\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'category\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'series_categories\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'subcategory\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'series_categories\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'frequency\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'query\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'is_public\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ],\n \'INDEXES\' => [\n \'series_creator_idx\',\n [\n \'creator\'\n ],\n \'series_category_idx\',\n {\n \'FIELDS\' => [\n \'category\',\n \'subcategory\',\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'series_categories\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'SMALLSERIAL\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n }\n ],\n \'INDEXES\' => [\n \'series_categories_name_idx\',\n {\n \'FIELDS\' => [\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'series_data\' => {\n \'FIELDS\' => [\n \'series_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'series_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'series\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'series_date\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'series_value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'series_data_series_id_idx\',\n {\n \'FIELDS\' => [\n \'series_id\',\n \'series_date\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'setting\' => {\n \'FIELDS\' => [\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'varchar(32)\'\n },\n \'default_value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(32)\'\n },\n \'is_enabled\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'subclass\',\n {\n \'TYPE\' => \'varchar(32)\'\n }\n ]\n },\n \'setting_value\' => {\n \'FIELDS\' => [\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'name\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'setting\',\n \'created\' => 1\n },\n \'TYPE\' => \'varchar(32)\'\n },\n \'value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(32)\'\n },\n \'sortindex\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n }\n ],\n \'INDEXES\' => [\n \'setting_value_nv_unique_idx\',\n {\n \'FIELDS\' => [\n \'name\',\n \'value\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'setting_value_ns_unique_idx\',\n {\n \'FIELDS\' => [\n \'name\',\n \'sortindex\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'status_workflow\' => {\n \'FIELDS\' => [\n \'old_status\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bug_status\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'new_status\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bug_status\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'require_comment\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT1\'\n }\n ],\n \'INDEXES\' => [\n \'status_workflow_idx\',\n {\n \'FIELDS\' => [\n \'old_status\',\n \'new_status\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'tag\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'tag_user_id_idx\',\n {\n \'FIELDS\' => [\n \'user_id\',\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'tokens\' => {\n \'FIELDS\' => [\n \'userid\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'issuedate\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'token\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'varchar(16)\'\n },\n \'tokentype\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(16)\'\n },\n \'eventdata\',\n {\n \'TYPE\' => \'TINYTEXT\'\n }\n ],\n \'INDEXES\' => [\n \'tokens_userid_idx\',\n [\n \'userid\'\n ]\n ]\n },\n \'ts_error\' => {\n \'FIELDS\' => [\n \'error_time\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT4\'\n },\n \'jobid\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT4\'\n },\n \'message\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n },\n \'funcid\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT4\'\n }\n ],\n \'INDEXES\' => [\n \'ts_error_funcid_idx\',\n [\n \'funcid\',\n \'error_time\'\n ],\n \'ts_error_error_time_idx\',\n [\n \'error_time\'\n ],\n \'ts_error_jobid_idx\',\n [\n \'jobid\'\n ]\n ]\n },\n \'ts_exitstatus\' => {\n \'FIELDS\' => [\n \'jobid\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'INTSERIAL\'\n },\n \'funcid\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT4\'\n },\n \'status\',\n {\n \'TYPE\' => \'INT2\'\n },\n \'completion_time\',\n {\n \'TYPE\' => \'INT4\'\n },\n \'delete_after\',\n {\n \'TYPE\' => \'INT4\'\n }\n ],\n \'INDEXES\' => [\n \'ts_exitstatus_funcid_idx\',\n [\n \'funcid\'\n ],\n \'ts_exitstatus_delete_after_idx\',\n [\n \'delete_after\'\n ]\n ]\n },\n \'ts_funcmap\' => {\n \'FIELDS\' => [\n \'funcid\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'INTSERIAL\'\n },\n \'funcname\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n }\n ],\n \'INDEXES\' => [\n \'ts_funcmap_funcname_idx\',\n {\n \'FIELDS\' => [\n \'funcname\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'ts_job\' => {\n \'FIELDS\' => [\n \'jobid\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'INTSERIAL\'\n },\n \'funcid\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT4\'\n },\n \'arg\',\n {\n \'TYPE\' => \'LONGBLOB\'\n },\n \'uniqkey\',\n {\n \'TYPE\' => \'varchar(255)\'\n },\n \'insert_time\',\n {\n \'TYPE\' => \'INT4\'\n },\n \'run_after\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT4\'\n },\n \'grabbed_until\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT4\'\n },\n \'priority\',\n {\n \'TYPE\' => \'INT2\'\n },\n \'coalesce\',\n {\n \'TYPE\' => \'varchar(255)\'\n }\n ],\n \'INDEXES\' => [\n \'ts_job_funcid_idx\',\n {\n \'FIELDS\' => [\n \'funcid\',\n \'uniqkey\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'ts_job_run_after_idx\',\n [\n \'run_after\',\n \'funcid\'\n ],\n \'ts_job_coalesce_idx\',\n [\n \'coalesce\',\n \'funcid\'\n ]\n ]\n },\n \'ts_note\' => {\n \'FIELDS\' => [\n \'jobid\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT4\'\n },\n \'notekey\',\n {\n \'TYPE\' => \'varchar(255)\'\n },\n \'value\',\n {\n \'TYPE\' => \'LONGBLOB\'\n }\n ],\n \'INDEXES\' => [\n \'ts_note_jobid_idx\',\n {\n \'FIELDS\' => [\n \'jobid\',\n \'notekey\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'user_api_keys\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'INTSERIAL\'\n },\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'api_key\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'VARCHAR(40)\'\n },\n \'description\',\n {\n \'TYPE\' => \'VARCHAR(255)\'\n },\n \'revoked\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'last_used\',\n {\n \'TYPE\' => \'DATETIME\'\n }\n ],\n \'INDEXES\' => [\n \'user_api_keys_api_key_idx\',\n {\n \'FIELDS\' => [\n \'api_key\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'user_api_keys_user_id_idx\',\n [\n \'user_id\'\n ]\n ]\n },\n \'user_group_map\' => {\n \'FIELDS\' => [\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'group_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'groups\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'isbless\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'grant_type\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT1\'\n }\n ],\n \'INDEXES\' => [\n \'user_group_map_user_id_idx\',\n {\n \'FIELDS\' => [\n \'user_id\',\n \'group_id\',\n \'grant_type\',\n \'isbless\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'versions\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'product_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'products\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ],\n \'INDEXES\' => [\n \'versions_product_id_idx\',\n {\n \'FIELDS\' => [\n \'product_id\',\n \'value\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'watch\' => {\n \'FIELDS\' => [\n \'watcher\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'watched\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'watch_watcher_idx\',\n {\n \'FIELDS\' => [\n \'watcher\',\n \'watched\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'watch_watched_idx\',\n [\n \'watched\'\n ]\n ]\n },\n \'whine_events\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'owner_userid\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'subject\',\n {\n \'TYPE\' => \'varchar(128)\'\n },\n \'body\',\n {\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'mailifnobugs\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ]\n },\n \'whine_queries\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'eventid\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'whine_events\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'query_name\',\n {\n \'DEFAULT\' => \'\\\'\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'sortkey\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'onemailperbug\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'title\',\n {\n \'DEFAULT\' => \'\\\'\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(128)\'\n }\n ],\n \'INDEXES\' => [\n \'whine_queries_eventid_idx\',\n [\n \'eventid\'\n ]\n ]\n },\n \'whine_schedules\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'eventid\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'whine_events\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'run_day\',\n {\n \'TYPE\' => \'varchar(32)\'\n },\n \'run_time\',\n {\n \'TYPE\' => \'varchar(32)\'\n },\n \'run_next\',\n {\n \'TYPE\' => \'DATETIME\'\n },\n \'mailto\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT3\'\n },\n \'mailto_type\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n }\n ],\n \'INDEXES\' => [\n \'whine_schedules_run_next_idx\',\n [\n \'run_next\'\n ],\n \'whine_schedules_eventid_idx\',\n [\n \'eventid\'\n ]\n ]\n }\n };\n',3.00);
/*!40000 ALTER TABLE `bz_schema` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `category_group_map`
--
DROP TABLE IF EXISTS `category_group_map`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `category_group_map` (
`category_id` smallint(6) NOT NULL,
`group_id` mediumint(9) NOT NULL,
UNIQUE KEY `category_group_map_category_id_idx` (`category_id`,`group_id`),
KEY `fk_category_group_map_group_id_groups_id` (`group_id`),
CONSTRAINT `fk_category_group_map_category_id_series_categories_id` FOREIGN KEY (`category_id`) REFERENCES `series_categories` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_category_group_map_group_id_groups_id` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `category_group_map`
--
LOCK TABLES `category_group_map` WRITE;
/*!40000 ALTER TABLE `category_group_map` DISABLE KEYS */;
/*!40000 ALTER TABLE `category_group_map` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `cc`
--
DROP TABLE IF EXISTS `cc`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `cc` (
`bug_id` mediumint(9) NOT NULL,
`who` mediumint(9) NOT NULL,
UNIQUE KEY `cc_bug_id_idx` (`bug_id`,`who`),
KEY `cc_who_idx` (`who`),
CONSTRAINT `fk_cc_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_cc_who_profiles_userid` FOREIGN KEY (`who`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `cc`
--
LOCK TABLES `cc` WRITE;
/*!40000 ALTER TABLE `cc` DISABLE KEYS */;
/*!40000 ALTER TABLE `cc` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `classifications`
--
DROP TABLE IF EXISTS `classifications`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `classifications` (
`id` smallint(6) NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL,
`description` mediumtext DEFAULT NULL,
`sortkey` smallint(6) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `classifications_name_idx` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `classifications`
--
LOCK TABLES `classifications` WRITE;
/*!40000 ALTER TABLE `classifications` DISABLE KEYS */;
INSERT INTO `classifications` VALUES (1,'Unclassified','Not assigned to any classification',0);
/*!40000 ALTER TABLE `classifications` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `component_cc`
--
DROP TABLE IF EXISTS `component_cc`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `component_cc` (
`user_id` mediumint(9) NOT NULL,
`component_id` mediumint(9) NOT NULL,
UNIQUE KEY `component_cc_user_id_idx` (`component_id`,`user_id`),
KEY `fk_component_cc_user_id_profiles_userid` (`user_id`),
CONSTRAINT `fk_component_cc_component_id_components_id` FOREIGN KEY (`component_id`) REFERENCES `components` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_component_cc_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `component_cc`
--
LOCK TABLES `component_cc` WRITE;
/*!40000 ALTER TABLE `component_cc` DISABLE KEYS */;
/*!40000 ALTER TABLE `component_cc` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `components`
--
DROP TABLE IF EXISTS `components`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `components` (
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL,
`product_id` smallint(6) NOT NULL,
`initialowner` mediumint(9) NOT NULL,
`initialqacontact` mediumint(9) DEFAULT NULL,
`description` mediumtext NOT NULL,
`isactive` tinyint(4) NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `components_product_id_idx` (`product_id`,`name`),
KEY `components_name_idx` (`name`),
KEY `fk_components_initialqacontact_profiles_userid` (`initialqacontact`),
KEY `fk_components_initialowner_profiles_userid` (`initialowner`),
CONSTRAINT `fk_components_initialowner_profiles_userid` FOREIGN KEY (`initialowner`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE,
CONSTRAINT `fk_components_initialqacontact_profiles_userid` FOREIGN KEY (`initialqacontact`) REFERENCES `profiles` (`userid`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `fk_components_product_id_products_id` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `components`
--
LOCK TABLES `components` WRITE;
/*!40000 ALTER TABLE `components` DISABLE KEYS */;
INSERT INTO `components` VALUES (1,'TestComponent',1,1,NULL,'This is a test component in the test product database. This ought to be blown away and replaced with real stuff in a finished installation of Bugzilla.',1),(2,'python-bugzilla',2,1,NULL,'Lorem ipsum dolor sit amet',1),(3,'Kernel',3,1,NULL,'Lorem ipsum',1),(4,'Containers',3,1,NULL,'Lorem ipsum',1);
/*!40000 ALTER TABLE `components` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `dependencies`
--
DROP TABLE IF EXISTS `dependencies`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `dependencies` (
`blocked` mediumint(9) NOT NULL,
`dependson` mediumint(9) NOT NULL,
UNIQUE KEY `dependencies_blocked_idx` (`blocked`,`dependson`),
KEY `dependencies_dependson_idx` (`dependson`),
CONSTRAINT `fk_dependencies_blocked_bugs_bug_id` FOREIGN KEY (`blocked`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_dependencies_dependson_bugs_bug_id` FOREIGN KEY (`dependson`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `dependencies`
--
LOCK TABLES `dependencies` WRITE;
/*!40000 ALTER TABLE `dependencies` DISABLE KEYS */;
/*!40000 ALTER TABLE `dependencies` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `duplicates`
--
DROP TABLE IF EXISTS `duplicates`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `duplicates` (
`dupe_of` mediumint(9) NOT NULL,
`dupe` mediumint(9) NOT NULL,
PRIMARY KEY (`dupe`),
KEY `fk_duplicates_dupe_of_bugs_bug_id` (`dupe_of`),
CONSTRAINT `fk_duplicates_dupe_bugs_bug_id` FOREIGN KEY (`dupe`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_duplicates_dupe_of_bugs_bug_id` FOREIGN KEY (`dupe_of`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `duplicates`
--
LOCK TABLES `duplicates` WRITE;
/*!40000 ALTER TABLE `duplicates` DISABLE KEYS */;
/*!40000 ALTER TABLE `duplicates` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `email_bug_ignore`
--
DROP TABLE IF EXISTS `email_bug_ignore`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `email_bug_ignore` (
`user_id` mediumint(9) NOT NULL,
`bug_id` mediumint(9) NOT NULL,
UNIQUE KEY `email_bug_ignore_user_id_idx` (`user_id`,`bug_id`),
KEY `fk_email_bug_ignore_bug_id_bugs_bug_id` (`bug_id`),
CONSTRAINT `fk_email_bug_ignore_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_email_bug_ignore_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `email_bug_ignore`
--
LOCK TABLES `email_bug_ignore` WRITE;
/*!40000 ALTER TABLE `email_bug_ignore` DISABLE KEYS */;
/*!40000 ALTER TABLE `email_bug_ignore` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `email_setting`
--
DROP TABLE IF EXISTS `email_setting`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `email_setting` (
`user_id` mediumint(9) NOT NULL,
`relationship` tinyint(4) NOT NULL,
`event` tinyint(4) NOT NULL,
UNIQUE KEY `email_setting_user_id_idx` (`user_id`,`relationship`,`event`),
CONSTRAINT `fk_email_setting_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `email_setting`
--
LOCK TABLES `email_setting` WRITE;
/*!40000 ALTER TABLE `email_setting` DISABLE KEYS */;
INSERT INTO `email_setting` VALUES (1,0,0),(1,0,1),(1,0,2),(1,0,3),(1,0,4),(1,0,5),(1,0,6),(1,0,7),(1,0,9),(1,0,10),(1,0,11),(1,0,50),(1,1,0),(1,1,1),(1,1,2),(1,1,3),(1,1,4),(1,1,5),(1,1,6),(1,1,7),(1,1,9),(1,1,10),(1,1,11),(1,1,50),(1,2,0),(1,2,1),(1,2,2),(1,2,3),(1,2,4),(1,2,5),(1,2,6),(1,2,7),(1,2,8),(1,2,9),(1,2,10),(1,2,11),(1,2,50),(1,3,0),(1,3,1),(1,3,2),(1,3,3),(1,3,4),(1,3,5),(1,3,6),(1,3,7),(1,3,9),(1,3,10),(1,3,11),(1,3,50),(1,5,0),(1,5,1),(1,5,2),(1,5,3),(1,5,4),(1,5,5),(1,5,6),(1,5,7),(1,5,9),(1,5,10),(1,5,11),(1,5,50),(1,100,100),(1,100,101);
/*!40000 ALTER TABLE `email_setting` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `field_visibility`
--
DROP TABLE IF EXISTS `field_visibility`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `field_visibility` (
`field_id` mediumint(9) DEFAULT NULL,
`value_id` smallint(6) NOT NULL,
UNIQUE KEY `field_visibility_field_id_idx` (`field_id`,`value_id`),
CONSTRAINT `fk_field_visibility_field_id_fielddefs_id` FOREIGN KEY (`field_id`) REFERENCES `fielddefs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `field_visibility`
--
LOCK TABLES `field_visibility` WRITE;
/*!40000 ALTER TABLE `field_visibility` DISABLE KEYS */;
/*!40000 ALTER TABLE `field_visibility` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `fielddefs`
--
DROP TABLE IF EXISTS `fielddefs`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `fielddefs` (
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL,
`type` smallint(6) NOT NULL DEFAULT 0,
`custom` tinyint(4) NOT NULL DEFAULT 0,
`description` tinytext NOT NULL,
`long_desc` varchar(255) NOT NULL DEFAULT '',
`mailhead` tinyint(4) NOT NULL DEFAULT 0,
`sortkey` smallint(6) NOT NULL,
`obsolete` tinyint(4) NOT NULL DEFAULT 0,
`enter_bug` tinyint(4) NOT NULL DEFAULT 0,
`buglist` tinyint(4) NOT NULL DEFAULT 0,
`visibility_field_id` mediumint(9) DEFAULT NULL,
`value_field_id` mediumint(9) DEFAULT NULL,
`reverse_desc` tinytext DEFAULT NULL,
`is_mandatory` tinyint(4) NOT NULL DEFAULT 0,
`is_numeric` tinyint(4) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `fielddefs_name_idx` (`name`),
KEY `fielddefs_sortkey_idx` (`sortkey`),
KEY `fielddefs_value_field_id_idx` (`value_field_id`),
KEY `fielddefs_is_mandatory_idx` (`is_mandatory`),
KEY `fk_fielddefs_visibility_field_id_fielddefs_id` (`visibility_field_id`),
CONSTRAINT `fk_fielddefs_value_field_id_fielddefs_id` FOREIGN KEY (`value_field_id`) REFERENCES `fielddefs` (`id`) ON UPDATE CASCADE,
CONSTRAINT `fk_fielddefs_visibility_field_id_fielddefs_id` FOREIGN KEY (`visibility_field_id`) REFERENCES `fielddefs` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=60 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `fielddefs`
--
LOCK TABLES `fielddefs` WRITE;
/*!40000 ALTER TABLE `fielddefs` DISABLE KEYS */;
INSERT INTO `fielddefs` VALUES (1,'bug_id',0,0,'Bug #','',1,100,0,0,1,NULL,NULL,NULL,0,1),(2,'short_desc',0,0,'Summary','',1,200,0,0,1,NULL,NULL,NULL,1,0),(3,'classification',2,0,'Classification','',1,300,0,0,1,NULL,NULL,NULL,0,0),(4,'product',2,0,'Product','',1,400,0,0,1,NULL,NULL,NULL,1,0),(5,'version',0,0,'Version','',1,500,0,0,1,NULL,NULL,NULL,1,0),(6,'rep_platform',2,0,'Platform','',1,600,0,0,1,NULL,NULL,NULL,0,0),(7,'bug_file_loc',0,0,'URL','',1,700,0,0,1,NULL,NULL,NULL,0,0),(8,'op_sys',2,0,'OS/Version','',1,800,0,0,1,NULL,NULL,NULL,0,0),(9,'bug_status',2,0,'Status','',1,900,0,0,1,NULL,NULL,NULL,0,0),(10,'status_whiteboard',0,0,'Status Whiteboard','',1,1000,0,0,1,NULL,NULL,NULL,0,0),(11,'keywords',8,0,'Keywords','',1,1100,0,0,1,NULL,NULL,NULL,0,0),(12,'resolution',2,0,'Resolution','',0,1200,0,0,1,NULL,NULL,NULL,0,0),(13,'bug_severity',2,0,'Severity','',1,1300,0,0,1,NULL,NULL,NULL,0,0),(14,'priority',2,0,'Priority','',1,1400,0,0,1,NULL,NULL,NULL,0,0),(15,'component',2,0,'Component','',1,1500,0,0,1,NULL,NULL,NULL,1,0),(16,'assigned_to',0,0,'AssignedTo','',1,1600,0,0,1,NULL,NULL,NULL,0,0),(17,'reporter',0,0,'ReportedBy','',1,1700,0,0,1,NULL,NULL,NULL,0,0),(18,'qa_contact',0,0,'QAContact','',1,1800,0,0,1,NULL,NULL,NULL,0,0),(19,'assigned_to_realname',0,0,'AssignedToName','',0,1900,0,0,1,NULL,NULL,NULL,0,0),(20,'reporter_realname',0,0,'ReportedByName','',0,2000,0,0,1,NULL,NULL,NULL,0,0),(21,'qa_contact_realname',0,0,'QAContactName','',0,2100,0,0,1,NULL,NULL,NULL,0,0),(22,'cc',0,0,'CC','',1,2200,0,0,0,NULL,NULL,NULL,0,0),(23,'dependson',0,0,'Depends on','',1,2300,0,0,1,NULL,NULL,NULL,0,1),(24,'blocked',0,0,'Blocks','',1,2400,0,0,1,NULL,NULL,NULL,0,1),(25,'attachments.description',0,0,'Attachment description','',0,2500,0,0,0,NULL,NULL,NULL,0,0),(26,'attachments.filename',0,0,'Attachment filename','',0,2600,0,0,0,NULL,NULL,NULL,0,0),(27,'attachments.mimetype',0,0,'Attachment mime type','',0,2700,0,0,0,NULL,NULL,NULL,0,0),(28,'attachments.ispatch',0,0,'Attachment is patch','',0,2800,0,0,0,NULL,NULL,NULL,0,1),(29,'attachments.isobsolete',0,0,'Attachment is obsolete','',0,2900,0,0,0,NULL,NULL,NULL,0,1),(30,'attachments.isprivate',0,0,'Attachment is private','',0,3000,0,0,0,NULL,NULL,NULL,0,1),(31,'attachments.submitter',0,0,'Attachment creator','',0,3100,0,0,0,NULL,NULL,NULL,0,0),(32,'target_milestone',0,0,'Target Milestone','',1,3200,0,0,1,NULL,NULL,NULL,0,0),(33,'creation_ts',0,0,'Creation date','',0,3300,0,0,1,NULL,NULL,NULL,0,0),(34,'delta_ts',0,0,'Last changed date','',0,3400,0,0,1,NULL,NULL,NULL,0,0),(35,'longdesc',0,0,'Comment','',0,3500,0,0,0,NULL,NULL,NULL,0,0),(36,'longdescs.isprivate',0,0,'Comment is private','',0,3600,0,0,0,NULL,NULL,NULL,0,1),(37,'longdescs.count',0,0,'Number of Comments','',0,3700,0,0,1,NULL,NULL,NULL,0,1),(38,'alias',0,0,'Alias','',0,3800,0,0,1,NULL,NULL,NULL,0,0),(39,'everconfirmed',0,0,'Ever Confirmed','',0,3900,0,0,0,NULL,NULL,NULL,0,1),(40,'reporter_accessible',0,0,'Reporter Accessible','',0,4000,0,0,0,NULL,NULL,NULL,0,1),(41,'cclist_accessible',0,0,'CC Accessible','',0,4100,0,0,0,NULL,NULL,NULL,0,1),(42,'bug_group',0,0,'Group','',1,4200,0,0,0,NULL,NULL,NULL,0,0),(43,'estimated_time',0,0,'Estimated Hours','',1,4300,0,0,1,NULL,NULL,NULL,0,1),(44,'remaining_time',0,0,'Remaining Hours','',0,4400,0,0,1,NULL,NULL,NULL,0,1),(45,'deadline',5,0,'Deadline','',1,4500,0,0,1,NULL,NULL,NULL,0,0),(46,'commenter',0,0,'Commenter','',0,4600,0,0,0,NULL,NULL,NULL,0,0),(47,'flagtypes.name',0,0,'Flags','',0,4700,0,0,1,NULL,NULL,NULL,0,0),(48,'requestees.login_name',0,0,'Flag Requestee','',0,4800,0,0,0,NULL,NULL,NULL,0,0),(49,'setters.login_name',0,0,'Flag Setter','',0,4900,0,0,0,NULL,NULL,NULL,0,0),(50,'work_time',0,0,'Hours Worked','',0,5000,0,0,1,NULL,NULL,NULL,0,1),(51,'percentage_complete',0,0,'Percentage Complete','',0,5100,0,0,1,NULL,NULL,NULL,0,1),(52,'content',0,0,'Content','',0,5200,0,0,0,NULL,NULL,NULL,0,0),(53,'attach_data.thedata',0,0,'Attachment data','',0,5300,0,0,0,NULL,NULL,NULL,0,0),(54,'owner_idle_time',0,0,'Time Since Assignee Touched','',0,5400,0,0,0,NULL,NULL,NULL,0,0),(55,'see_also',7,0,'See Also','',0,5500,0,0,0,NULL,NULL,NULL,0,0),(56,'tag',8,0,'Personal Tags','',0,5600,0,0,1,NULL,NULL,NULL,0,0),(57,'last_visit_ts',5,0,'Last Visit','',0,5700,0,0,1,NULL,NULL,NULL,0,0),(58,'comment_tag',0,0,'Comment Tag','',0,5800,0,0,0,NULL,NULL,NULL,0,0),(59,'days_elapsed',0,0,'Days since bug changed','',0,5900,0,0,0,NULL,NULL,NULL,0,0);
/*!40000 ALTER TABLE `fielddefs` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `flagexclusions`
--
DROP TABLE IF EXISTS `flagexclusions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `flagexclusions` (
`type_id` mediumint(9) NOT NULL,
`product_id` smallint(6) DEFAULT NULL,
`component_id` mediumint(9) DEFAULT NULL,
UNIQUE KEY `flagexclusions_type_id_idx` (`type_id`,`product_id`,`component_id`),
KEY `fk_flagexclusions_component_id_components_id` (`component_id`),
KEY `fk_flagexclusions_product_id_products_id` (`product_id`),
CONSTRAINT `fk_flagexclusions_component_id_components_id` FOREIGN KEY (`component_id`) REFERENCES `components` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_flagexclusions_product_id_products_id` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_flagexclusions_type_id_flagtypes_id` FOREIGN KEY (`type_id`) REFERENCES `flagtypes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `flagexclusions`
--
LOCK TABLES `flagexclusions` WRITE;
/*!40000 ALTER TABLE `flagexclusions` DISABLE KEYS */;
/*!40000 ALTER TABLE `flagexclusions` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `flaginclusions`
--
DROP TABLE IF EXISTS `flaginclusions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `flaginclusions` (
`type_id` mediumint(9) NOT NULL,
`product_id` smallint(6) DEFAULT NULL,
`component_id` mediumint(9) DEFAULT NULL,
UNIQUE KEY `flaginclusions_type_id_idx` (`type_id`,`product_id`,`component_id`),
KEY `fk_flaginclusions_component_id_components_id` (`component_id`),
KEY `fk_flaginclusions_product_id_products_id` (`product_id`),
CONSTRAINT `fk_flaginclusions_component_id_components_id` FOREIGN KEY (`component_id`) REFERENCES `components` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_flaginclusions_product_id_products_id` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_flaginclusions_type_id_flagtypes_id` FOREIGN KEY (`type_id`) REFERENCES `flagtypes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `flaginclusions`
--
LOCK TABLES `flaginclusions` WRITE;
/*!40000 ALTER TABLE `flaginclusions` DISABLE KEYS */;
/*!40000 ALTER TABLE `flaginclusions` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `flags`
--
DROP TABLE IF EXISTS `flags`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `flags` (
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
`type_id` mediumint(9) NOT NULL,
`status` char(1) NOT NULL,
`bug_id` mediumint(9) NOT NULL,
`attach_id` mediumint(9) DEFAULT NULL,
`creation_date` datetime NOT NULL,
`modification_date` datetime DEFAULT NULL,
`setter_id` mediumint(9) NOT NULL,
`requestee_id` mediumint(9) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `flags_bug_id_idx` (`bug_id`,`attach_id`),
KEY `flags_setter_id_idx` (`setter_id`),
KEY `flags_requestee_id_idx` (`requestee_id`),
KEY `flags_type_id_idx` (`type_id`),
KEY `fk_flags_attach_id_attachments_attach_id` (`attach_id`),
CONSTRAINT `fk_flags_attach_id_attachments_attach_id` FOREIGN KEY (`attach_id`) REFERENCES `attachments` (`attach_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_flags_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_flags_requestee_id_profiles_userid` FOREIGN KEY (`requestee_id`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE,
CONSTRAINT `fk_flags_setter_id_profiles_userid` FOREIGN KEY (`setter_id`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE,
CONSTRAINT `fk_flags_type_id_flagtypes_id` FOREIGN KEY (`type_id`) REFERENCES `flagtypes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `flags`
--
LOCK TABLES `flags` WRITE;
/*!40000 ALTER TABLE `flags` DISABLE KEYS */;
/*!40000 ALTER TABLE `flags` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `flagtypes`
--
DROP TABLE IF EXISTS `flagtypes`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `flagtypes` (
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`description` mediumtext NOT NULL,
`cc_list` varchar(200) DEFAULT NULL,
`target_type` char(1) NOT NULL DEFAULT 'b',
`is_active` tinyint(4) NOT NULL DEFAULT 1,
`is_requestable` tinyint(4) NOT NULL DEFAULT 0,
`is_requesteeble` tinyint(4) NOT NULL DEFAULT 0,
`is_multiplicable` tinyint(4) NOT NULL DEFAULT 0,
`sortkey` smallint(6) NOT NULL DEFAULT 0,
`grant_group_id` mediumint(9) DEFAULT NULL,
`request_group_id` mediumint(9) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fk_flagtypes_request_group_id_groups_id` (`request_group_id`),
KEY `fk_flagtypes_grant_group_id_groups_id` (`grant_group_id`),
CONSTRAINT `fk_flagtypes_grant_group_id_groups_id` FOREIGN KEY (`grant_group_id`) REFERENCES `groups` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `fk_flagtypes_request_group_id_groups_id` FOREIGN KEY (`request_group_id`) REFERENCES `groups` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `flagtypes`
--
LOCK TABLES `flagtypes` WRITE;
/*!40000 ALTER TABLE `flagtypes` DISABLE KEYS */;
/*!40000 ALTER TABLE `flagtypes` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `group_control_map`
--
DROP TABLE IF EXISTS `group_control_map`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `group_control_map` (
`group_id` mediumint(9) NOT NULL,
`product_id` smallint(6) NOT NULL,
`entry` tinyint(4) NOT NULL DEFAULT 0,
`membercontrol` tinyint(4) NOT NULL DEFAULT 0,
`othercontrol` tinyint(4) NOT NULL DEFAULT 0,
`canedit` tinyint(4) NOT NULL DEFAULT 0,
`editcomponents` tinyint(4) NOT NULL DEFAULT 0,
`editbugs` tinyint(4) NOT NULL DEFAULT 0,
`canconfirm` tinyint(4) NOT NULL DEFAULT 0,
UNIQUE KEY `group_control_map_product_id_idx` (`product_id`,`group_id`),
KEY `group_control_map_group_id_idx` (`group_id`),
CONSTRAINT `fk_group_control_map_group_id_groups_id` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_group_control_map_product_id_products_id` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `group_control_map`
--
LOCK TABLES `group_control_map` WRITE;
/*!40000 ALTER TABLE `group_control_map` DISABLE KEYS */;
/*!40000 ALTER TABLE `group_control_map` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `group_group_map`
--
DROP TABLE IF EXISTS `group_group_map`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `group_group_map` (
`member_id` mediumint(9) NOT NULL,
`grantor_id` mediumint(9) NOT NULL,
`grant_type` tinyint(4) NOT NULL DEFAULT 0,
UNIQUE KEY `group_group_map_member_id_idx` (`member_id`,`grantor_id`,`grant_type`),
KEY `fk_group_group_map_grantor_id_groups_id` (`grantor_id`),
CONSTRAINT `fk_group_group_map_grantor_id_groups_id` FOREIGN KEY (`grantor_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_group_group_map_member_id_groups_id` FOREIGN KEY (`member_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `group_group_map`
--
LOCK TABLES `group_group_map` WRITE;
/*!40000 ALTER TABLE `group_group_map` DISABLE KEYS */;
INSERT INTO `group_group_map` VALUES (1,1,0),(1,1,1),(1,1,2),(1,2,0),(1,2,1),(1,2,2),(1,3,0),(1,3,1),(1,3,2),(1,4,0),(1,4,1),(1,4,2),(1,5,0),(1,5,1),(1,5,2),(1,6,0),(1,6,1),(1,6,2),(1,7,0),(1,7,1),(1,7,2),(1,8,0),(1,8,1),(1,8,2),(1,9,0),(1,9,1),(1,9,2),(1,10,0),(1,10,1),(1,10,2),(1,11,0),(1,11,1),(1,11,2),(8,11,0),(10,11,0),(1,12,0),(1,12,1),(1,12,2),(1,13,0),(1,13,1),(1,13,2),(12,13,0),(1,14,0),(1,14,1),(1,14,2);
/*!40000 ALTER TABLE `group_group_map` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `groups`
--
DROP TABLE IF EXISTS `groups`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `groups` (
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`description` mediumtext NOT NULL,
`isbuggroup` tinyint(4) NOT NULL,
`userregexp` tinytext NOT NULL DEFAULT '',
`isactive` tinyint(4) NOT NULL DEFAULT 1,
`icon_url` tinytext DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `groups_name_idx` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `groups`
--
LOCK TABLES `groups` WRITE;
/*!40000 ALTER TABLE `groups` DISABLE KEYS */;
INSERT INTO `groups` VALUES (1,'admin','Administrators',0,'',1,NULL),(2,'tweakparams','Can change Parameters',0,'',1,NULL),(3,'editusers','Can edit or disable users',0,'',1,NULL),(4,'creategroups','Can create and destroy groups',0,'',1,NULL),(5,'editclassifications','Can create, destroy, and edit classifications',0,'',1,NULL),(6,'editcomponents','Can create, destroy, and edit components',0,'',1,NULL),(7,'editkeywords','Can create, destroy, and edit keywords',0,'',1,NULL),(8,'editbugs','Can edit all bug fields',0,'.*',1,NULL),(9,'canconfirm','Can confirm a bug or mark it a duplicate',0,'',1,NULL),(10,'bz_canusewhineatothers','Can configure whine reports for other users',0,'',1,NULL),(11,'bz_canusewhines','User can configure whine reports for self',0,'',1,NULL),(12,'bz_sudoers','Can perform actions as other users',0,'',1,NULL),(13,'bz_sudo_protect','Can not be impersonated by other users',0,'',1,NULL),(14,'bz_quip_moderators','Can moderate quips',0,'',1,NULL);
/*!40000 ALTER TABLE `groups` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `keyworddefs`
--
DROP TABLE IF EXISTS `keyworddefs`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `keyworddefs` (
`id` smallint(6) NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL,
`description` mediumtext NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `keyworddefs_name_idx` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `keyworddefs`
--
LOCK TABLES `keyworddefs` WRITE;
/*!40000 ALTER TABLE `keyworddefs` DISABLE KEYS */;
/*!40000 ALTER TABLE `keyworddefs` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `keywords`
--
DROP TABLE IF EXISTS `keywords`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `keywords` (
`bug_id` mediumint(9) NOT NULL,
`keywordid` smallint(6) NOT NULL,
UNIQUE KEY `keywords_bug_id_idx` (`bug_id`,`keywordid`),
KEY `keywords_keywordid_idx` (`keywordid`),
CONSTRAINT `fk_keywords_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_keywords_keywordid_keyworddefs_id` FOREIGN KEY (`keywordid`) REFERENCES `keyworddefs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `keywords`
--
LOCK TABLES `keywords` WRITE;
/*!40000 ALTER TABLE `keywords` DISABLE KEYS */;
/*!40000 ALTER TABLE `keywords` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `login_failure`
--
DROP TABLE IF EXISTS `login_failure`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `login_failure` (
`user_id` mediumint(9) NOT NULL,
`login_time` datetime NOT NULL,
`ip_addr` varchar(40) NOT NULL,
KEY `login_failure_user_id_idx` (`user_id`),
CONSTRAINT `fk_login_failure_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `login_failure`
--
LOCK TABLES `login_failure` WRITE;
/*!40000 ALTER TABLE `login_failure` DISABLE KEYS */;
/*!40000 ALTER TABLE `login_failure` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `logincookies`
--
DROP TABLE IF EXISTS `logincookies`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `logincookies` (
`cookie` varchar(16) NOT NULL,
`userid` mediumint(9) NOT NULL,
`ipaddr` varchar(40) DEFAULT NULL,
`lastused` datetime NOT NULL,
PRIMARY KEY (`cookie`),
KEY `logincookies_lastused_idx` (`lastused`),
KEY `fk_logincookies_userid_profiles_userid` (`userid`),
CONSTRAINT `fk_logincookies_userid_profiles_userid` FOREIGN KEY (`userid`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `logincookies`
--
LOCK TABLES `logincookies` WRITE;
/*!40000 ALTER TABLE `logincookies` DISABLE KEYS */;
INSERT INTO `logincookies` VALUES ('Ypt6rPqHjG',1,NULL,'2023-11-27 15:53:08');
/*!40000 ALTER TABLE `logincookies` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `longdescs`
--
DROP TABLE IF EXISTS `longdescs`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `longdescs` (
`comment_id` int(11) NOT NULL AUTO_INCREMENT,
`bug_id` mediumint(9) NOT NULL,
`who` mediumint(9) NOT NULL,
`bug_when` datetime NOT NULL,
`work_time` decimal(7,2) NOT NULL DEFAULT 0.00,
`thetext` mediumtext NOT NULL,
`isprivate` tinyint(4) NOT NULL DEFAULT 0,
`already_wrapped` tinyint(4) NOT NULL DEFAULT 0,
`type` smallint(6) NOT NULL DEFAULT 0,
`extra_data` varchar(255) DEFAULT NULL,
PRIMARY KEY (`comment_id`),
KEY `longdescs_bug_id_idx` (`bug_id`,`work_time`),
KEY `longdescs_who_idx` (`who`,`bug_id`),
KEY `longdescs_bug_when_idx` (`bug_when`),
CONSTRAINT `fk_longdescs_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_longdescs_who_profiles_userid` FOREIGN KEY (`who`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `longdescs`
--
LOCK TABLES `longdescs` WRITE;
/*!40000 ALTER TABLE `longdescs` DISABLE KEYS */;
INSERT INTO `longdescs` VALUES (1,1,1,'2023-11-27 15:35:33',0.00,'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\nAt vero eos et accusam et justo duo dolores et ea rebum.',0,0,0,NULL),(2,1,1,'2023-11-27 15:37:05',0.00,'Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.',0,0,0,NULL),(3,2,1,'2023-11-27 15:38:45',0.00,'Nobody expects the Spanish Inquisition! \n\nOur chief weapon is surprise, surprise and fear, fear and surprise. \n\nOur two weapons are fear and surprise, and ruthless efficiency. \n\nOur three weapons are fear and surprise and ruthless efficiency and an almost fanatical dedication to the pope.',0,0,0,NULL);
/*!40000 ALTER TABLE `longdescs` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `longdescs_tags`
--
DROP TABLE IF EXISTS `longdescs_tags`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `longdescs_tags` (
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
`comment_id` int(11) DEFAULT NULL,
`tag` varchar(24) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `longdescs_tags_idx` (`comment_id`,`tag`),
CONSTRAINT `fk_longdescs_tags_comment_id_longdescs_comment_id` FOREIGN KEY (`comment_id`) REFERENCES `longdescs` (`comment_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `longdescs_tags`
--
LOCK TABLES `longdescs_tags` WRITE;
/*!40000 ALTER TABLE `longdescs_tags` DISABLE KEYS */;
/*!40000 ALTER TABLE `longdescs_tags` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `longdescs_tags_activity`
--
DROP TABLE IF EXISTS `longdescs_tags_activity`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `longdescs_tags_activity` (
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
`bug_id` mediumint(9) NOT NULL,
`comment_id` int(11) DEFAULT NULL,
`who` mediumint(9) NOT NULL,
`bug_when` datetime NOT NULL,
`added` varchar(24) DEFAULT NULL,
`removed` varchar(24) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `longdescs_tags_activity_bug_id_idx` (`bug_id`),
KEY `fk_longdescs_tags_activity_comment_id_longdescs_comment_id` (`comment_id`),
KEY `fk_longdescs_tags_activity_who_profiles_userid` (`who`),
CONSTRAINT `fk_longdescs_tags_activity_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_longdescs_tags_activity_comment_id_longdescs_comment_id` FOREIGN KEY (`comment_id`) REFERENCES `longdescs` (`comment_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_longdescs_tags_activity_who_profiles_userid` FOREIGN KEY (`who`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `longdescs_tags_activity`
--
LOCK TABLES `longdescs_tags_activity` WRITE;
/*!40000 ALTER TABLE `longdescs_tags_activity` DISABLE KEYS */;
/*!40000 ALTER TABLE `longdescs_tags_activity` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `longdescs_tags_weights`
--
DROP TABLE IF EXISTS `longdescs_tags_weights`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `longdescs_tags_weights` (
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
`tag` varchar(24) NOT NULL,
`weight` mediumint(9) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `longdescs_tags_weights_tag_idx` (`tag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `longdescs_tags_weights`
--
LOCK TABLES `longdescs_tags_weights` WRITE;
/*!40000 ALTER TABLE `longdescs_tags_weights` DISABLE KEYS */;
/*!40000 ALTER TABLE `longdescs_tags_weights` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `mail_staging`
--
DROP TABLE IF EXISTS `mail_staging`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `mail_staging` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`message` longblob NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `mail_staging`
--
LOCK TABLES `mail_staging` WRITE;
/*!40000 ALTER TABLE `mail_staging` DISABLE KEYS */;
/*!40000 ALTER TABLE `mail_staging` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `milestones`
--
DROP TABLE IF EXISTS `milestones`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `milestones` (
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
`product_id` smallint(6) NOT NULL,
`value` varchar(64) NOT NULL,
`sortkey` smallint(6) NOT NULL DEFAULT 0,
`isactive` tinyint(4) NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `milestones_product_id_idx` (`product_id`,`value`),
CONSTRAINT `fk_milestones_product_id_products_id` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `milestones`
--
LOCK TABLES `milestones` WRITE;
/*!40000 ALTER TABLE `milestones` DISABLE KEYS */;
INSERT INTO `milestones` VALUES (1,1,'---',0,1),(2,2,'---',0,1),(3,3,'---',0,1);
/*!40000 ALTER TABLE `milestones` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `namedqueries`
--
DROP TABLE IF EXISTS `namedqueries`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `namedqueries` (
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
`userid` mediumint(9) NOT NULL,
`name` varchar(64) NOT NULL,
`query` mediumtext NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `namedqueries_userid_idx` (`userid`,`name`),
CONSTRAINT `fk_namedqueries_userid_profiles_userid` FOREIGN KEY (`userid`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `namedqueries`
--
LOCK TABLES `namedqueries` WRITE;
/*!40000 ALTER TABLE `namedqueries` DISABLE KEYS */;
/*!40000 ALTER TABLE `namedqueries` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `namedqueries_link_in_footer`
--
DROP TABLE IF EXISTS `namedqueries_link_in_footer`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `namedqueries_link_in_footer` (
`namedquery_id` mediumint(9) NOT NULL,
`user_id` mediumint(9) NOT NULL,
UNIQUE KEY `namedqueries_link_in_footer_id_idx` (`namedquery_id`,`user_id`),
KEY `namedqueries_link_in_footer_userid_idx` (`user_id`),
CONSTRAINT `fk_namedqueries_link_in_footer_namedquery_id_namedqueries_id` FOREIGN KEY (`namedquery_id`) REFERENCES `namedqueries` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_namedqueries_link_in_footer_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `namedqueries_link_in_footer`
--
LOCK TABLES `namedqueries_link_in_footer` WRITE;
/*!40000 ALTER TABLE `namedqueries_link_in_footer` DISABLE KEYS */;
/*!40000 ALTER TABLE `namedqueries_link_in_footer` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `namedquery_group_map`
--
DROP TABLE IF EXISTS `namedquery_group_map`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `namedquery_group_map` (
`namedquery_id` mediumint(9) NOT NULL,
`group_id` mediumint(9) NOT NULL,
UNIQUE KEY `namedquery_group_map_namedquery_id_idx` (`namedquery_id`),
KEY `namedquery_group_map_group_id_idx` (`group_id`),
CONSTRAINT `fk_namedquery_group_map_group_id_groups_id` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_namedquery_group_map_namedquery_id_namedqueries_id` FOREIGN KEY (`namedquery_id`) REFERENCES `namedqueries` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `namedquery_group_map`
--
LOCK TABLES `namedquery_group_map` WRITE;
/*!40000 ALTER TABLE `namedquery_group_map` DISABLE KEYS */;
/*!40000 ALTER TABLE `namedquery_group_map` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `op_sys`
--
DROP TABLE IF EXISTS `op_sys`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `op_sys` (
`id` smallint(6) NOT NULL AUTO_INCREMENT,
`value` varchar(64) NOT NULL,
`sortkey` smallint(6) NOT NULL DEFAULT 0,
`isactive` tinyint(4) NOT NULL DEFAULT 1,
`visibility_value_id` smallint(6) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `op_sys_value_idx` (`value`),
KEY `op_sys_sortkey_idx` (`sortkey`,`value`),
KEY `op_sys_visibility_value_id_idx` (`visibility_value_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `op_sys`
--
LOCK TABLES `op_sys` WRITE;
/*!40000 ALTER TABLE `op_sys` DISABLE KEYS */;
INSERT INTO `op_sys` VALUES (1,'All',100,1,NULL),(2,'Windows',200,1,NULL),(3,'Mac OS',300,1,NULL),(4,'Linux',400,1,NULL),(5,'Other',500,1,NULL);
/*!40000 ALTER TABLE `op_sys` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `priority`
--
DROP TABLE IF EXISTS `priority`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `priority` (
`id` smallint(6) NOT NULL AUTO_INCREMENT,
`value` varchar(64) NOT NULL,
`sortkey` smallint(6) NOT NULL DEFAULT 0,
`isactive` tinyint(4) NOT NULL DEFAULT 1,
`visibility_value_id` smallint(6) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `priority_value_idx` (`value`),
KEY `priority_sortkey_idx` (`sortkey`,`value`),
KEY `priority_visibility_value_id_idx` (`visibility_value_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `priority`
--
LOCK TABLES `priority` WRITE;
/*!40000 ALTER TABLE `priority` DISABLE KEYS */;
INSERT INTO `priority` VALUES (1,'Highest',100,1,NULL),(2,'High',200,1,NULL),(3,'Normal',300,1,NULL),(4,'Low',400,1,NULL),(5,'Lowest',500,1,NULL),(6,'---',600,1,NULL);
/*!40000 ALTER TABLE `priority` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `products`
--
DROP TABLE IF EXISTS `products`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `products` (
`id` smallint(6) NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL,
`classification_id` smallint(6) NOT NULL DEFAULT 1,
`description` mediumtext NOT NULL,
`isactive` tinyint(4) NOT NULL DEFAULT 1,
`defaultmilestone` varchar(64) NOT NULL DEFAULT '---',
`allows_unconfirmed` tinyint(4) NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `products_name_idx` (`name`),
KEY `fk_products_classification_id_classifications_id` (`classification_id`),
CONSTRAINT `fk_products_classification_id_classifications_id` FOREIGN KEY (`classification_id`) REFERENCES `classifications` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `products`
--
LOCK TABLES `products` WRITE;
/*!40000 ALTER TABLE `products` DISABLE KEYS */;
INSERT INTO `products` VALUES (1,'TestProduct',1,'This is a test product. This ought to be blown away and replaced with real stuff in a finished installation of bugzilla.',1,'---',1),(2,'Red Hat Enterprise Linux 9',1,'Lorem ipsum',1,'---',1),(3,'SUSE Linux Enterprise Server 15 SP6',1,'Lorem ipsum dolor sit amet',1,'---',1);
/*!40000 ALTER TABLE `products` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `profile_search`
--
DROP TABLE IF EXISTS `profile_search`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `profile_search` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` mediumint(9) NOT NULL,
`bug_list` mediumtext NOT NULL,
`list_order` mediumtext DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `profile_search_user_id_idx` (`user_id`),
CONSTRAINT `fk_profile_search_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `profile_search`
--
LOCK TABLES `profile_search` WRITE;
/*!40000 ALTER TABLE `profile_search` DISABLE KEYS */;
INSERT INTO `profile_search` VALUES (1,1,'1','bug_status,priority,assigned_to,bug_id');
/*!40000 ALTER TABLE `profile_search` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `profile_setting`
--
DROP TABLE IF EXISTS `profile_setting`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `profile_setting` (
`user_id` mediumint(9) NOT NULL,
`setting_name` varchar(32) NOT NULL,
`setting_value` varchar(32) NOT NULL,
UNIQUE KEY `profile_setting_value_unique_idx` (`user_id`,`setting_name`),
KEY `fk_profile_setting_setting_name_setting_name` (`setting_name`),
CONSTRAINT `fk_profile_setting_setting_name_setting_name` FOREIGN KEY (`setting_name`) REFERENCES `setting` (`name`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_profile_setting_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `profile_setting`
--
LOCK TABLES `profile_setting` WRITE;
/*!40000 ALTER TABLE `profile_setting` DISABLE KEYS */;
/*!40000 ALTER TABLE `profile_setting` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `profiles`
--
DROP TABLE IF EXISTS `profiles`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `profiles` (
`userid` mediumint(9) NOT NULL AUTO_INCREMENT,
`login_name` varchar(255) NOT NULL,
`cryptpassword` varchar(128) DEFAULT NULL,
`realname` varchar(255) NOT NULL DEFAULT '',
`disabledtext` mediumtext NOT NULL DEFAULT '',
`disable_mail` tinyint(4) NOT NULL DEFAULT 0,
`mybugslink` tinyint(4) NOT NULL DEFAULT 1,
`extern_id` varchar(64) DEFAULT NULL,
`is_enabled` tinyint(4) NOT NULL DEFAULT 1,
`last_seen_date` datetime DEFAULT NULL,
PRIMARY KEY (`userid`),
UNIQUE KEY `profiles_login_name_idx` (`login_name`),
UNIQUE KEY `profiles_extern_id_idx` (`extern_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `profiles`
--
LOCK TABLES `profiles` WRITE;
/*!40000 ALTER TABLE `profiles` DISABLE KEYS */;
INSERT INTO `profiles` VALUES (1,'andreas@hasenkopf.xyz','2207pp7o,ialUTtf7x78ge5SbbN7+W+1lXGJBXmMlYt26C1egd4g{SHA-256}','Andreas','',0,1,NULL,1,'2023-11-27 00:00:00');
/*!40000 ALTER TABLE `profiles` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `profiles_activity`
--
DROP TABLE IF EXISTS `profiles_activity`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `profiles_activity` (
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
`userid` mediumint(9) NOT NULL,
`who` mediumint(9) NOT NULL,
`profiles_when` datetime NOT NULL,
`fieldid` mediumint(9) NOT NULL,
`oldvalue` tinytext DEFAULT NULL,
`newvalue` tinytext DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `profiles_activity_userid_idx` (`userid`),
KEY `profiles_activity_profiles_when_idx` (`profiles_when`),
KEY `profiles_activity_fieldid_idx` (`fieldid`),
KEY `fk_profiles_activity_who_profiles_userid` (`who`),
CONSTRAINT `fk_profiles_activity_fieldid_fielddefs_id` FOREIGN KEY (`fieldid`) REFERENCES `fielddefs` (`id`) ON UPDATE CASCADE,
CONSTRAINT `fk_profiles_activity_userid_profiles_userid` FOREIGN KEY (`userid`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_profiles_activity_who_profiles_userid` FOREIGN KEY (`who`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `profiles_activity`
--
LOCK TABLES `profiles_activity` WRITE;
/*!40000 ALTER TABLE `profiles_activity` DISABLE KEYS */;
INSERT INTO `profiles_activity` VALUES (1,1,1,'2023-09-20 13:12:55',33,NULL,'2023-09-20 13:12:55');
/*!40000 ALTER TABLE `profiles_activity` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `quips`
--
DROP TABLE IF EXISTS `quips`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `quips` (
`quipid` mediumint(9) NOT NULL AUTO_INCREMENT,
`userid` mediumint(9) DEFAULT NULL,
`quip` varchar(512) NOT NULL,
`approved` tinyint(4) NOT NULL DEFAULT 1,
PRIMARY KEY (`quipid`),
KEY `fk_quips_userid_profiles_userid` (`userid`),
CONSTRAINT `fk_quips_userid_profiles_userid` FOREIGN KEY (`userid`) REFERENCES `profiles` (`userid`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `quips`
--
LOCK TABLES `quips` WRITE;
/*!40000 ALTER TABLE `quips` DISABLE KEYS */;
/*!40000 ALTER TABLE `quips` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `rep_platform`
--
DROP TABLE IF EXISTS `rep_platform`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `rep_platform` (
`id` smallint(6) NOT NULL AUTO_INCREMENT,
`value` varchar(64) NOT NULL,
`sortkey` smallint(6) NOT NULL DEFAULT 0,
`isactive` tinyint(4) NOT NULL DEFAULT 1,
`visibility_value_id` smallint(6) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `rep_platform_value_idx` (`value`),
KEY `rep_platform_sortkey_idx` (`sortkey`,`value`),
KEY `rep_platform_visibility_value_id_idx` (`visibility_value_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `rep_platform`
--
LOCK TABLES `rep_platform` WRITE;
/*!40000 ALTER TABLE `rep_platform` DISABLE KEYS */;
INSERT INTO `rep_platform` VALUES (1,'All',100,1,NULL),(2,'PC',200,1,NULL),(3,'Macintosh',300,1,NULL),(4,'Other',400,1,NULL);
/*!40000 ALTER TABLE `rep_platform` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `reports`
--
DROP TABLE IF EXISTS `reports`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `reports` (
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
`user_id` mediumint(9) NOT NULL,
`name` varchar(64) NOT NULL,
`query` mediumtext NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `reports_user_id_idx` (`user_id`,`name`),
CONSTRAINT `fk_reports_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `reports`
--
LOCK TABLES `reports` WRITE;
/*!40000 ALTER TABLE `reports` DISABLE KEYS */;
/*!40000 ALTER TABLE `reports` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `resolution`
--
DROP TABLE IF EXISTS `resolution`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `resolution` (
`id` smallint(6) NOT NULL AUTO_INCREMENT,
`value` varchar(64) NOT NULL,
`sortkey` smallint(6) NOT NULL DEFAULT 0,
`isactive` tinyint(4) NOT NULL DEFAULT 1,
`visibility_value_id` smallint(6) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `resolution_value_idx` (`value`),
KEY `resolution_sortkey_idx` (`sortkey`,`value`),
KEY `resolution_visibility_value_id_idx` (`visibility_value_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `resolution`
--
LOCK TABLES `resolution` WRITE;
/*!40000 ALTER TABLE `resolution` DISABLE KEYS */;
INSERT INTO `resolution` VALUES (1,'',100,1,NULL),(2,'FIXED',200,1,NULL),(3,'INVALID',300,1,NULL),(4,'WONTFIX',400,1,NULL),(5,'DUPLICATE',500,1,NULL),(6,'WORKSFORME',600,1,NULL);
/*!40000 ALTER TABLE `resolution` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `series`
--
DROP TABLE IF EXISTS `series`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `series` (
`series_id` mediumint(9) NOT NULL AUTO_INCREMENT,
`creator` mediumint(9) DEFAULT NULL,
`category` smallint(6) NOT NULL,
`subcategory` smallint(6) NOT NULL,
`name` varchar(64) NOT NULL,
`frequency` smallint(6) NOT NULL,
`query` mediumtext NOT NULL,
`is_public` tinyint(4) NOT NULL DEFAULT 0,
PRIMARY KEY (`series_id`),
UNIQUE KEY `series_category_idx` (`category`,`subcategory`,`name`),
KEY `series_creator_idx` (`creator`),
KEY `fk_series_subcategory_series_categories_id` (`subcategory`),
CONSTRAINT `fk_series_category_series_categories_id` FOREIGN KEY (`category`) REFERENCES `series_categories` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_series_creator_profiles_userid` FOREIGN KEY (`creator`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_series_subcategory_series_categories_id` FOREIGN KEY (`subcategory`) REFERENCES `series_categories` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `series`
--
LOCK TABLES `series` WRITE;
/*!40000 ALTER TABLE `series` DISABLE KEYS */;
INSERT INTO `series` VALUES (1,1,1,2,'UNCONFIRMED',1,'bug_status=UNCONFIRMED&product=Red%20Hat%20Enterprise%20Linux%209',1),(2,1,1,2,'CONFIRMED',1,'bug_status=CONFIRMED&product=Red%20Hat%20Enterprise%20Linux%209',1),(3,1,1,2,'IN_PROGRESS',1,'bug_status=IN_PROGRESS&product=Red%20Hat%20Enterprise%20Linux%209',1),(4,1,1,2,'RESOLVED',1,'bug_status=RESOLVED&product=Red%20Hat%20Enterprise%20Linux%209',1),(5,1,1,2,'VERIFIED',1,'bug_status=VERIFIED&product=Red%20Hat%20Enterprise%20Linux%209',1),(6,1,1,2,'FIXED',1,'resolution=FIXED&product=Red%20Hat%20Enterprise%20Linux%209',1),(7,1,1,2,'INVALID',1,'resolution=INVALID&product=Red%20Hat%20Enterprise%20Linux%209',1),(8,1,1,2,'WONTFIX',1,'resolution=WONTFIX&product=Red%20Hat%20Enterprise%20Linux%209',1),(9,1,1,2,'DUPLICATE',1,'resolution=DUPLICATE&product=Red%20Hat%20Enterprise%20Linux%209',1),(10,1,1,2,'WORKSFORME',1,'resolution=WORKSFORME&product=Red%20Hat%20Enterprise%20Linux%209',1),(11,1,1,2,'All Open',1,'bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=IN_PROGRESS&product=Red%20Hat%20Enterprise%20Linux%209',1),(12,1,1,3,'All Open',1,'field0-0-0=resolution&type0-0-0=notregexp&value0-0-0=.&product=Red%20Hat%20Enterprise%20Linux%209&component=python-bugzilla',1),(13,1,1,3,'All Closed',1,'field0-0-0=resolution&type0-0-0=regexp&value0-0-0=.&product=Red%20Hat%20Enterprise%20Linux%209&component=python-bugzilla',1),(14,1,4,2,'UNCONFIRMED',1,'bug_status=UNCONFIRMED&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(15,1,4,2,'CONFIRMED',1,'bug_status=CONFIRMED&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(16,1,4,2,'IN_PROGRESS',1,'bug_status=IN_PROGRESS&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(17,1,4,2,'RESOLVED',1,'bug_status=RESOLVED&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(18,1,4,2,'VERIFIED',1,'bug_status=VERIFIED&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(19,1,4,2,'FIXED',1,'resolution=FIXED&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(20,1,4,2,'INVALID',1,'resolution=INVALID&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(21,1,4,2,'WONTFIX',1,'resolution=WONTFIX&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(22,1,4,2,'DUPLICATE',1,'resolution=DUPLICATE&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(23,1,4,2,'WORKSFORME',1,'resolution=WORKSFORME&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(24,1,4,2,'All Open',1,'bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=IN_PROGRESS&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(25,1,4,5,'All Open',1,'field0-0-0=resolution&type0-0-0=notregexp&value0-0-0=.&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6&component=Kernel',1),(26,1,4,5,'All Closed',1,'field0-0-0=resolution&type0-0-0=regexp&value0-0-0=.&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6&component=Kernel',1),(27,1,4,6,'All Open',1,'field0-0-0=resolution&type0-0-0=notregexp&value0-0-0=.&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6&component=Containers',1),(28,1,4,6,'All Closed',1,'field0-0-0=resolution&type0-0-0=regexp&value0-0-0=.&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6&component=Containers',1);
/*!40000 ALTER TABLE `series` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `series_categories`
--
DROP TABLE IF EXISTS `series_categories`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `series_categories` (
`id` smallint(6) NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `series_categories_name_idx` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `series_categories`
--
LOCK TABLES `series_categories` WRITE;
/*!40000 ALTER TABLE `series_categories` DISABLE KEYS */;
INSERT INTO `series_categories` VALUES (2,'-All-'),(6,'Containers'),(5,'Kernel'),(3,'python-bugzilla'),(1,'Red Hat Enterprise Linux 9'),(4,'SUSE Linux Enterprise Server 15 SP6');
/*!40000 ALTER TABLE `series_categories` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `series_data`
--
DROP TABLE IF EXISTS `series_data`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `series_data` (
`series_id` mediumint(9) NOT NULL,
`series_date` datetime NOT NULL,
`series_value` mediumint(9) NOT NULL,
UNIQUE KEY `series_data_series_id_idx` (`series_id`,`series_date`),
CONSTRAINT `fk_series_data_series_id_series_series_id` FOREIGN KEY (`series_id`) REFERENCES `series` (`series_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `series_data`
--
LOCK TABLES `series_data` WRITE;
/*!40000 ALTER TABLE `series_data` DISABLE KEYS */;
/*!40000 ALTER TABLE `series_data` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `setting`
--
DROP TABLE IF EXISTS `setting`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `setting` (
`name` varchar(32) NOT NULL,
`default_value` varchar(32) NOT NULL,
`is_enabled` tinyint(4) NOT NULL DEFAULT 1,
`subclass` varchar(32) DEFAULT NULL,
PRIMARY KEY (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `setting`
--
LOCK TABLES `setting` WRITE;
/*!40000 ALTER TABLE `setting` DISABLE KEYS */;
INSERT INTO `setting` VALUES ('bugmail_new_prefix','on',1,NULL),('comment_box_position','before_comments',1,NULL),('comment_sort_order','oldest_to_newest',1,NULL),('csv_colsepchar',',',1,NULL),('display_quips','on',1,NULL),('email_format','html',1,NULL),('lang','en',1,'Lang'),('possible_duplicates','on',1,NULL),('post_bug_submit_action','next_bug',1,NULL),('quicksearch_fulltext','on',1,NULL),('quote_replies','quoted_reply',1,NULL),('requestee_cc','on',1,NULL),('skin','Dusk',1,'Skin'),('state_addselfcc','cc_unless_role',1,NULL),('timezone','local',1,'Timezone'),('zoom_textareas','on',1,NULL);
/*!40000 ALTER TABLE `setting` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `setting_value`
--
DROP TABLE IF EXISTS `setting_value`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `setting_value` (
`name` varchar(32) NOT NULL,
`value` varchar(32) NOT NULL,
`sortindex` smallint(6) NOT NULL,
UNIQUE KEY `setting_value_nv_unique_idx` (`name`,`value`),
UNIQUE KEY `setting_value_ns_unique_idx` (`name`,`sortindex`),
CONSTRAINT `fk_setting_value_name_setting_name` FOREIGN KEY (`name`) REFERENCES `setting` (`name`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `setting_value`
--
LOCK TABLES `setting_value` WRITE;
/*!40000 ALTER TABLE `setting_value` DISABLE KEYS */;
INSERT INTO `setting_value` VALUES ('bugmail_new_prefix','on',5),('bugmail_new_prefix','off',10),('comment_box_position','before_comments',5),('comment_box_position','after_comments',10),('comment_sort_order','oldest_to_newest',5),('comment_sort_order','newest_to_oldest',10),('comment_sort_order','newest_to_oldest_desc_first',15),('csv_colsepchar',',',5),('csv_colsepchar',';',10),('display_quips','on',5),('display_quips','off',10),('email_format','html',5),('email_format','text_only',10),('possible_duplicates','on',5),('possible_duplicates','off',10),('post_bug_submit_action','next_bug',5),('post_bug_submit_action','same_bug',10),('post_bug_submit_action','nothing',15),('quicksearch_fulltext','on',5),('quicksearch_fulltext','off',10),('quote_replies','quoted_reply',5),('quote_replies','simple_reply',10),('quote_replies','off',15),('requestee_cc','on',5),('requestee_cc','off',10),('state_addselfcc','always',5),('state_addselfcc','never',10),('state_addselfcc','cc_unless_role',15),('zoom_textareas','on',5),('zoom_textareas','off',10);
/*!40000 ALTER TABLE `setting_value` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `status_workflow`
--
DROP TABLE IF EXISTS `status_workflow`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `status_workflow` (
`old_status` smallint(6) DEFAULT NULL,
`new_status` smallint(6) NOT NULL,
`require_comment` tinyint(4) NOT NULL DEFAULT 0,
UNIQUE KEY `status_workflow_idx` (`old_status`,`new_status`),
KEY `fk_status_workflow_new_status_bug_status_id` (`new_status`),
CONSTRAINT `fk_status_workflow_new_status_bug_status_id` FOREIGN KEY (`new_status`) REFERENCES `bug_status` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_status_workflow_old_status_bug_status_id` FOREIGN KEY (`old_status`) REFERENCES `bug_status` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `status_workflow`
--
LOCK TABLES `status_workflow` WRITE;
/*!40000 ALTER TABLE `status_workflow` DISABLE KEYS */;
INSERT INTO `status_workflow` VALUES (NULL,1,0),(NULL,2,0),(NULL,3,0),(1,2,0),(1,3,0),(1,4,0),(2,3,0),(2,4,0),(3,2,0),(3,4,0),(4,1,0),(4,2,0),(4,5,0),(5,1,0),(5,2,0);
/*!40000 ALTER TABLE `status_workflow` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `tag`
--
DROP TABLE IF EXISTS `tag`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `tag` (
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL,
`user_id` mediumint(9) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `tag_user_id_idx` (`user_id`,`name`),
CONSTRAINT `fk_tag_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `tag`
--
LOCK TABLES `tag` WRITE;
/*!40000 ALTER TABLE `tag` DISABLE KEYS */;
/*!40000 ALTER TABLE `tag` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `tokens`
--
DROP TABLE IF EXISTS `tokens`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `tokens` (
`userid` mediumint(9) DEFAULT NULL,
`issuedate` datetime NOT NULL,
`token` varchar(16) NOT NULL,
`tokentype` varchar(16) NOT NULL,
`eventdata` tinytext DEFAULT NULL,
PRIMARY KEY (`token`),
KEY `tokens_userid_idx` (`userid`),
CONSTRAINT `fk_tokens_userid_profiles_userid` FOREIGN KEY (`userid`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `tokens`
--
LOCK TABLES `tokens` WRITE;
/*!40000 ALTER TABLE `tokens` DISABLE KEYS */;
INSERT INTO `tokens` VALUES (1,'2023-11-27 15:46:15','5HVJhRRo6t','session','edit_parameters'),(1,'2023-11-27 12:25:54','a9MgwT7N7x','session','edit_product'),(1,'2023-11-27 15:42:50','CRSwDhzaXc','session','edit_parameters'),(1,'2023-11-27 12:29:18','DXFuAIZ5GH','session','edit_product'),(1,'2023-09-20 13:13:14','ery9F3ZaAV','session','edit_user_prefs'),(1,'2023-11-27 15:44:26','gnPazrbni2','session','edit_product'),(1,'2023-11-27 15:43:10','GZT1mYgIAF','session','edit_settings'),(1,'2023-11-27 15:42:57','hYkjAGXNIj','session','add_field'),(1,'2023-11-27 15:46:35','ibDe8MPzGE','session','edit_parameters'),(1,'2023-09-20 13:13:14','oukIJJwYod','api_token',''),(1,'2023-11-27 12:26:29','PIjhZLJ29K','session','edit_product'),(1,'2023-11-27 12:23:39','pIrqNpsRDo','api_token',''),(1,'2023-11-27 15:44:36','rkyOtDBxr4','session','edit_group_controls'),(1,'2023-09-20 13:13:20','VLrgLovfH9','session','edit_user_prefs'),(1,'2023-11-27 15:45:59','xgQpxIS10M','session','edit_user_prefs');
/*!40000 ALTER TABLE `tokens` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `ts_error`
--
DROP TABLE IF EXISTS `ts_error`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `ts_error` (
`error_time` int(11) NOT NULL,
`jobid` int(11) NOT NULL,
`message` varchar(255) NOT NULL,
`funcid` int(11) NOT NULL DEFAULT 0,
KEY `ts_error_funcid_idx` (`funcid`,`error_time`),
KEY `ts_error_error_time_idx` (`error_time`),
KEY `ts_error_jobid_idx` (`jobid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `ts_error`
--
LOCK TABLES `ts_error` WRITE;
/*!40000 ALTER TABLE `ts_error` DISABLE KEYS */;
/*!40000 ALTER TABLE `ts_error` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `ts_exitstatus`
--
DROP TABLE IF EXISTS `ts_exitstatus`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `ts_exitstatus` (
`jobid` int(11) NOT NULL AUTO_INCREMENT,
`funcid` int(11) NOT NULL DEFAULT 0,
`status` smallint(6) DEFAULT NULL,
`completion_time` int(11) DEFAULT NULL,
`delete_after` int(11) DEFAULT NULL,
PRIMARY KEY (`jobid`),
KEY `ts_exitstatus_funcid_idx` (`funcid`),
KEY `ts_exitstatus_delete_after_idx` (`delete_after`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `ts_exitstatus`
--
LOCK TABLES `ts_exitstatus` WRITE;
/*!40000 ALTER TABLE `ts_exitstatus` DISABLE KEYS */;
/*!40000 ALTER TABLE `ts_exitstatus` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `ts_funcmap`
--
DROP TABLE IF EXISTS `ts_funcmap`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `ts_funcmap` (
`funcid` int(11) NOT NULL AUTO_INCREMENT,
`funcname` varchar(255) NOT NULL,
PRIMARY KEY (`funcid`),
UNIQUE KEY `ts_funcmap_funcname_idx` (`funcname`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `ts_funcmap`
--
LOCK TABLES `ts_funcmap` WRITE;
/*!40000 ALTER TABLE `ts_funcmap` DISABLE KEYS */;
/*!40000 ALTER TABLE `ts_funcmap` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `ts_job`
--
DROP TABLE IF EXISTS `ts_job`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `ts_job` (
`jobid` int(11) NOT NULL AUTO_INCREMENT,
`funcid` int(11) NOT NULL,
`arg` longblob DEFAULT NULL,
`uniqkey` varchar(255) DEFAULT NULL,
`insert_time` int(11) DEFAULT NULL,
`run_after` int(11) NOT NULL,
`grabbed_until` int(11) NOT NULL,
`priority` smallint(6) DEFAULT NULL,
`coalesce` varchar(255) DEFAULT NULL,
PRIMARY KEY (`jobid`),
UNIQUE KEY `ts_job_funcid_idx` (`funcid`,`uniqkey`),
KEY `ts_job_run_after_idx` (`run_after`,`funcid`),
KEY `ts_job_coalesce_idx` (`coalesce`,`funcid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `ts_job`
--
LOCK TABLES `ts_job` WRITE;
/*!40000 ALTER TABLE `ts_job` DISABLE KEYS */;
/*!40000 ALTER TABLE `ts_job` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `ts_note`
--
DROP TABLE IF EXISTS `ts_note`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `ts_note` (
`jobid` int(11) NOT NULL,
`notekey` varchar(255) DEFAULT NULL,
`value` longblob DEFAULT NULL,
UNIQUE KEY `ts_note_jobid_idx` (`jobid`,`notekey`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `ts_note`
--
LOCK TABLES `ts_note` WRITE;
/*!40000 ALTER TABLE `ts_note` DISABLE KEYS */;
/*!40000 ALTER TABLE `ts_note` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `user_api_keys`
--
DROP TABLE IF EXISTS `user_api_keys`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `user_api_keys` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` mediumint(9) NOT NULL,
`api_key` varchar(40) NOT NULL,
`description` varchar(255) DEFAULT NULL,
`revoked` tinyint(4) NOT NULL DEFAULT 0,
`last_used` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_api_keys_api_key_idx` (`api_key`),
KEY `user_api_keys_user_id_idx` (`user_id`),
CONSTRAINT `fk_user_api_keys_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `user_api_keys`
--
LOCK TABLES `user_api_keys` WRITE;
/*!40000 ALTER TABLE `user_api_keys` DISABLE KEYS */;
INSERT INTO `user_api_keys` VALUES (1,1,'AxBntHGSL97CmoTahkey8RNyo2K65NEfJBuk5ATe','',0,NULL);
/*!40000 ALTER TABLE `user_api_keys` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `user_group_map`
--
DROP TABLE IF EXISTS `user_group_map`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `user_group_map` (
`user_id` mediumint(9) NOT NULL,
`group_id` mediumint(9) NOT NULL,
`isbless` tinyint(4) NOT NULL DEFAULT 0,
`grant_type` tinyint(4) NOT NULL DEFAULT 0,
UNIQUE KEY `user_group_map_user_id_idx` (`user_id`,`group_id`,`grant_type`,`isbless`),
KEY `fk_user_group_map_group_id_groups_id` (`group_id`),
CONSTRAINT `fk_user_group_map_group_id_groups_id` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_user_group_map_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `user_group_map`
--
LOCK TABLES `user_group_map` WRITE;
/*!40000 ALTER TABLE `user_group_map` DISABLE KEYS */;
INSERT INTO `user_group_map` VALUES (1,1,0,0),(1,1,1,0),(1,3,0,0),(1,8,0,2);
/*!40000 ALTER TABLE `user_group_map` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `versions`
--
DROP TABLE IF EXISTS `versions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `versions` (
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
`value` varchar(64) NOT NULL,
`product_id` smallint(6) NOT NULL,
`isactive` tinyint(4) NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `versions_product_id_idx` (`product_id`,`value`),
CONSTRAINT `fk_versions_product_id_products_id` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `versions`
--
LOCK TABLES `versions` WRITE;
/*!40000 ALTER TABLE `versions` DISABLE KEYS */;
INSERT INTO `versions` VALUES (1,'unspecified',1,1),(2,'unspecified',2,1),(3,'9.0',2,1),(4,'9.1',2,1),(5,'unspecified',3,1);
/*!40000 ALTER TABLE `versions` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `watch`
--
DROP TABLE IF EXISTS `watch`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `watch` (
`watcher` mediumint(9) NOT NULL,
`watched` mediumint(9) NOT NULL,
UNIQUE KEY `watch_watcher_idx` (`watcher`,`watched`),
KEY `watch_watched_idx` (`watched`),
CONSTRAINT `fk_watch_watched_profiles_userid` FOREIGN KEY (`watched`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_watch_watcher_profiles_userid` FOREIGN KEY (`watcher`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `watch`
--
LOCK TABLES `watch` WRITE;
/*!40000 ALTER TABLE `watch` DISABLE KEYS */;
/*!40000 ALTER TABLE `watch` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `whine_events`
--
DROP TABLE IF EXISTS `whine_events`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `whine_events` (
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
`owner_userid` mediumint(9) NOT NULL,
`subject` varchar(128) DEFAULT NULL,
`body` mediumtext DEFAULT NULL,
`mailifnobugs` tinyint(4) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `fk_whine_events_owner_userid_profiles_userid` (`owner_userid`),
CONSTRAINT `fk_whine_events_owner_userid_profiles_userid` FOREIGN KEY (`owner_userid`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `whine_events`
--
LOCK TABLES `whine_events` WRITE;
/*!40000 ALTER TABLE `whine_events` DISABLE KEYS */;
/*!40000 ALTER TABLE `whine_events` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `whine_queries`
--
DROP TABLE IF EXISTS `whine_queries`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `whine_queries` (
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
`eventid` mediumint(9) NOT NULL,
`query_name` varchar(64) NOT NULL DEFAULT '',
`sortkey` smallint(6) NOT NULL DEFAULT 0,
`onemailperbug` tinyint(4) NOT NULL DEFAULT 0,
`title` varchar(128) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `whine_queries_eventid_idx` (`eventid`),
CONSTRAINT `fk_whine_queries_eventid_whine_events_id` FOREIGN KEY (`eventid`) REFERENCES `whine_events` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `whine_queries`
--
LOCK TABLES `whine_queries` WRITE;
/*!40000 ALTER TABLE `whine_queries` DISABLE KEYS */;
/*!40000 ALTER TABLE `whine_queries` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `whine_schedules`
--
DROP TABLE IF EXISTS `whine_schedules`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `whine_schedules` (
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
`eventid` mediumint(9) NOT NULL,
`run_day` varchar(32) DEFAULT NULL,
`run_time` varchar(32) DEFAULT NULL,
`run_next` datetime DEFAULT NULL,
`mailto` mediumint(9) NOT NULL,
`mailto_type` smallint(6) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `whine_schedules_run_next_idx` (`run_next`),
KEY `whine_schedules_eventid_idx` (`eventid`),
CONSTRAINT `fk_whine_schedules_eventid_whine_events_id` FOREIGN KEY (`eventid`) REFERENCES `whine_events` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `whine_schedules`
--
LOCK TABLES `whine_schedules` WRITE;
/*!40000 ALTER TABLE `whine_schedules` DISABLE KEYS */;
/*!40000 ALTER TABLE `whine_schedules` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2023-11-27 16:56:56
070701000000AD000081A400000000000000000000000166EC67150000010A000000000000000000000000000000000000004A00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/services/bugzilla.conf<VirtualHost *:80>
DocumentRoot /var/www/webapps/bugzilla
<Directory /var/www/webapps/bugzilla>
AddHandler cgi-script .cgi
Options +ExecCGI
DirectoryIndex index.cgi index.html
AllowOverride All
</Directory>
</VirtualHost>
070701000000AE000081A400000000000000000000000166EC67150000003F000000000000000000000000000000000000004700000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/services/bugzillarc[localhost]
api_key = AxBntHGSL97CmoTahkey8RNyo2K65NEfJBuk5ATe
070701000000AF000081A400000000000000000000000166EC6715000001E7000000000000000000000000000000000000004800000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/services/localconfig$create_htaccess = 1;
$webservergroup = 'www-data';
$use_suexec = 0;
$db_driver = 'mysql';
$db_host = 'mariadb';
$db_name = 'bugs';
$db_user = 'bugs';
$db_pass = 'secret';
$db_port = 3306;
$db_sock = '';
$db_check = 1;
$db_mysql_ssl_ca_file = '';
$db_mysql_ssl_ca_path = '';
$db_mysql_ssl_client_cert = '';
$db_mysql_ssl_client_key = '';
$index_html = 0;
$interdiffbin = '';
$diffpath = '/usr/bin';
$site_wide_secret = 'oCIbi5WC04h86lW7L8fDcPCrVjb3JNeA2St94QlQtfjZrorjKmOdeVV0feHNDeFH';
070701000000B0000081A400000000000000000000000166EC671500000D6C000000000000000000000000000000000000004800000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/services/params.json{
"LDAPBaseDN" : "",
"LDAPbinddn" : "",
"LDAPfilter" : "",
"LDAPmailattribute" : "mail",
"LDAPserver" : "",
"LDAPstarttls" : "0",
"LDAPuidattribute" : "uid",
"RADIUS_NAS_IP" : "",
"RADIUS_email_suffix" : "",
"RADIUS_secret" : "",
"RADIUS_server" : "",
"ajax_user_autocompletion" : "1",
"allow_attachment_deletion" : "0",
"allow_attachment_display" : "0",
"allowbugdeletion" : "0",
"allowemailchange" : "1",
"allowuserdeletion" : "0",
"announcehtml" : "",
"attachment_base" : "",
"auth_env_email" : "",
"auth_env_id" : "",
"auth_env_realname" : "",
"chartgroup" : "editbugs",
"collapsed_comment_tags" : "obsolete, spam",
"comment_taggers_group" : "editbugs",
"commentonchange_resolution" : "0",
"commentonduplicate" : "0",
"confirmuniqueusermatch" : "1",
"cookiedomain" : "",
"cookiepath" : "/",
"createemailregexp" : ".*",
"debug_group" : "admin",
"default_search_limit" : "500",
"defaultopsys" : "",
"defaultplatform" : "",
"defaultpriority" : "---",
"defaultquery" : "resolution=---&emailassigned_to1=1&emailassigned_to2=1&emailreporter2=1&emailcc2=1&emailqa_contact2=1&emaillongdesc3=1&order=Importance&long_desc_type=substring",
"defaultseverity" : "enhancement",
"duplicate_or_move_bug_status" : "RESOLVED",
"emailregexp" : "^[\\w\\.\\+\\-=']+@[\\w\\.\\-]+\\.[\\w\\-]+$",
"emailregexpdesc" : "A legal address must contain exactly one '@', and at least one '.' after the @.",
"emailsuffix" : "",
"font_file" : "",
"globalwatchers" : "",
"inbound_proxies" : "",
"insidergroup" : "",
"last_visit_keep_days" : "10",
"letsubmitterchoosemilestone" : "1",
"letsubmitterchoosepriority" : "1",
"mail_delivery_method" : "None",
"mailfrom" : "bugzilla-daemon",
"maintainer" : "andreas@hasenkopf.xyz",
"makeproductgroups" : "0",
"max_search_results" : "10000",
"maxattachmentsize" : "1000",
"maxlocalattachment" : "0",
"maxusermatches" : "1000",
"memcached_namespace" : "bugzilla:",
"memcached_servers" : "",
"musthavemilestoneonaccept" : "0",
"mybugstemplate" : "buglist.cgi?resolution=---&emailassigned_to1=1&emailreporter1=1&emailtype1=exact&email1=%userid%",
"noresolveonopenblockers" : "0",
"or_groups" : "1",
"password_check_on_login" : "1",
"password_complexity" : "no_constraints",
"proxy_url" : "",
"querysharegroup" : "editbugs",
"quip_list_entry_control" : "open",
"rememberlogin" : "on",
"requirelogin" : "0",
"search_allow_no_criteria" : "1",
"shadowdb" : "",
"shadowdbhost" : "",
"shadowdbport" : "3306",
"shadowdbsock" : "",
"shutdownhtml" : "",
"smtp_debug" : "0",
"smtp_password" : "",
"smtp_ssl" : "0",
"smtp_username" : "",
"smtpserver" : "localhost",
"ssl_redirect" : "0",
"sslbase" : "",
"strict_isolation" : "0",
"strict_transport_security" : "off",
"timetrackinggroup" : "editbugs",
"upgrade_notification" : "latest_stable_release",
"urlbase" : "",
"use_mailer_queue" : "0",
"use_see_also" : "1",
"useclassification" : "0",
"usemenuforusers" : "0",
"useqacontact" : "0",
"user_info_class" : "CGI",
"user_verify_class" : "DB",
"usestatuswhiteboard" : "0",
"usetargetmilestone" : "0",
"usevisibilitygroups" : "0",
"utf8" : "1",
"webdotbase" : "",
"webservice_email_filter" : "0",
"whinedays" : "7"
}
070701000000B1000081A400000000000000000000000166EC6715000006BE000000000000000000000000000000000000004B00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/test_api_attachments.py# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
#
import os
import pytest
import tests
import tests.mockbackend
def test_api_attachments():
# misc coverage testing for Bugzilla attachment APIs
fakebz = tests.mockbackend.make_bz(
bug_attachment_get_all_args=(
"data/mockargs/test_attachments_getall1.txt"),
bug_attachment_get_all_return={},
bug_attachment_update_args=(
"data/mockargs/test_attachments_update1.txt"),
bug_attachment_update_return={},
bug_attachment_get_args=(
"data/mockargs/test_attachments_get1.txt"),
bug_attachment_get_return=(
"data/mockreturn/test_attach_get1.txt"),
bug_attachment_create_args=(
"data/mockargs/test_api_attachments_create1.txt"),
bug_attachment_create_return={
"attachments": {"123456": {}, "456789": []}},
)
# coverage for include/exclude handling
fakebz.get_attachments([123456], None,
include_fields=["foo"], exclude_fields="bar")
# coverage for updateattachment
fakebz.updateattachmentflags(None, "112233", "needinfo",
value="foobar", is_patch=True)
# coverage for openattachment
fobj = fakebz.openattachment(502352)
assert "Hooray" in str(fobj.read())
# Error on bad input type
with pytest.raises(TypeError):
fakebz.attachfile([123456], None, "some desc")
# Misc attachfile() pieces
attachfile = os.path.dirname(__file__) + "/data/bz-attach-get1.txt"
ret = fakebz.attachfile([123456], attachfile, "some desc",
isprivate=True)
ret.sort()
assert ret == [123456, 456789]
070701000000B2000081A400000000000000000000000166EC67150000152D000000000000000000000000000000000000004900000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/test_api_authfiles.py#
# Copyright Red Hat, Inc. 2012
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
#
"""
Test miscellaneous API bits
"""
import os
import shutil
import tempfile
import tests
import tests.mockbackend
import tests.utils
def test_tokenfile(monkeypatch):
dirname = os.path.dirname(__file__)
monkeypatch.setitem(os.environ, "HOME", dirname + "/data/homedir")
bz = tests.mockbackend.make_bz(bz_kwargs={"use_creds": True})
token = dirname + "/data/homedir/.cache/python-bugzilla/bugzillatoken"
assert token == bz.tokenfile
del bz.tokenfile
assert bz.tokenfile is None
assert bz.cookiefile is None
def test_readconfig():
# Testing for bugzillarc handling
bzapi = tests.mockbackend.make_bz(version="4.4.0", rhbz=True)
bzapi.url = "example.com"
temp = tempfile.NamedTemporaryFile(mode="w")
def _check(user, password, api_key, cert):
assert bzapi.user == user
assert bzapi.password == password
assert bzapi.api_key == api_key
assert bzapi.cert == cert
def _write(c):
temp.seek(0)
temp.write(c)
temp.flush()
return temp.name
# Check readconfig normal usage
content = """
[example.com]
foo=1
user=test1
password=test2
api_key=123abc
cert=/a/b/c
someunknownkey=someval
"""
bzapi.readconfig(_write(content))
_check("test1", "test2", "123abc", "/a/b/c")
# Check loading a different URL, that values aren't overwritten
content = """
[foo.example.com]
user=test3
password=test4
api_key=567abc
cert=/newpath
"""
bzapi.readconfig(_write(content))
_check("test1", "test2", "123abc", "/a/b/c")
# Change URL, but check readconfig with overwrite=False
bzapi.url = "foo.example.com"
bzapi.readconfig(temp.name, overwrite=False)
_check("test1", "test2", "123abc", "/a/b/c")
# With default overwrite=True, values will be updated
# Alter the config to have a / in the hostname, which hits different code
content = content.replace("example.com", "example.com/xmlrpc.cgi")
bzapi.url = "https://foo.example.com/xmlrpc.cgi"
bzapi.readconfig(_write(content))
_check("test3", "test4", "567abc", "/newpath")
# Confirm nothing overwritten for a totally different URL
bzapi.user = None
bzapi.password = None
bzapi.api_key = None
bzapi.cert = None
bzapi.url = "bugzilla.redhat.com"
bzapi.readconfig(temp.name)
_check(None, None, None, None)
# Test confipath overwrite
assert [temp.name] == bzapi.configpath
del bzapi.configpath
assert [] == bzapi.configpath
bzapi.readconfig()
_check(None, None, None, None)
def test_authfiles_saving(monkeypatch):
tmpdir = tempfile.mkdtemp()
try:
monkeypatch.setitem(os.environ, "HOME", tmpdir)
bzapi = tests.mockbackend.make_bz(
bz_kwargs={"use_creds": True, "cert": "foo-fake-cert"})
bzapi.connect("https://example.com/fakebz")
bzapi.cert = "foo-fake-path"
backend = bzapi._backend # pylint: disable=protected-access
bsession = backend._bugzillasession # pylint: disable=protected-access
btokencache = bzapi._tokencache # pylint: disable=protected-access
# token testing, with repetitions to hit various code paths
btokencache.set_value(bzapi.url, None)
assert "Bugzilla_token" not in bsession.get_auth_params()
btokencache.set_value(bzapi.url, "MY-FAKE-TOKEN")
assert bsession.get_auth_params()["Bugzilla_token"] == "MY-FAKE-TOKEN"
btokencache.set_value(bzapi.url, "MY-FAKE-TOKEN")
btokencache.set_value(bzapi.url, None)
assert "Bugzilla_token" not in bsession.get_auth_params()
btokencache.set_value(bzapi.url, "MY-FAKE-TOKEN")
dirname = os.path.dirname(__file__) + "/data/authfiles/"
output_token = dirname + "output-token.txt"
tests.utils.diff_compare(open(bzapi.tokenfile).read(), output_token)
# Make sure file can re-read them and not error
bzapi = tests.mockbackend.make_bz(
bz_kwargs={"use_creds": True,
"tokenfile": output_token})
assert bzapi.tokenfile == output_token
# Test rcfile writing for api_key
rcfile = bzapi._rcfile # pylint: disable=protected-access
bzapi.url = "https://example.com/fake"
rcfile.save_api_key(bzapi.url, "TEST-API-KEY")
rcfilepath = tmpdir + "/.config/python-bugzilla/bugzillarc"
assert rcfile.get_configpaths()[-1] == rcfilepath
tests.utils.diff_compare(open(rcfilepath).read(),
dirname + "output-bugzillarc.txt")
# Use that generated rcfile to test default URL lookup
fakeurl = "http://foo.bar.baz/wibble"
open(rcfilepath, "w").write("\n[DEFAULT]\nurl = %s" % fakeurl)
assert bzapi.get_rcfile_default_url() == fakeurl
finally:
shutil.rmtree(tmpdir)
def test_authfiles_nowrite():
# Setting values tokenfile is None, should be fine
bzapi = tests.mockbackend.make_bz(bz_kwargs={"use_creds": False})
bzapi.connect("https://example.com/foo")
btokencache = bzapi._tokencache # pylint: disable=protected-access
rcfile = bzapi._rcfile # pylint: disable=protected-access
btokencache.set_value(bzapi.url, "NEW-TOKEN-VALUE")
assert rcfile.save_api_key(bzapi.url, "fookey") is None
070701000000B3000081A400000000000000000000000166EC6715000019D5000000000000000000000000000000000000004300000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/test_api_bug.py#
# Copyright Red Hat, Inc. 2014
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
#
"""
Unit tests for testing some bug.py magic
"""
import io
import pickle
import pytest
import tests
import tests.mockbackend
import tests.utils
from bugzilla.bug import Bug
rhbz = tests.mockbackend.make_bz(version="4.4.0", rhbz=True)
def testBasic():
data = {
"bug_id": 123456,
"status": "NEW",
"assigned_to": "foo@bar.com",
"component": "foo",
"product": "bar",
"short_desc": "some short desc",
"cf_fixed_in": "nope",
"fixed_in": "1.2.3.4",
"devel_whiteboard": "some status value",
}
bug = Bug(bugzilla=rhbz, dict=data)
def _assert_bug():
assert hasattr(bug, "component") is True
assert getattr(bug, "components") == ["foo"]
assert getattr(bug, "product") == "bar"
assert hasattr(bug, "short_desc") is True
assert getattr(bug, "summary") == "some short desc"
assert bool(getattr(bug, "cf_fixed_in")) is True
assert getattr(bug, "fixed_in") == "1.2.3.4"
assert bool(getattr(bug, "cf_devel_whiteboard")) is True
assert getattr(bug, "devel_whiteboard") == "some status value"
_assert_bug()
assert str(bug) == "#123456 NEW - foo@bar.com - some short desc"
assert repr(bug).startswith("<Bug #123456")
# This triggers some code in __getattr__
dir(bug)
# Test special pickle support
fd = io.BytesIO()
pickle.dump(bug, fd)
fd.seek(0)
bug = pickle.load(fd)
assert getattr(bug, "bugzilla") is None
assert str(bug)
assert repr(bug)
bug.bugzilla = rhbz
_assert_bug()
def testBugNoID():
try:
Bug(bugzilla=rhbz, dict={"component": "foo"})
raise AssertionError("Expected lack of ID failure.")
except TypeError:
pass
def test_api_getbugs():
fakebz = tests.mockbackend.make_bz(
bug_get_args="data/mockargs/test_api_getbugs1.txt",
bug_get_return="data/mockreturn/test_query_cve_getbug.txt")
fakebz.bug_autorefresh = True
bug = fakebz.getbug("CVE-1234-5678", exclude_fields="foo")
assert bug.alias == ["CVE-1234-5678"]
assert bug.autorefresh is True
fakebz = tests.mockbackend.make_bz(
bug_get_args="data/mockargs/test_api_getbugs2.txt",
bug_get_return={"bugs": [{}, {}]})
assert fakebz.getbugs(["123456", "CVE-1234-FAKE"]) == []
def test_getbug_alias():
"""
Test that `getbug(<alias>)` includes the alias in `include_fields`
"""
fakebz = tests.mockbackend.make_bz(
bug_get_args=None,
bug_get_return="data/mockreturn/test_query_cve_getbug.txt")
bug = fakebz.getbug("CVE-1234-5678", include_fields=["id"])
assert bug.alias == ["CVE-1234-5678"]
assert bug.id == 123456
def mock_bug_get(bug_ids, aliases, paramdict):
assert bug_ids == []
assert aliases == ["CVE-1234-5678"]
assert "alias" in paramdict.get("include_fields", [])
return {"bugs": [bug.get_raw_data()]}
backend = getattr(fakebz, "_backend")
setattr(backend, "bug_get", mock_bug_get)
fakebz.getbug("CVE-1234-5678", include_fields=["id"])
def test_bug_getattr():
fakebz = tests.mockbackend.make_bz(
bug_get_args=None,
bug_get_return="data/mockreturn/test_getbug_rhel.txt")
bug = fakebz.getbug(1165434)
with pytest.raises(AttributeError):
# Hits a specific codepath in Bug.__getattr__
dummy = bug.__baditem__
bug.autorefresh = True
summary = bug.summary
del bug.__dict__["summary"]
# Trigger autorefresh
assert bug.summary == summary
def test_bug_apis():
def _get_fake_bug(apiname):
update_args = "data/mockargs/test_bug_apis_%s_update.txt" % apiname
fakebz = tests.mockbackend.make_bz(rhbz=True,
bug_get_args=None,
bug_get_return="data/mockreturn/test_getbug_rhel.txt",
bug_update_args=update_args,
bug_update_return={})
return fakebz.getbug(1165434)
# bug.setstatus, wrapper for update_bugs
bug = _get_fake_bug("setstatus")
bug.setstatus("POST", "foocomment", private=True)
# bug.close, wrapper for update_bugs
bug = _get_fake_bug("close")
bug.close("UPSTREAM", dupeid=123456, comment="foocomment2",
isprivate=False, fixedin="1.2.3.4.5")
# bug.setassignee, wrapper for update_bugs
bug = _get_fake_bug("setassignee")
bug.setassignee(
assigned_to="foo@example.com", qa_contact="bar@example.com",
comment="foocomment")
with pytest.raises(ValueError):
# Hits a validation path
bug.setassignee()
# bug.addcc test
bug = _get_fake_bug("addcc")
bug.addcc("foo2@example.com", comment="foocomment")
# bug.deletecc test
bug = _get_fake_bug("deletecc")
bug.deletecc("foo2@example.com", comment="foocomment")
# bug.addcomment test
bug = _get_fake_bug("addcomment")
bug.addcomment("test comment", private=True)
# bug.updateflags test
bug = _get_fake_bug("updateflags")
bug.updateflags({"someflag": "someval"})
# Some minor flag API tests
assert "creation_date" in bug.get_flag_type("needinfo")
assert bug.get_flag_type("NOPE") is None
assert bug.get_flags("NOPE") is None
assert bug.get_flag_status("NOPE") is None
# Minor get_history_raw wrapper
fakebz = tests.mockbackend.make_bz(rhbz=True,
bug_history_args="data/mockargs/test_bug_api_history.txt",
bug_history_return={},
bug_get_args=None,
bug_get_return="data/mockreturn/test_getbug_rhel.txt",
bug_comments_args="data/mockargs/test_bug_api_comments.txt",
bug_comments_return={"bugs": {"1165434": {"comments": []}}},
bug_attachment_get_all_args=(
"data/mockargs/test_bug_api_get_attachments.txt"),
bug_attachment_get_all_return="data/mockreturn/test_attach_get2.txt",
)
# Stub API testing
bug = fakebz.getbug(1165434)
bug.get_history_raw()
bug.getcomments()
# Some hackery to hit a few attachment code paths
bug.id = 663674
attachments = bug.get_attachments()
bug.attachments = attachments
assert [469147, 470041, 502352] == bug.get_attachment_ids()
def test_bug_weburl():
fakebz = tests.mockbackend.make_bz(
bug_get_args=None,
bug_get_return="data/mockreturn/test_getbug_rhel.txt")
bug_id = 1165434
bug = fakebz.getbug(bug_id)
assert bug.weburl == f"https:///show_bug.cgi?id={bug_id}"
070701000000B4000081A400000000000000000000000166EC67150000067B000000000000000000000000000000000000004C00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/test_api_externalbugs.py#
# Copyright Red Hat, Inc. 2012
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
#
"""
Test miscellaneous API bits
"""
import tests
import tests.mockbackend
def test_externalbugs():
# Basic API testing of the ExternalBugs wrappers
fakebz = tests.mockbackend.make_bz(
externalbugs_add_args="data/mockargs/test_externalbugs_add.txt",
externalbugs_add_return={},
externalbugs_update_args="data/mockargs/test_externalbugs_update.txt",
externalbugs_update_return={},
externalbugs_remove_args="data/mockargs/test_externalbugs_remove.txt",
externalbugs_remove_return={})
fakebz.add_external_tracker(
bug_ids=[1234, 5678],
ext_bz_bug_id="externalid",
ext_type_id="launchpad",
ext_type_description="some-bug-add-description",
ext_type_url="https://example.com/launchpad/1234",
ext_status="CLOSED",
ext_description="link to launchpad",
ext_priority="bigly")
fakebz.update_external_tracker(
ids=["external1", "external2"],
ext_bz_bug_id="externalid-update",
ext_type_id="mozilla",
ext_type_description="some-bug-update",
ext_type_url="https://mozilla.foo/bar/5678",
ext_status="OPEN",
bug_ids=["some", "bug", "id"],
ext_description="link to mozilla",
ext_priority="like, really bigly")
fakebz.remove_external_tracker(
ids="remove1",
ext_bz_bug_id="99999",
ext_type_id="footype",
ext_type_description="foo-desc",
ext_type_url="foo-url",
bug_ids="blah")
070701000000B5000081A400000000000000000000000166EC6715000006B5000000000000000000000000000000000000004600000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/test_api_groups.py#
# Copyright Red Hat, Inc. 2012
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
#
"""
Test miscellaneous API bits
"""
import tests
import tests.mockbackend
def test_api_groups():
# Basic API testing of the users APIs
group_ret = {"groups": [{
"membership": [
{"real_name": "Bugzilla User",
"can_login": 1,
"name": "user@bugzilla.org",
"login_denied_text": "",
"id": 85,
"email_enabled": 1,
"email": "user@bugzilla.org"},
{"real_name": "Bugzilla User2",
"can_login": 0,
"name": "user2@bugzilla.org",
"login_denied_text": "",
"id": 77,
"email_enabled": 0,
"email": "user2@bugzilla.org"},
],
"is_active": 1,
"description": "Test Group",
"user_regexp": "",
"is_bug_group": 1,
"name": "TestGroup",
"id": 9
}]}
fakebz = tests.mockbackend.make_bz(
group_get_args="data/mockargs/test_api_groups_get1.txt",
group_get_return=group_ret)
# getgroups testing
groupobj = fakebz.getgroups("TestGroups")[0]
assert groupobj.groupid == 9
assert groupobj.member_emails == [
"user2@bugzilla.org", "user@bugzilla.org"]
assert groupobj.name == "TestGroup"
# getgroup testing
fakebz = tests.mockbackend.make_bz(
group_get_args="data/mockargs/test_api_groups_get2.txt",
group_get_return=group_ret)
groupobj = fakebz.getgroup("TestGroup", membership=True)
groupobj.membership = []
assert groupobj.members() == group_ret["groups"][0]["membership"]
070701000000B6000081A400000000000000000000000166EC6715000024F2000000000000000000000000000000000000004400000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/test_api_misc.py#
# Copyright Red Hat, Inc. 2012
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
#
"""
Test miscellaneous API bits
"""
import pytest
import bugzilla
import tests
import tests.mockbackend
def test_mock_rhbz():
fakebz = tests.mockbackend.make_bz(rhbz=True)
assert fakebz.__class__ == bugzilla.RHBugzilla
def test_file_imports():
# Ensure historically stable import paths continue to work
# pylint: disable=unused-import
from bugzilla.rhbugzilla import RHBugzilla
from bugzilla.bug import Bug
from bugzilla.base import Bugzilla
def testUserAgent():
b3 = tests.mockbackend.make_bz(version="3.0.0")
assert "python-bugzilla" in b3.user_agent
def test_fixurl():
assert (bugzilla.Bugzilla.fix_url("example.com") ==
"https://example.com/xmlrpc.cgi")
assert (bugzilla.Bugzilla.fix_url("example.com", force_rest=True) ==
"https://example.com/rest/")
assert (bugzilla.Bugzilla.fix_url("example.com/xmlrpc.cgi") ==
"https://example.com/xmlrpc.cgi")
assert (bugzilla.Bugzilla.fix_url("http://example.com/somepath.cgi") ==
"http://example.com/somepath.cgi")
assert bugzilla.Bugzilla.fix_url("http:///foo") == "http:///foo"
def testPostTranslation():
def _testPostCompare(bz, indict, outexpect):
outdict = indict.copy()
bz.post_translation({}, outdict)
assert outdict == outexpect
# Make sure multiple calls don't change anything
bz.post_translation({}, outdict)
assert outdict == outexpect
bug3 = tests.mockbackend.make_bz(version="3.4.0")
rhbz = tests.mockbackend.make_bz(version="4.4.0", rhbz=True)
test1 = {
"component": ["comp1"],
"version": ["ver1", "ver2"],
'flags': [{
'is_active': 1,
'name': 'qe_test_coverage',
'setter': 'pm-rhel@redhat.com',
'status': '?',
}, {
'is_active': 1,
'name': 'rhel-6.4.0',
'setter': 'pm-rhel@redhat.com',
'status': '+',
}],
'alias': ["FOO", "BAR"],
'blocks': [782183, 840699, 923128],
'keywords': ['Security'],
'groups': ['redhat'],
}
out_simple = test1.copy()
out_simple["components"] = out_simple["component"]
out_simple["component"] = out_simple["components"][0]
out_simple["versions"] = out_simple["version"]
out_simple["version"] = out_simple["versions"][0]
_testPostCompare(bug3, test1, test1)
_testPostCompare(rhbz, test1, out_simple)
def test_rhbz_pre_translation():
bz = tests.mockbackend.make_bz(rhbz=True)
input_query = {
"bug_id": "12345,6789",
"component": "comp1,comp2",
"column_list": ["field1", "field8"],
}
bz.pre_translation(input_query)
output_query = {
'component': ['comp1', 'comp2'],
'id': ['12345', '6789'],
'include_fields': ['field1', 'field8', 'id'],
}
assert output_query == input_query
def testUpdateFailures():
# sub_component without component also passed
bz = tests.mockbackend.make_bz(version="4.4.0", rhbz=True)
with pytest.raises(ValueError):
bz.build_update(sub_component="some sub component")
# Trying to update value that only rhbz supports
bz = tests.mockbackend.make_bz()
with pytest.raises(ValueError):
bz.build_update(fixed_in="some fixedin value")
def testCreatebugFieldConversion():
bz4 = tests.mockbackend.make_bz(version="4.0.0")
vc = bz4._validate_createbug # pylint: disable=protected-access
out = vc(product="foo", component="bar",
version="12", description="foo", short_desc="bar",
check_args=False)
assert out == {
'component': 'bar', 'description': 'foo', 'product': 'foo',
'summary': 'bar', 'version': '12'}
def testURLSavedSearch():
bz4 = tests.mockbackend.make_bz(version="4.0.0")
url = ("https://bugzilla.redhat.com/buglist.cgi?"
"cmdtype=dorem&list_id=2342312&namedcmd="
"RHEL7%20new%20assigned%20virt-maint&remaction=run&"
"sharer_id=321167")
query = {
'sharer_id': '321167',
'savedsearch': 'RHEL7 new assigned virt-maint'
}
assert bz4.url_to_query(url) == query
def testStandardQuery():
bz4 = tests.mockbackend.make_bz(version="4.0.0")
url = ("https://bugzilla.redhat.com/buglist.cgi?"
"component=virt-manager&query_format=advanced&classification="
"Fedora&product=Fedora&bug_status=NEW&bug_status=ASSIGNED&"
"bug_status=MODIFIED&bug_status=ON_DEV&bug_status=ON_QA&"
"bug_status=VERIFIED&bug_status=FAILS_QA&bug_status="
"RELEASE_PENDING&bug_status=POST&order=bug_status%2Cbug_id")
query = {
'product': 'Fedora',
'query_format': 'advanced',
'bug_status': ['NEW', 'ASSIGNED', 'MODIFIED', 'ON_DEV',
'ON_QA', 'VERIFIED', 'FAILS_QA', 'RELEASE_PENDING', 'POST'],
'classification': 'Fedora',
'component': 'virt-manager',
'order': 'bug_status,bug_id'
}
assert bz4.url_to_query(url) == query
# pylint: disable=use-implicit-booleaness-not-comparison
# Test with unknown URL
assert bz4.url_to_query("https://example.com") == {}
def test_api_login():
with pytest.raises(TypeError):
# Missing explicit URL
bugzilla.Bugzilla()
with pytest.raises(Exception):
# Calling connect() with passed in URL
bugzilla.Bugzilla(url="https:///FAKEURL")
bz = tests.mockbackend.make_bz()
with pytest.raises(ValueError):
# Errors on missing user
bz.login()
bz.user = "FOO"
with pytest.raises(ValueError):
# Errors on missing pass
bz.login()
bz.password = "BAR"
bz.api_key = "WIBBLE"
with pytest.raises(ValueError):
# Errors on api_key + login()
bz.login()
# Hit default api_key code path
bz = tests.mockbackend.make_bz(
bz_kwargs={"api_key": "FAKE_KEY"},
user_login_args="data/mockargs/test_api_login.txt",
user_login_return={})
# Try reconnect, with RHBZ testing
bz.connect("https:///fake/bugzilla.redhat.com")
bz.connect()
# Test auto login if user/password is set
bz = tests.mockbackend.make_bz(
bz_kwargs={"user": "FOO", "password": "BAR"},
user_login_args="data/mockargs/test_api_login2.txt",
user_login_return={},
user_logout_args=None,
user_logout_return={})
# Test logout
bz.logout()
def test_version_bad():
# Hit version error handling
bz = tests.mockbackend.make_bz(version="badversion")
assert bz.bz_ver_major == 5
assert bz.bz_ver_minor == 0
# pylint: disable=protected-access
assert bz._get_version() == 5.0
def test_extensions_bad():
# Hit bad extensions error handling
tests.mockbackend.make_bz(extensions="BADEXTENSIONS")
def test_bad_scheme():
bz = tests.mockbackend.make_bz()
try:
bz.connect("ftp://example.com")
except Exception as e:
assert "Invalid URL scheme: ftp" in str(e)
def test_update_flags():
# update_flags is just a compat wrapper for update_bugs
bz = tests.mockbackend.make_bz(
bug_update_args="data/mockargs/test_update_flags.txt",
bug_update_return={})
bz.update_flags([12345, 6789], {"name": "needinfo", "status": "?"})
def test_bugs_history_raw():
# Stub test for bugs_history_raw
ids = ["12345", 567]
bz = tests.mockbackend.make_bz(
bug_history_args=(ids, {}),
bug_history_return={})
bz.bugs_history_raw(ids)
def test_get_comments():
# Stub test for get_commands
ids = ["12345", 567]
bz = tests.mockbackend.make_bz(
bug_comments_args=(ids, {}),
bug_comments_return={})
bz.get_comments(ids)
def test_get_xmlrpc_proxy():
# Ensure _proxy goes to a backend API
bz = tests.mockbackend.make_bz()
with pytest.raises(NotImplementedError):
dummy = bz._proxy # pylint: disable=protected-access
assert bz.is_xmlrpc() is False
assert bz.is_rest() is False
assert hasattr(bz.get_requests_session(), "request")
def test_requests_session_passthrough():
import requests
session = requests.Session()
bz = tests.mockbackend.make_bz(
bz_kwargs={"requests_session": session, "sslverify": False})
assert bz.get_requests_session() is session
assert session.verify is False
def test_query_url_fail():
# test some handling of query from_url errors
query = {"query_format": "advanced", "product": "FOO"}
checkstr = "does not appear to support"
exc = bugzilla.BugzillaError("FAKEERROR query_format", code=123)
bz = tests.mockbackend.make_bz(version="4.0.0",
bug_search_args=None, bug_search_return=exc)
try:
bz.query(query)
except Exception as e:
assert checkstr in str(e)
bz = tests.mockbackend.make_bz(version="5.1.0",
bug_search_args=None, bug_search_return=exc)
try:
bz.query(query)
except Exception as e:
assert checkstr not in str(e)
def test_query_return_extra():
bz = tests.mockbackend.make_bz(version="5.1.0",
bug_search_args=None,
bug_search_return="data/mockreturn/test_query1.txt")
dummy, extra = bz.query_return_extra({})
assert extra['limit'] == 0
assert extra['FOOFAKEVALUE'] == "hello"
070701000000B7000081A400000000000000000000000166EC671500000EB4000000000000000000000000000000000000004800000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/test_api_products.py# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import pytest
import bugzilla
import tests
import tests.mockbackend
def test_api_component_edit():
fakebz = tests.mockbackend.make_bz(
component_create_args="data/mockargs/test_api_component_create1.txt",
component_create_return={},
component_update_args="data/mockargs/test_api_component_update1.txt",
component_update_return={},
)
# addcomponent stub testing
fakebz.addcomponent({
"initialowner": "foo@example.com",
"initialqacontact": "foo2@example.com",
"initialcclist": "foo3@example.com",
"product": "fooproduct",
"is_active": 0,
})
# editcomponent stub testing
fakebz.editcomponent({
"initialowner": "foo@example.com",
"blaharg": "blahval",
"product": "fooproduct",
"component": "foocomponent",
"is_active": 0,
})
def test_api_products():
prod_list_return = {'ids': [1, 7]}
prod_get_return = {'products': [
{'id': 7, 'name': 'test-fake-product',
'foo': {"bar": "baz"},
'components': [
{'default_assigned_to': 'Fake Guy',
'name': 'client-interfaces'},
{'default_assigned_to': 'ANother fake dude!',
'name': 'configuration'},
]},
]}
compnames = ["client-interfaces", "configuration"]
fakebz = tests.mockbackend.make_bz(
product_get_enterable_args=None,
product_get_enterable_return=prod_list_return,
product_get_selectable_args=None,
product_get_selectable_return=prod_list_return,
product_get_args="data/mockargs/test_api_products_get1.txt",
product_get_return=prod_get_return,
)
# enterable products
fakebz.product_get(ptype="enterable")
fakebz.product_get(ptype="selectable")
with pytest.raises(RuntimeError):
fakebz.product_get(ptype="idontknow")
# Double refresh things
fakebz.getproducts(force_refresh=True, ptype="enterable")
fakebz.getproducts(force_refresh=True, ptype="enterable")
# getcomponents etc. testing
fakebz = tests.mockbackend.make_bz(
product_get_args="data/mockargs/test_api_products_get2.txt",
product_get_return=prod_get_return,
)
# Lookup in product cache by name
ret = fakebz.getcomponents("test-fake-product")
assert ret == compnames
# Lookup in product cache by id
ret = fakebz.getcomponents(7)
assert ret == compnames
# force_refresh but its cool
ret = fakebz.getcomponents("test-fake-product", force_refresh=True)
assert ret == compnames
# getcomponentsdetails usage
fakebz = tests.mockbackend.make_bz(
product_get_args="data/mockargs/test_api_products_get3.txt",
product_get_return=prod_get_return,
)
fakebz.getcomponentdetails("test-fake-product", "configuration")
# Some bit to test productget exclude_args
fakebz = tests.mockbackend.make_bz(
product_get_args="data/mockargs/test_api_products_get4.txt",
product_get_return=prod_get_return)
fakebz.product_get(ids=["7"], exclude_fields=["product.foo"])
# Unknown product
fakebz = tests.mockbackend.make_bz(
product_get_args="data/mockargs/test_api_products_get5.txt",
product_get_return=prod_get_return)
with pytest.raises(bugzilla.BugzillaError):
fakebz.getcomponents(0)
def test_bug_fields():
fakebz = tests.mockbackend.make_bz(
bug_fields_args="data/mockargs/test_bug_fields.txt",
bug_fields_return="data/mockreturn/test_bug_fields.txt",
)
ret = fakebz.getbugfields(names=["bug_status"])
assert ["bug_status"] == ret
070701000000B8000081A400000000000000000000000166EC6715000008B6000000000000000000000000000000000000004500000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/test_api_users.py#
# Copyright Red Hat, Inc. 2012
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
#
"""
Test miscellaneous API bits
"""
import pytest
import bugzilla
import tests
import tests.mockbackend
def test_api_users():
# Basic API testing of the users APIs
user_ret = {'users': [
{'can_login': True,
'email': 'example1@example.com',
'id': 1010101,
'name': 'example1@example.com',
'real_name': 'Mr. Example Man'},
{'can_login': False,
'email': 'example2@example.com',
'id': 2222333,
'name': 'example name',
'real_name': 'Example real name',
'saved_reports': [],
'saved_searches': [],
'groups': [
{"id": 1, "name": "testgroup", "description": "somedesc"}
]},
]}
# getusers and User testing
fakebz = tests.mockbackend.make_bz(
user_get_args="data/mockargs/test_api_users_get1.txt",
user_get_return=user_ret,
user_update_args="data/mockargs/test_api_users_update1.txt",
user_update_return={})
userobj = fakebz.getuser("example2@example.com")
# Some userobj testing
userobj.refresh()
assert userobj.userid == 2222333
assert userobj.email == "example2@example.com"
assert userobj.name == "example name"
assert userobj.can_login is False
userobj.updateperms("rem", ["fedora_contrib"])
# Catch a validation error
with pytest.raises(bugzilla.BugzillaError):
userobj.updateperms("badaction", ["newgroup"])
# createuser tests
fakebz = tests.mockbackend.make_bz(
user_get_args="data/mockargs/test_api_users_get2.txt",
user_get_return=user_ret,
user_create_args="data/mockargs/test_api_users_create.txt",
user_create_return={})
userobj = fakebz.createuser("example1@example.com", "fooname", "foopass")
assert userobj.email == "example1@example.com"
# searchuser tests
fakebz = tests.mockbackend.make_bz(
user_get_args="data/mockargs/test_api_users_get3.txt",
user_get_return=user_ret)
userlist = fakebz.searchusers("example1@example.com")
assert len(userlist) == 2
070701000000B9000081A400000000000000000000000166EC67150000049A000000000000000000000000000000000000004800000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/test_backend_rest.pyfrom types import MethodType
from bugzilla._backendrest import _BackendREST
from bugzilla._session import _BugzillaSession
def test_getbug():
session = _BugzillaSession(url="http://example.com",
user_agent="py-bugzilla-test",
sslverify=False,
cert=None,
tokencache={},
api_key="",
is_redhat_bugzilla=False)
backend = _BackendREST(url="http://example.com",
bugzillasession=session)
def _assertion(self, *args):
self.assertion_called = True
assert args and args[0] == url
setattr(backend, "_get", MethodType(_assertion, backend))
for _ids, aliases, url in (
(1, None, "/bug/1"),
([1], [], "/bug/1"),
(None, "CVE-1999-0001", "/bug/CVE-1999-0001"),
([], ["CVE-1999-0001"], "/bug/CVE-1999-0001"),
(1, "CVE-1999-0001", "/bug"),
):
backend.assertion_called = False
backend.bug_get(_ids, aliases, {})
assert backend.assertion_called is True
070701000000BA000081A400000000000000000000000166EC67150000020D000000000000000000000000000000000000004000000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/test_base.pyfrom bugzilla.base import Bugzilla
def test_build_createbug():
bz = Bugzilla(url=None)
args = {"product": "Ubuntu 33⅓", "summary": "Hello World", "alias": "CVE-2024-0000"}
result = bz.build_createbug(**args)
assert result == args
result = bz.build_createbug(groups=None, **args)
assert result == args
args["groups"] = []
result = bz.build_createbug(**args)
assert result == args
args["groups"] += ["the-group"]
result = bz.build_createbug(**args)
assert result == args
070701000000BB000081A400000000000000000000000166EC671500000E7B000000000000000000000000000000000000004600000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/test_cli_attach.py# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import os
import tests
import tests.mockbackend
import tests.utils
##################################
# 'bugzilla attach' mock testing #
##################################
def test_attach(run_cli):
attachfile = os.path.dirname(__file__) + "/data/bz-attach-get1.txt"
attachcontent = open(attachfile).read()
# Hit error when no ID specified
fakebz = tests.mockbackend.make_bz()
out = run_cli("bugzilla attach", fakebz, expectfail=True)
assert "ID must be specified" in out
# Hit error when using tty and no --file specified
out = run_cli("bugzilla attach 123456", fakebz, expectfail=True)
assert "--file must be specified" in out
# Hit error when using stdin, but no --desc
out = run_cli("bugzilla attach 123456", fakebz, expectfail=True,
stdin=attachcontent)
assert "--description must be specified" in out
# Basic CLI attach
cmd = "bugzilla attach 123456 --file=%s " % attachfile
cmd += "--type text/x-patch --private "
cmd += "--comment 'some comment to go with it'"
fakebz = tests.mockbackend.make_bz(
bug_attachment_create_args="data/mockargs/test_attach1.txt",
bug_attachment_create_return={'ids': [1557949]})
out = run_cli(cmd, fakebz)
assert "Created attachment 1557949 on bug 123456" in out
# Attach from stdin
cmd = "bugzilla attach 123456 --file=fake-file-name.txt "
cmd += "--description 'Some attachment description' "
fakebz = tests.mockbackend.make_bz(
bug_attachment_create_args="data/mockargs/test_attach2.txt",
bug_attachment_create_return={'ids': [1557949]})
out = run_cli(cmd, fakebz, stdin=attachcontent)
assert "Created attachment 1557949 on bug 123456" in out
# Test --field passthrough
cmd = "bugzilla attach 123456 --file=%s " % attachfile
cmd += "--field=is_obsolete=1 "
cmd += "--field-json "
cmd += ('\'{"flags": [{"name": "review"'
', "requestee": "crobinso@redhat.com", "status": "-"}]}\'')
fakebz = tests.mockbackend.make_bz(
bug_attachment_create_args="data/mockargs/test_attach3.txt",
bug_attachment_create_return={'ids': [1557949]})
out = run_cli(cmd, fakebz)
assert "Created attachment 1557949 on bug 123456" in out
def _test_attach_get(run_cli):
# Hit error when using ids with --get*
fakebz = tests.mockbackend.make_bz()
out = run_cli("bugzilla attach 123456 --getall 123456",
fakebz, expectfail=True)
assert "not used for" in out
# Basic --get ATTID usage
filename = "Klíč memorial test file.txt"
cmd = "bugzilla attach --get 112233"
fakebz = tests.mockbackend.make_bz(
bug_attachment_get_args="data/mockargs/test_attach_get1.txt",
bug_attachment_get_return="data/mockreturn/test_attach_get1.txt")
out = run_cli(cmd, fakebz)
assert filename in out
# Basic --getall with --ignore-obsolete
cmd = "bugzilla attach --getall 663674 --ignore-obsolete"
fakebz = tests.mockbackend.make_bz(
bug_attachment_get_all_args="data/mockargs/test_attach_get2.txt",
bug_attachment_get_all_return="data/mockreturn/test_attach_get2.txt")
out = run_cli(cmd, fakebz)
os.system("ls %s" % os.getcwd())
filename += ".1"
assert filename in out
assert "bugzilla-filename" in out
def test_attach_get(run_cli):
import tempfile
import shutil
tmpdir = tempfile.mkdtemp(dir=os.getcwd())
origcwd = os.getcwd()
os.chdir(tmpdir)
try:
_test_attach_get(run_cli)
finally:
os.chdir(origcwd)
shutil.rmtree(tmpdir)
070701000000BC000081A400000000000000000000000166EC671500000C59000000000000000000000000000000000000004400000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/test_cli_info.py# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import tests
import tests.mockbackend
import tests.utils
################################
# 'bugzilla info' mock testing #
################################
def test_info(run_cli):
funcname = tests.utils.get_funcname()
argsprefix = "data/mockargs/%s_" % funcname
cliprefix = "data/clioutput/%s_" % funcname
prod_accessible = {'ids': [1, 7]}
prod_get = {'products': [
{'id': 1, 'name': 'Prod 1 Test'},
{'id': 7, 'name': 'test-fake-product'}
]}
# info --products
fakebz = tests.mockbackend.make_bz(
product_get_accessible_args=None,
product_get_accessible_return=prod_accessible,
product_get_args=argsprefix + "products.txt",
product_get_return=prod_get)
cmd = "bugzilla info --products"
out = run_cli(cmd, fakebz)
tests.utils.diff_compare(out, cliprefix + "products.txt")
# info --versions
prod_get_ver = {'products': [
{'id': 7, 'name': 'test-fake-product',
'versions': [
{'id': 360, 'is_active': True, 'name': '7.1'},
{'id': 123, 'is_active': True, 'name': 'fooversion!'},
]},
]}
fakebz = tests.mockbackend.make_bz(
product_get_args=argsprefix + "versions.txt",
product_get_return=prod_get_ver)
cmd = "bugzilla info --versions test-fake-product"
out = run_cli(cmd, fakebz)
tests.utils.diff_compare(out, cliprefix + "versions.txt")
# info --components
prod_get_comp_active = {'products': [
{'id': 7, 'name': 'test-fake-product',
'components': [
{'is_active': True, 'name': 'backend/kernel'},
{'is_active': True, 'name': 'client-interfaces'},
]},
]}
cmd = "bugzilla info --components test-fake-product"
fakebz = tests.mockbackend.make_bz(
product_get_args=argsprefix + "components.txt",
product_get_return=prod_get_comp_active)
out = run_cli(cmd, fakebz)
tests.utils.diff_compare(out, cliprefix + "components.txt")
# info --components --active-components
cmd = "bugzilla info --components test-fake-product --active-components"
fakebz = tests.mockbackend.make_bz(
product_get_args=argsprefix + "components-active.txt",
product_get_return=prod_get_comp_active)
out = run_cli(cmd, fakebz)
tests.utils.diff_compare(out, cliprefix + "components-active.txt")
# info --components_owners
cmd = "bugzilla info --component_owners test-fake-product"
prod_get_comp_owners = {'products': [
{'id': 7, 'name': 'test-fake-product',
'components': [
{'default_assigned_to': 'Fake Guy',
'name': 'client-interfaces'},
{'default_assigned_to': 'ANother fake dude!',
'name': 'configuration'},
]},
]}
fakebz = tests.mockbackend.make_bz(
product_get_args=argsprefix + "components-owners.txt",
product_get_return=prod_get_comp_owners)
out = run_cli(cmd, fakebz)
tests.utils.diff_compare(out, cliprefix + "components-owners.txt")
070701000000BD000081A400000000000000000000000166EC671500001102000000000000000000000000000000000000004500000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/test_cli_login.py# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import tempfile
import pytest
import bugzilla
import tests
import tests.mockbackend
import tests.utils
#################################
# 'bugzilla login' mock testing #
#################################
def test_login(run_cli):
cmd = "bugzilla login FOO BAR"
fakebz = tests.mockbackend.make_bz(
user_login_args="data/mockargs/test_login.txt",
user_login_return=RuntimeError("TEST ERROR"))
out = run_cli(cmd, fakebz, expectfail=True)
assert "Login failed: TEST ERROR" in out
fakebz = tests.mockbackend.make_bz(
user_login_args="data/mockargs/test_login.txt",
user_login_return={})
out = run_cli(cmd, fakebz)
assert "Login successful" in out
cmd = "bugzilla --restrict-login --user FOO --password BAR login"
fakebz = tests.mockbackend.make_bz(
user_login_args="data/mockargs/test_login-restrict.txt",
user_login_return={})
out = run_cli(cmd, fakebz)
assert "Login successful" in out
cmd = "bugzilla --ensure-logged-in --user FOO --password BAR login"
# Raises raw error trying to see if we aren't logged in
with pytest.raises(NotImplementedError):
fakebz = tests.mockbackend.make_bz(
user_login_args="data/mockargs/test_login.txt",
user_login_return={},
user_get_args=None,
user_get_return=NotImplementedError())
out = run_cli(cmd, fakebz)
# Errors with expected code
cmd = "bugzilla --ensure-logged-in --user FOO --password BAR login"
fakebz = tests.mockbackend.make_bz(
user_login_args="data/mockargs/test_login.txt",
user_login_return={},
user_get_args=None,
user_get_return=bugzilla.BugzillaError("TESTMESSAGE", code=505))
out = run_cli(cmd, fakebz, expectfail=True)
assert "--ensure-logged-in passed but you" in out
# Returns success for logged_in check and hits a tokenfile line
cmd = "bugzilla --ensure-logged-in "
cmd += "login FOO BAR"
tmp = tempfile.NamedTemporaryFile()
fakebz = tests.mockbackend.make_bz(
bz_kwargs={"use_creds": True, "tokenfile": tmp.name},
user_login_args="data/mockargs/test_login.txt",
user_login_return={'id': 1234, 'token': 'my-fake-token'},
user_get_args=None,
user_get_return={})
fakebz.connect("https://example.com")
out = run_cli(cmd, fakebz)
assert "Token cache saved" in out
assert fakebz.tokenfile in out
assert "Consider using bugzilla API" in out
tests.utils.diff_compare(open(tmp.name).read(),
"data/clioutput/tokenfile.txt")
# Returns success for logged_in check and hits another tokenfile line
cmd = "bugzilla --ensure-logged-in "
cmd += "login FOO BAR"
fakebz = tests.mockbackend.make_bz(
bz_kwargs={"use_creds": True, "tokenfile": None},
user_login_args="data/mockargs/test_login.txt",
user_login_return={'id': 1234, 'token': 'my-fake-token'},
user_get_args=None,
user_get_return={})
out = run_cli(cmd, fakebz)
assert "Token not saved" in out
def test_interactive_login(monkeypatch, run_cli):
bz = tests.mockbackend.make_bz(
user_login_args="data/mockargs/test_interactive_login.txt",
user_login_return={},
user_logout_args=None,
user_logout_return={},
user_get_args=None,
user_get_return={})
tests.utils.monkeypatch_getpass(monkeypatch)
cmd = "bugzilla login"
fakestdin = "fakeuser\nfakepass\n"
out = run_cli(cmd, bz, stdin=fakestdin)
assert "Bugzilla Username:" in out
assert "Bugzilla Password:" in out
# API key prompting and saving
tmp = tempfile.NamedTemporaryFile()
bz.configpath = [tmp.name]
bz.url = "https://example.com"
cmd = "bugzilla login --api-key"
fakestdin = "MY-FAKE-KEY\n"
out = run_cli(cmd, bz, stdin=fakestdin)
assert "API Key:" in out
assert tmp.name in out
tests.utils.diff_compare(open(tmp.name).read(),
"data/clioutput/test_interactive_login_apikey_rcfile.txt")
# Check that we don't attempt to log in if API key is configured
assert bz.api_key
cmd = "bugzilla login"
out = run_cli(cmd, bz)
assert "already have an API" in out
070701000000BE000081A400000000000000000000000166EC67150000110E000000000000000000000000000000000000004400000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/test_cli_misc.py#
# Copyright Red Hat, Inc. 2012
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
#
"""
Test miscellaneous CLI bits to get build out our code coverage
"""
import base64
import datetime
import json
import xmlrpc.client
import pytest
import requests
import bugzilla
import tests
import tests.mockbackend
def testHelp(run_cli):
out = run_cli("bugzilla --help", None)
assert len(out.splitlines()) > 18
def testCmdHelp(run_cli):
out = run_cli("bugzilla query --help", None)
assert len(out.splitlines()) > 40
def testVersion(run_cli):
out = run_cli("bugzilla --version", None)
assert out.strip() == bugzilla.__version__
def testCookiefileDeprecated(run_cli):
with pytest.raises(TypeError) as e:
run_cli("bugzilla --cookiefile foobar login",
None, expectfail=True)
assert "cookiefile is deprecated" in str(e)
def testPositionalArgs(run_cli):
# Make sure cli correctly rejects ambiguous positional args
out = run_cli("bugzilla login --xbadarg foo",
None, expectfail=True)
assert "unrecognized arguments: --xbadarg" in out
out = run_cli("bugzilla modify 123456 --foobar --status NEW",
None, expectfail=True)
assert "unrecognized arguments: --foobar" in out
def testDebug(run_cli):
# Coverage testing for debug options
run_cli("bugzilla --bugzilla https:///BADURI --verbose login",
None, expectfail=True)
run_cli("bugzilla --bugzilla https:///BADURI --debug login",
None, expectfail=True)
def testExceptions(run_cli):
"""
Test exception handling around main()
"""
fakebz = tests.mockbackend.make_bz(
bug_search_args=None,
bug_search_return=KeyboardInterrupt())
out = run_cli("bugzilla query --bug_id 1", fakebz, expectfail=True)
assert "user request" in out
fakebz = tests.mockbackend.make_bz(
bug_search_args=None,
bug_search_return=bugzilla.BugzillaError("foo"))
out = run_cli("bugzilla query --bug_id 1", fakebz, expectfail=True)
assert "Server error:" in out
fakebz = tests.mockbackend.make_bz(
bug_search_args=None,
bug_search_return=requests.exceptions.SSLError())
out = run_cli("bugzilla query --bug_id 1", fakebz, expectfail=True)
assert "trust the remote server" in out
fakebz = tests.mockbackend.make_bz(
bug_search_args=None,
bug_search_return=requests.exceptions.ConnectionError())
out = run_cli("bugzilla query --bug_id 1", fakebz, expectfail=True)
assert "Connection lost" in out
def testManualURL(run_cli):
"""
Test passing a manual URL, to hit those non-testsuite code paths
"""
try:
cmd = "bugzilla --bztype foobar "
cmd += "--bugzilla https:///FAKEURL query --bug_id 1"
run_cli(cmd, None)
except Exception as e:
assert "No host supplied" in str(e)
def test_json_xmlrpc(run_cli):
# Test --json output with XMLRPC type conversion
cmd = "bugzilla query --json --id 1165434"
timestr = '20181209T19:12:12'
dateobj = datetime.datetime.strptime(timestr, '%Y%m%dT%H:%M:%S')
attachfile = tests.utils.tests_path("data/bz-attach-get1.txt")
attachdata = open(attachfile, "rb").read()
bugid = 1165434
data = {"bugs": [{
'id': bugid,
'timetest': xmlrpc.client.DateTime(dateobj),
'binarytest': xmlrpc.client.Binary(attachdata),
}]}
fakebz = tests.mockbackend.make_bz(
bug_search_args=None,
bug_search_return={"bugs": [{"id": bugid}]},
bug_get_args=None,
bug_get_return=data)
out = run_cli(cmd, fakebz)
tests.utils.diff_compare(tests.utils.sanitize_json(out),
"data/clioutput/test_json_xmlrpc.txt")
retdata = json.loads(out)["bugs"][0]
assert (base64.b64decode(retdata["binarytest"]) ==
attachdata)
assert retdata["timetest"] == dateobj.isoformat() + "Z"
# Test an error case, json converter can't handle Exception class
data["bugs"][0]["foo"] = Exception("foo")
fakebz = tests.mockbackend.make_bz(
bug_search_args=None,
bug_search_return={"bugs": [{"id": bugid}]},
bug_get_args=None,
bug_get_return=data)
with pytest.raises(RuntimeError):
run_cli(cmd, fakebz, expectfail=True)
070701000000BF000081A400000000000000000000000166EC671500000E4D000000000000000000000000000000000000004600000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/test_cli_modify.py# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import tests
import tests.mockbackend
import tests.utils
##################################
# 'bugzilla modify' mock testing #
##################################
def test_modify(run_cli):
# errors on missing args
cmd = "bugzilla modify 123456"
fakebz = tests.mockbackend.make_bz()
out = run_cli(cmd, fakebz, expectfail=True)
assert "additional arguments" in out
# Modify basic
cmd = "bugzilla modify 123456 1234567 "
cmd += "--status ASSIGNED --component NEWCOMP "
fakebz = tests.mockbackend.make_bz(
bug_update_args="data/mockargs/test_modify1.txt",
bug_update_return={})
out = run_cli(cmd, fakebz)
assert not out
# Modify with lots of opts
cmd = "bugzilla modify 123456 --component NEWCOMP "
cmd += "--keyword +FOO --groups=-BAR --blocked =123456,445566 "
cmd += "--flag=-needinfo,+somethingelse "
cmd += "--whiteboard =foo --whiteboard =thisone "
cmd += "--dupeid 555666 "
cmd += "--comment 'some example comment' --private "
fakebz = tests.mockbackend.make_bz(
bug_update_args="data/mockargs/test_modify2.txt",
bug_update_return={})
out = run_cli(cmd, fakebz)
assert not out
# Modify with tricky opts hitting other API calls
cmd = "bugzilla modify 1165434 "
cmd += "--tags +addtag --tags=-rmtag "
cmd += "--qa_whiteboard +yo-qa --qa_whiteboard=-foo "
cmd += "--internal_whiteboard +internal-hey --internal_whiteboard +bar "
cmd += "--devel_whiteboard +devel-duh --devel_whiteboard=-yay "
fakebz = tests.mockbackend.make_bz(rhbz=True,
bug_update_tags_args="data/mockargs/test_modify3-tags.txt",
bug_update_tags_return={},
bug_update_args="data/mockargs/test_modify3.txt",
bug_update_return={},
bug_get_args=None,
bug_get_return="data/mockreturn/test_getbug_rhel.txt")
out = run_cli(cmd, fakebz)
assert not out
# Modify hitting some rhbz paths
cmd = "bugzilla modify 1165434 "
cmd += "--fixed_in foofixedin "
cmd += "--component lvm2 "
cmd += "--sub-component some-sub-component"
fakebz = tests.mockbackend.make_bz(rhbz=True,
bug_update_args="data/mockargs/test_modify4.txt",
bug_update_return={})
out = run_cli(cmd, fakebz)
assert not out
# Modify with a slew of misc opt coverage
cmd = "bugzilla modify 1165434 "
cmd += "--assigned_to foo@example.com --qa_contact qa@example.com "
cmd += "--product newproduct "
cmd += "--blocked +1234 --blocked -1235 --blocked = "
cmd += "--url https://example.com "
cmd += "--cc=+bar@example.com --cc=-steve@example.com "
cmd += "--dependson=+2234 --dependson=-2235 --dependson = "
cmd += "--groups +foogroup "
cmd += "--keywords +newkeyword --keywords=-byekeyword --keywords = "
cmd += "--os windows --arch mips "
cmd += "--priority high --severity low "
cmd += "--summary newsummary --version 1.2.3 "
cmd += "--reset-assignee --reset-qa-contact "
cmd += "--alias fooalias "
cmd += "--target_release 1.2.4 --target_milestone beta "
cmd += "--devel_whiteboard =DEVBOARD --internal_whiteboard =INTBOARD "
cmd += "--qa_whiteboard =QABOARD "
cmd += "--comment-tag FOOTAG --field bar=foo "
cmd += '--field-json \'{"cf_verified": ["Tested"], "cf_blah": {"1": 2}}\' '
cmd += "--minor-update "
fakebz = tests.mockbackend.make_bz(rhbz=True,
bug_update_args="data/mockargs/test_modify5.txt",
bug_update_return={})
out = run_cli(cmd, fakebz)
assert not out
070701000000C0000081A400000000000000000000000166EC671500000867000000000000000000000000000000000000004300000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/test_cli_new.py# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import tests
import tests.mockbackend
import tests.utils
###############################
# 'bugzilla new' mock testing #
###############################
def test_new(run_cli):
# Test a simpler creation
cmd = "bugzilla new --product FOOPROD --component FOOCOMP "
cmd += "--summary 'Hey this is the title!' "
cmd += "--comment 'This is the first comment!\nWith newline & stuff.' "
cmd += "--keywords ADDKEY --groups FOOGROUP,BARGROUP "
cmd += "--blocked 12345,6789 --cc foo@example.com --cc bar@example.com "
cmd += "--dependson dependme --private "
fakebz = tests.mockbackend.make_bz(
bug_create_args="data/mockargs/test_new1.txt",
bug_create_return={"id": 1694158},
bug_get_args=None,
bug_get_return="data/mockreturn/test_getbug.txt")
out = run_cli(cmd, fakebz)
tests.utils.diff_compare(out, "data/clioutput/test_new1.txt")
# Test every option
cmd = "bugzilla new --product FOOPROD --component FOOCOMP "
cmd += "--summary 'Hey this is the title!' "
cmd += "--comment 'This is the first comment!\nWith newline & stuff.' "
cmd += "--keywords ADDKEY --groups FOOGROUP,BARGROUP "
cmd += "--blocked 12345,6789 --cc foo@example.com --cc bar@example.com "
cmd += "--dependson dependme --private "
cmd += "--os linux --arch mips --severity high --priority low "
cmd += "--url https://some.example.com "
cmd += "--version 5.6.7 --alias somealias "
cmd += "--sub-component FOOCOMP "
cmd += "--assignee foo@example.com --qa_contact qa@example.com "
cmd += "--comment-tag FOO "
cmd += "--field foo=bar "
cmd += '--field-json \'{"cf_verified": ["Tested"], "cf_blah": {"1": 2}}\' '
fakebz = tests.mockbackend.make_bz(
bug_create_args="data/mockargs/test_new2.txt",
bug_create_return={"id": 1694158},
bug_get_args=None,
bug_get_return="data/mockreturn/test_getbug.txt",
rhbz=True)
out = run_cli(cmd, fakebz)
tests.utils.diff_compare(out, "data/clioutput/test_new2.txt")
070701000000C1000081A400000000000000000000000166EC671500001F65000000000000000000000000000000000000004500000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/test_cli_query.py# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import json
import os
import re
import tests
import tests.mockbackend
import tests.utils
#################################
# 'bugzilla query' mock testing #
#################################
def test_query(run_cli):
# bad --field option
fakebz = tests.mockbackend.make_bz()
cmd = "bugzilla query --field FOO"
out = run_cli(cmd, fakebz, expectfail=True)
assert "Invalid field argument" in out
# bad --field-json option
fakebz = tests.mockbackend.make_bz()
cmd = "bugzilla query --field-json='{1: 2}'"
out = run_cli(cmd, fakebz, expectfail=True)
assert "Invalid field-json" in out
# Simple query with some comma opts
cmd = "bugzilla query "
cmd += "--product foo --component foo,bar --bug_id 1234,2480"
fakebz = tests.mockbackend.make_bz(
bug_search_args="data/mockargs/test_query1.txt",
bug_search_return="data/mockreturn/test_query1.txt")
out = run_cli(cmd, fakebz)
tests.utils.diff_compare(out, "data/clioutput/test_query1.txt")
# RHBZ query with a ton of opts
cmd = "bugzilla query "
cmd += "--product foo --component foo,bar --bug_id 1234,2480 "
cmd += "--keywords fribkeyword --fixed_in amifixed "
cmd += "--qa_whiteboard some-example-whiteboard "
cmd += "--cc foo@example.com --qa_contact qa@example.com "
cmd += "--comment 'some comment string' "
fakebz = tests.mockbackend.make_bz(rhbz=True,
bug_search_args="data/mockargs/test_query1-rhbz.txt",
bug_search_return="data/mockreturn/test_query1.txt")
out = run_cli(cmd, fakebz)
tests.utils.diff_compare(out, "data/clioutput/test_query1-rhbz.txt")
# --emailtype handling
cmd = "bugzilla query --cc foo@example.com --emailtype BAR "
fakebz = tests.mockbackend.make_bz(rhbz=True,
bug_search_args="data/mockargs/test_query2-rhbz.txt",
bug_search_return="data/mockreturn/test_query1.txt")
out = run_cli(cmd, fakebz)
tests.utils.diff_compare(out, "data/clioutput/test_query2-rhbz.txt")
# Same but with --ids output
cmd = "bugzilla query --ids "
cmd += "--product foo --component foo,bar --bug_id 1234,2480"
fakebz = tests.mockbackend.make_bz(
bug_search_args="data/mockargs/test_query1-ids.txt",
bug_search_return="data/mockreturn/test_query1.txt")
out = run_cli(cmd, fakebz)
tests.utils.diff_compare(out, "data/clioutput/test_query1-ids.txt")
# Same but with --raw output
cmd = "bugzilla query --raw --bug_id 1165434"
fakebz = tests.mockbackend.make_bz(
bug_search_args="data/mockargs/test_query2.txt",
bug_search_return={"bugs": [{"id": 1165434}]},
bug_get_args=None,
bug_get_return="data/mockreturn/test_getbug_rhel.txt")
out = run_cli(cmd, fakebz)
# Dictionary ordering is random, so scrub it from our output
out = re.sub(r"\{.*\}", r"'DICT SCRUBBED'", out, re.MULTILINE)
tests.utils.diff_compare(out, "data/clioutput/test_query2.txt")
# Test a bunch of different combinations for code coverage
cmd = "bugzilla query --status ALL --severity sev1,sev2 "
cmd += "--outputformat='%{foo}:%{bar}::%{whiteboard}:"
cmd += "%{flags}:%{flags_requestee}%{whiteboard:devel}::"
cmd += "%{flag:needinfo}::%{comments}::%{external_bugs}'"
fakebz = tests.mockbackend.make_bz(
bug_search_args="data/mockargs/test_query3.txt",
bug_search_return="data/mockreturn/test_getbug_rhel.txt")
out = run_cli(cmd, fakebz)
tests.utils.diff_compare(out, "data/clioutput/test_query3.txt")
# Test --status DEV and --full
cmd = "bugzilla query --status DEV --full"
fakebz = tests.mockbackend.make_bz(
bug_search_args="data/mockargs/test_query4.txt",
bug_search_return="data/mockreturn/test_getbug_rhel.txt")
out = run_cli(cmd, fakebz)
tests.utils.diff_compare(out, "data/clioutput/test_query4.txt")
# Test --status QE and --extra, and components-file
compfile = os.path.dirname(__file__) + "/data/components_file.txt"
cmd = "bugzilla query --status QE --extra "
cmd += "--components_file %s" % compfile
fakebz = tests.mockbackend.make_bz(
bug_search_args="data/mockargs/test_query5.txt",
bug_search_return="data/mockreturn/test_getbug_rhel.txt")
out = run_cli(cmd, fakebz)
tests.utils.diff_compare(out, "data/clioutput/test_query5.txt")
# Test --status EOL and --oneline, and some --field usage
cmd = "bugzilla query --status EOL --oneline "
cmd += "--field FOO=1 --field=BAR=WIBBLE "
cmd += '--field-json \'{"cf_verified": ["Tested"], "cf_blah": {"1": 2}}\' '
fakebz = tests.mockbackend.make_bz(
bug_search_args="data/mockargs/test_query6.txt",
bug_search_return="data/mockreturn/test_getbug_rhel.txt",
bug_get_args="data/mockargs/test_query_cve_getbug.txt",
bug_get_return="data/mockreturn/test_query_cve_getbug.txt")
out = run_cli(cmd, fakebz)
tests.utils.diff_compare(out, "data/clioutput/test_query6.txt")
# Test --status OPEN and --from-url
url = "https://bugzilla.redhat.com/buglist.cgi?bug_status=NEW&bug_status=ASSIGNED&bug_status=MODIFIED&bug_status=ON_DEV&bug_status=ON_QA&bug_status=VERIFIED&bug_status=FAILS_QA&bug_status=RELEASE_PENDING&bug_status=POST&classification=Fedora&component=virt-manager&order=bug_status%2Cbug_id&product=Fedora&query_format=advanced" # noqa
cmd = "bugzilla query --status OPEN --from-url %s" % url
fakebz = tests.mockbackend.make_bz(
bug_search_args="data/mockargs/test_query7.txt",
bug_search_return="data/mockreturn/test_getbug_rhel.txt")
out = run_cli(cmd, fakebz)
tests.utils.diff_compare(out, "data/clioutput/test_query7.txt")
# Test --json output
cmd = "bugzilla query --json --id 1165434"
fakebz = tests.mockbackend.make_bz(
bug_search_args="data/mockargs/test_query8.txt",
bug_search_return={"bugs": [{"id": 1165434}]},
bug_get_args=None,
bug_get_return="data/mockreturn/test_getbug_rhel.txt")
out = run_cli(cmd, fakebz)
tests.utils.diff_compare(tests.utils.sanitize_json(out),
"data/clioutput/test_query8.txt")
assert json.loads(out)
# Test --json output
cmd = ("bugzilla query --json --id 1165434 "
"--includefield foo --includefield bar "
"--excludefield excludeme "
"--extrafield extrame1 --extrafield extrame2 ")
fakebz = tests.mockbackend.make_bz(rhbz=True,
bug_search_args="data/mockargs/test_query9.txt",
bug_search_return={"bugs": [{"id": 1165434}]},
bug_get_args="data/mockargs/test_getbug_query9.txt",
bug_get_return="data/mockreturn/test_getbug_rhel.txt")
out = run_cli(cmd, fakebz)
tests.utils.diff_compare(tests.utils.sanitize_json(out),
"data/clioutput/test_query9.txt")
assert json.loads(out)
# Test every remaining option
cmd = "bugzilla query "
cmd += "--sub-component FOOCOMP "
cmd += "--version 5.6.7 --reporter me@example.com "
cmd += "--summary 'search summary' "
cmd += "--assignee bar@example.com "
cmd += "--blocked 12345 --dependson 23456 "
cmd += "--keywords FOO --keywords_type substring "
cmd += "--url https://example.com --url_type sometype "
cmd += "--target_release foo --target_milestone bar "
cmd += "--quicksearch 1 --savedsearch 2 --savedsearch-sharer-id 3 "
cmd += "--tags +foo --flag needinfo --alias somealias "
cmd += "--devel_whiteboard DEVBOARD "
cmd += "--priority wibble "
cmd += "--fixed_in 5.5.5 --fixed_in_type substring "
cmd += "--whiteboard FOO --status_whiteboard_type substring "
fakebz = tests.mockbackend.make_bz(
bug_search_args="data/mockargs/test_query10.txt",
bug_search_return="data/mockreturn/test_getbug_rhel.txt",
rhbz=True)
out = run_cli(cmd, fakebz)
tests.utils.diff_compare(out, "data/clioutput/test_query10.txt")
070701000000C2000081A400000000000000000000000166EC671500003801000000000000000000000000000000000000004900000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/test_ro_functional.py#
# Copyright Red Hat, Inc. 2012
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
#
"""
Unit tests that do readonly functional tests against real bugzilla instances.
"""
from xmlrpc.client import Fault
import pytest
import bugzilla
from bugzilla.exceptions import BugzillaError
import tests
REDHAT_URL = (tests.CLICONFIG.REDHAT_URL or
"https://bugzilla.redhat.com")
def _open_bz(url, bzclass=None, **kwargs):
if "use_creds" not in kwargs:
kwargs["use_creds"] = False
return tests.utils.open_functional_bz(bzclass or bugzilla.Bugzilla,
url, kwargs)
def _check(out, mincount, expectstr):
# Since we are running these tests against bugzilla instances in
# the wild, we can't depend on certain data like product lists
# remaining static. Use lax sanity checks in this case
if mincount is not None:
assert len(out.splitlines()) >= mincount
assert expectstr in out
def _test_version(bz, bzversion):
assert bz.bz_ver_major == bzversion[0]
assert bz.bz_ver_minor == bzversion[1]
def test_bugzilla_override():
class MyBugzilla(bugzilla.Bugzilla):
pass
bz = _open_bz("bugzilla.redhat.com", bzclass=MyBugzilla)
assert bz.__class__ is MyBugzilla
assert bz._is_redhat_bugzilla is True # pylint: disable=protected-access
def test_rest_xmlrpc_detection():
# The default: use XMLRPC
bz = _open_bz("bugzilla.redhat.com")
assert bz.is_xmlrpc()
assert "/xmlrpc.cgi" in bz.url
assert bz.__class__ is bugzilla.RHBugzilla
# See /rest in the URL, so use REST
bz = _open_bz("bugzilla.redhat.com/rest")
assert bz.is_rest()
with pytest.raises(bugzilla.BugzillaError) as e:
dummy = bz._proxy # pylint: disable=protected-access
assert "raw XMLRPC access is not provided" in str(e)
# See /xmlrpc.cgi in the URL, so use XMLRPC
bz = _open_bz("bugzilla.redhat.com/xmlrpc.cgi")
assert bz.is_xmlrpc()
assert bz._proxy # pylint: disable=protected-access
def test_apikey_error_scraping():
# Ensure the API key does not leak into any requests exceptions
fakekey = "FOOBARMYKEY"
with pytest.raises(Exception) as e:
_open_bz("https://bugzilla.redhat.nopedontexist",
force_rest=True, api_key=fakekey)
assert fakekey not in str(e.value)
with pytest.raises(Exception) as e:
_open_bz("https://bugzilla.redhat.nopedontexist",
force_xmlrpc=True, api_key=fakekey)
assert fakekey not in str(e.value)
with pytest.raises(Exception) as e:
_open_bz("https://httpstat.us/502&foo",
force_xmlrpc=True, api_key=fakekey)
assert "Client Error" in str(e.value)
assert fakekey not in str(e.value)
with pytest.raises(Exception) as e:
_open_bz("https://httpstat.us/502&foo",
force_rest=True, api_key=fakekey)
assert "Client Error" in str(e.value)
assert fakekey not in str(e.value)
def test_xmlrpc_bad_url():
with pytest.raises(bugzilla.BugzillaError) as e:
_open_bz("https://example.com/#xmlrpc")
assert "URL may not be an XMLRPC URL" in str(e)
###################
# mozilla testing #
###################
def test_mozilla(backends):
url = "bugzilla.mozilla.org"
bz = _open_bz(url, **backends)
# bugzilla.mozilla.org returns version values in YYYY-MM-DD
# format, so just try to confirm that
assert bz.__class__ == bugzilla.Bugzilla
assert bz.bz_ver_major >= 2016
assert bz.bz_ver_minor in range(1, 13)
##################
# gentoo testing #
##################
def test_gentoo(backends):
url = "bugs.gentoo.org"
bzversion = (5, 0)
bz = _open_bz(url, **backends)
_test_version(bz, bzversion)
# This is a bugzilla 5.0 instance, which supports URL queries now
query_url = ("https://bugs.gentoo.org/buglist.cgi?"
"component=[CS]&product=Doc%20Translations"
"&query_format=advanced&resolution=FIXED")
ret = bz.query(bz.url_to_query(query_url))
assert len(ret) > 0
##################
# redhat testing #
##################
def testInfoProducts(run_cli, backends):
bz = _open_bz(REDHAT_URL, **backends)
out = run_cli("bugzilla info --products", bz)
_check(out, 123, "Virtualization Tools")
def testInfoComps(run_cli, backends):
bz = _open_bz(REDHAT_URL, **backends)
out = run_cli("bugzilla info --components 'Virtualization Tools'", bz)
_check(out, 8, "virtinst")
def testInfoVers(run_cli, backends):
bz = _open_bz(REDHAT_URL, **backends)
out = run_cli("bugzilla info --versions Fedora", bz)
_check(out, 17, "rawhide")
def testInfoCompOwners(run_cli, backends):
bz = _open_bz(REDHAT_URL, **backends)
out = run_cli("bugzilla info "
"--component_owners 'Virtualization Tools'", bz)
_check(out, None, "libvirt: Libvirt Maintainers")
def testQuery(run_cli, backends):
bz = _open_bz(REDHAT_URL, **backends)
args = "--product Fedora --component python-bugzilla --version 14"
cli = "bugzilla query %s --bug_status CLOSED" % args
mincount = 4
expectbug = "621030"
out = run_cli(cli, bz)
assert len(out.splitlines()) >= mincount
assert bool([l1 for l1 in out.splitlines() if
l1.startswith("#" + expectbug)])
# Check --ids output option
out2 = run_cli(cli + " --ids", bz)
assert len(out.splitlines()) == len(out2.splitlines())
assert bool([l2 for l2 in out2.splitlines() if
l2 == expectbug])
def testQueryFull(run_cli, backends):
bz = _open_bz(REDHAT_URL, **backends)
bugid = "621601"
out = run_cli("bugzilla query --full --bug_id %s" % bugid, bz)
_check(out, 60, "end-of-life (EOL)")
def testQueryRaw(run_cli, backends):
bz = _open_bz(REDHAT_URL, **backends)
bugid = "307471"
out = run_cli("bugzilla query --raw --bug_id %s" % bugid, bz)
_check(out, 70, "ATTRIBUTE[whiteboard]: bzcl34nup")
def testQueryOneline(run_cli, backends):
bz = _open_bz(REDHAT_URL, **backends)
bugid = "785016"
out = run_cli("bugzilla query --oneline --bug_id %s" % bugid, bz)
assert len(out.splitlines()) == 1
assert out.splitlines()[0].startswith("#%s" % bugid)
assert "[---] fedora-review+,fedora-cvs+" in out
bugid = "720784"
out = run_cli("bugzilla query --oneline --bug_id %s" % bugid, bz)
assert len(out.splitlines()) == 1
assert out.splitlines()[0].startswith("#%s" % bugid)
assert " CVE-2011-2527" in out
def testQueryExtra(run_cli, backends):
bz = _open_bz(REDHAT_URL, **backends)
bugid = "307471"
out = run_cli("bugzilla query --extra --bug_id %s" % bugid, bz)
assert ("#%s" % bugid) in out
assert " +Status Whiteboard: bzcl34nup" in out
def testQueryFormat(run_cli, backends):
bz = _open_bz(REDHAT_URL, **backends)
args = ("--bug_id 307471 --outputformat=\"id=%{bug_id} "
"sw=%{whiteboard:status} needinfo=%{flag:needinfo} "
"sum=%{summary}\"")
out = run_cli("bugzilla query %s" % args, bz)
assert "id=307471 sw= bzcl34nup needinfo= " in out
args = ("--bug_id 785016 --outputformat=\"id=%{bug_id} "
"sw=%{whiteboard:status} flag=%{flag:fedora-review} "
"sum=%{summary}\"")
out = run_cli("bugzilla query %s" % args, bz)
assert "id=785016 sw= flag=+" in out
# Unicode in this bug's summary
args = "--bug_id 522796 --outputformat \"%{summary}\""
out = run_cli("bugzilla query %s" % args, bz)
assert "V34 — system" in out
def testQueryURL(run_cli, backends):
bz = _open_bz(REDHAT_URL, **backends)
qurl = ("/buglist.cgi?f1=creation_ts"
"&list_id=973582&o1=greaterthaneq&classification=Fedora&"
"o2=lessthaneq&query_format=advanced&f2=creation_ts"
"&v1=2010-01-01&component=python-bugzilla&v2=2010-06-01"
"&product=Fedora")
url = REDHAT_URL
if "/xmlrpc.cgi" in url:
url = url.replace("/xmlrpc.cgi", qurl)
else:
url += qurl
out = run_cli("bugzilla query --from-url \"%s\"" % url, bz)
_check(out, 10, "#553878 CLOSED")
def testQueryFixedIn(run_cli, backends):
bz = _open_bz(REDHAT_URL, **backends)
out = run_cli("bugzilla query --fixed_in anaconda-15.29-1", bz)
assert len(out.splitlines()) == 4
assert "#629311 CLOSED" in out
def testQueryExtrafieldPool(run_cli, backends):
# rhbz has an agile 'pool' extension but doesn't return the field
# by default. Check that '-extrafield pool' returns it for --json output
bz = _open_bz(REDHAT_URL, **backends)
out1 = run_cli("bugzilla query --id 1717616 --json", bz)
out2 = run_cli("bugzilla query --id 1717616 --json --extrafield pool", bz)
assert "current_sprint_id" not in out1
assert "current_sprint_id" in out2
def testComponentsDetails(backends):
"""
Fresh call to getcomponentsdetails should properly refresh
"""
bz = _open_bz(REDHAT_URL, **backends)
assert bool(bz.getcomponentsdetails("Red Hat Developer Toolset"))
def testGetBugAlias(backends):
"""
getbug() works if passed an alias
"""
bz = _open_bz(REDHAT_URL, **backends)
bug = bz.getbug("CVE-2011-2527")
assert bug.bug_id == 720773
def testGetBug404(backends):
"""
getbug() is expected to raise an error, if a bug ID or alias does not exist
"""
bz = _open_bz(REDHAT_URL, **backends)
try:
bz.getbug(100000000)
except Fault as error: # XMLRPC API
assert error.faultCode == 101
except BugzillaError as error: # REST API
assert error.code == 101
else:
raise AssertionError("No exception raised")
def testGetBugAlias404(backends):
"""
getbug() is expected to raise an error, if a bug ID or alias does not exist
"""
bz = _open_bz(REDHAT_URL, **backends)
try:
bz.getbug("CVE-1234-4321")
except Fault as error: # XMLRPC API
assert error.faultCode == 100
except BugzillaError as error: # REST API
assert error.code == 100
else:
raise AssertionError("No exception raised")
def testGetBugAliasIncludedField(backends):
bz = _open_bz(REDHAT_URL, **backends)
bug = bz.getbug("CVE-2011-2527", include_fields=["id"])
assert bug.bug_id == 720773
def testQuerySubComponent(run_cli, backends):
bz = _open_bz(REDHAT_URL, **backends)
# Test special error wrappers in bugzilla/_cli.py
out = run_cli("bugzilla query --product 'Red Hat Enterprise Linux 7' "
"--component lvm2 --sub-component 'Thin Provisioning'", bz)
assert len(out.splitlines()) >= 3
assert "#1060931 " in out
def testBugFields(backends):
bz = _open_bz(REDHAT_URL, **backends)
fields = bz.getbugfields(names=["product"])[:]
assert fields == ["product"]
bz.getbugfields(names=["product", "bug_status"], force_refresh=True)
assert set(bz.bugfields) == set(["product", "bug_status"])
def testProductGetMisc(backends):
bz = _open_bz(REDHAT_URL, **backends)
assert bz.product_get(ptype="enterable", include_fields=["id"])
assert bz.product_get(ptype="selectable", include_fields=["name"])
def testBugAutoRefresh(backends):
bz = _open_bz(REDHAT_URL, **backends)
bz.bug_autorefresh = True
bug = bz.query(bz.build_query(bug_id=720773,
include_fields=["summary"]))[0]
assert hasattr(bug, "component")
assert bool(bug.component)
bz.bug_autorefresh = False
bug = bz.query(bz.build_query(bug_id=720773,
include_fields=["summary"]))[0]
assert not hasattr(bug, "component")
try:
assert bool(bug.component)
except Exception as e:
assert "adjust your include_fields" in str(e)
def testExtraFields(backends):
bz = _open_bz(REDHAT_URL, **backends)
# Check default extra_fields will pull in comments
bug = bz.getbug(720773, exclude_fields=["product"])
assert "comments" in dir(bug)
assert "product" not in dir(bug)
# Ensure that include_fields overrides default extra_fields
bug = bz.getbug(720773, include_fields=["summary"])
assert "summary" in dir(bug)
assert "comments" not in dir(bug)
def testExternalBugsOutput(run_cli, backends):
bz = _open_bz(REDHAT_URL, **backends)
out = run_cli('bugzilla query --bug_id 989253 '
'--outputformat="%{external_bugs}"', bz)
assert "bugzilla.gnome.org/show_bug.cgi?id=703421" in out
assert "External bug: https://bugs.launchpad.net/bugs/1203576" in out
def testActiveComps(run_cli, backends):
bz = _open_bz(REDHAT_URL, **backends)
out = run_cli("bugzilla info --components 'Virtualization Tools' "
"--active-components", bz)
assert "virtinst" not in out
out = run_cli("bugzilla info --component_owners 'Virtualization Tools' "
"--active-components", bz)
assert "virtinst" not in out
def testFaults(run_cli, backends):
bz = _open_bz(REDHAT_URL, **backends)
# Test special error wrappers in bugzilla/_cli.py
out = run_cli("bugzilla query --field=IDONTEXIST=FOO", bz,
expectfail=True)
assert "Server error:" in out
out = run_cli("bugzilla "
"--bugzilla https://example.com/xmlrpc.cgi "
"query --field=IDONTEXIST=FOO", None, expectfail=True)
assert "Connection lost/failed" in out
out = run_cli("bugzilla "
"--bugzilla https://expired.badssl.com/ "
"query --bug_id 1234", None, expectfail=True)
assert "trust the remote server" in out
assert "--nosslverify" in out
def test_login_stubs(backends):
bz = _open_bz(REDHAT_URL, **backends)
# Failed login, verifies our backends are calling the correct API
with pytest.raises(bugzilla.BugzillaError) as e:
bz.login("foo", "bar")
assert "Login failed" in str(e)
# Works fine when not logged in
bz.logout()
def test_redhat_version(backends):
bzversion = (5, 0)
bz = _open_bz(REDHAT_URL, **backends)
if not tests.CLICONFIG.REDHAT_URL:
_test_version(bz, bzversion)
def test_bug_misc(backends):
bz = _open_bz(REDHAT_URL, **backends)
# Ensure weburl is generated consistently whether
# we are using XMLRPC or REST
bug = bz.getbug(720773)
assert bug.weburl == "https://bugzilla.redhat.com/show_bug.cgi?id=720773"
070701000000C3000081A400000000000000000000000166EC671500007C17000000000000000000000000000000000000004900000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/test_rw_functional.py#
# Copyright Red Hat, Inc. 2012
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
#
"""
Unit tests that do permanent functional against a real bugzilla instances.
"""
import datetime
import inspect
import os
import random
import sys
import bugzilla
import tests
import tests.mockbackend
import tests.utils
RHURL = tests.CLICONFIG.REDHAT_URL or "bugzilla.stage.redhat.com"
##################
# helper methods #
##################
def _split_int(s):
return [int(i) for i in s.split(",")]
def _open_bz(**kwargs):
return tests.utils.open_functional_bz(bugzilla.RHBugzilla, RHURL, kwargs)
if not _open_bz().logged_in:
print("\nR/W tests require cached login credentials for url=%s\n" % RHURL)
sys.exit(1)
def _check_have_admin(bz):
funcname = inspect.stack()[1][3]
# groupnames is empty for any user if our logged in user does not
# have admin privs.
# Check a known account that likely won't ever go away
ret = bool(bz.getuser("anaconda-maint-list@redhat.com").groupnames)
if not ret:
print("\nNo admin privs, reduced testing of %s" % funcname)
return ret
def _set_have_dev(bug, assigned_to):
# This will only take effect if the logged in user has fedora dev perms
have_dev = bug.assigned_to == assigned_to
bug._testsuite_have_dev = have_dev # pylint: disable=protected-access
def _bug_close(run_cli, bug):
# Pre-close it
bz = bug.bugzilla
run_cli("bugzilla modify --close NOTABUG %s --minor-update" % bug.id, bz)
bug.refresh()
assert bug.status == "CLOSED"
assert bug.resolution == "NOTABUG"
def _makebug(run_cli, bz):
"""
Make a basic bug that the logged in user can maximally manipulate
"""
product = "Fedora"
component = "python-bugzilla"
version = "rawhide"
assigned_to = "triage@lists.fedoraproject.org"
summary = ("python-bugzilla test basic bug %s" %
datetime.datetime.today())
newout = run_cli("bugzilla new "
f"--product '{product}' "
f"--component '{component}' "
f"--version '{version}' "
f"--assigned_to '{assigned_to}' "
f"--summary \"{summary}\" "
"--comment \"Test bug from the python-bugzilla test suite\" "
"--outputformat \"%{bug_id}\"", bz)
bugid = int(newout.splitlines()[-1])
bug = bz.getbug(bugid)
print("\nCreated bugid: %s" % bug.id)
assert bug.component == component
assert bug.version == version
assert bug.summary == summary
_set_have_dev(bug, assigned_to)
_bug_close(run_cli, bug)
return bug
def _check_have_dev(bug):
funcname = inspect.stack()[1][3]
have_dev = bug._testsuite_have_dev # pylint: disable=protected-access
if not have_dev:
print("\nNo dev privs, reduced testing of %s" % funcname)
return have_dev
class _BugCache:
cache = {}
@classmethod
def get_bug(cls, run_cli, bz):
key = bz.is_xmlrpc() and "xmlrpc" or "rest"
if key not in cls.cache:
cls.cache[key] = _makebug(run_cli, bz)
return cls.cache[key]
def _make_subcomponent_bug(run_cli, bz):
"""
Helper for creating a bug that can handle rhbz sub components
"""
summary = ("python-bugzilla test manyfields bug %s" %
datetime.datetime.today())
assigned_to = "triage@lists.fedoraproject.org"
url = "http://example.com"
osval = "Windows"
cc = "triage@lists.fedoraproject.org"
assigned_to = "triage@lists.fedoraproject.org"
blocked = "461686,461687"
dependson = "427301"
comment = "Test bug from python-bugzilla test suite"
# We use this product+component to test sub_component
product = "Bugzilla"
component = "Extensions"
version = "5.0"
sub_component = "AgileTools"
alias = "pybz-%s" % datetime.datetime.today().strftime("%s")
newout = run_cli("bugzilla new "
f"--product '{product}' "
f"--version '{version}' "
f"--component '{component}' "
f"--sub-component '{sub_component}' "
f"--summary \"{summary}\" "
f"--comment \"{comment}\" "
f"--url {url} "
f"--os {osval} "
f"--cc {cc} "
f"--assigned_to {assigned_to} "
f"--blocked {blocked} "
f"--dependson {dependson} "
f"--alias {alias} "
"--arch ppc --severity Urgent --priority Low "
"--outputformat \"%{bug_id}\"", bz)
bugid = int(newout.splitlines()[-1])
bug = bz.getbug(bugid, extra_fields=["sub_components"])
print("\nCreated bugid: %s" % bugid)
_set_have_dev(bug, assigned_to)
have_dev = _check_have_dev(bug)
assert bug.summary == summary
assert bug.bug_file_loc == url
assert bug.op_sys == osval
assert all([e in bug.cc for e in cc.split(",")])
assert bug.longdescs[0]["text"] == comment
assert bug.sub_components == {component: [sub_component]}
assert bug.alias == [alias]
if have_dev:
assert bug.blocks == _split_int(blocked)
assert bug.depends_on == _split_int(dependson)
else:
# Using a non-dev account seems to fail to set these at bug create time
assert bug.blocks == []
assert bug.depends_on == []
_bug_close(run_cli, bug)
return bug
##############
# test cases #
##############
def test0LoggedInNoCreds(backends):
bz = _open_bz(**backends, use_creds=False)
assert not bz.logged_in
def test0ClassDetection():
bz = bugzilla.Bugzilla(RHURL, use_creds=False)
assert bz.__class__ is bugzilla.RHBugzilla
def test04NewBugAllFields(run_cli, backends):
"""
Create a bug using all 'new' fields, check some values, close it
"""
bz = _open_bz(**backends)
bug = _make_subcomponent_bug(run_cli, bz)
# Verify hasattr works
assert hasattr(bug, "id")
assert hasattr(bug, "bug_id")
# Close the bug
run_cli("bugzilla modify "
"--close WONTFIX %s " %
bug.id, bz)
bug.refresh()
assert bug.status == "CLOSED"
assert bug.resolution == "WONTFIX"
# Check bug's minimal history
ret = bug.get_history_raw()
assert len(ret["bugs"]) == 1
assert len(ret["bugs"][0]["history"])
def test05ModifyStatus(run_cli, backends):
"""
Modify status and comment fields for an existing bug
"""
bz = _open_bz(**backends)
bug = _BugCache.get_bug(run_cli, bz)
have_dev = _check_have_dev(bug)
cmd = "bugzilla modify %s " % bug.id
origstatus = bug.status
perm_error = "not allowed to (un)mark comments"
# Set to ON_QA with a private comment
try:
status = "ON_QA"
comment = ("changing status to %s at %s" %
(status, datetime.datetime.today()))
run_cli(cmd +
"--status %s --comment \"%s\" --private" % (status, comment), bz)
bug.refresh()
assert bug.status == status
assert bug.longdescs[-1]["is_private"] == 1
assert bug.longdescs[-1]["text"] == comment
except RuntimeError as e:
if have_dev:
raise
assert perm_error in str(e)
# Close bug as DEFERRED with a private comment
try:
resolution = "DEFERRED"
comment = ("changing status to CLOSED=%s at %s" %
(resolution, datetime.datetime.today()))
run_cli(cmd +
"--close %s --comment \"%s\" --private" %
(resolution, comment), bz)
bug.refresh()
assert bug.status == "CLOSED"
assert bug.resolution == resolution
assert bug.comments[-1]["is_private"] == 1
assert bug.comments[-1]["text"] == comment
except RuntimeError as e:
if have_dev:
raise
assert perm_error in str(e)
# Set to assigned
run_cli(cmd + "--status ASSIGNED", bz)
bug.refresh()
assert bug.status == "ASSIGNED"
# Close bug as dup with no comment
dupeid = "461686"
desclen = len(bug.longdescs)
run_cli(cmd +
"--close DUPLICATE --dupeid %s" % dupeid, bz)
bug.refresh()
assert bug.dupe_of == int(dupeid)
assert len(bug.longdescs) == (desclen + 1)
assert "marked as a duplicate" in bug.longdescs[-1]["text"]
# bz.setstatus test
try:
comment = ("adding lone comment at %s" % datetime.datetime.today())
bug.setstatus("POST", comment=comment, private=True)
bug.refresh()
assert bug.longdescs[-1]["is_private"] == 1
assert bug.longdescs[-1]["text"] == comment
assert bug.status == "POST"
except Exception as e:
if have_dev:
raise
assert perm_error in str(e)
# bz.close test
fixed_in = str(datetime.datetime.today())
bug.close("ERRATA", fixedin=fixed_in)
bug.refresh()
assert bug.status == "CLOSED"
assert bug.resolution == "ERRATA"
assert bug.fixed_in == fixed_in
# bz.addcomment test
comment = ("yet another test comment %s" % datetime.datetime.today())
bug.addcomment(comment, private=False)
bug.refresh()
assert bug.longdescs[-1]["text"] == comment
assert bug.longdescs[-1]["is_private"] == 0
# Confirm comments is same as getcomments
assert bug.comments == bug.getcomments()
# Reset state
run_cli(cmd + "--status %s" % origstatus, bz)
bug.refresh()
assert bug.status == origstatus
def test06ModifyEmails(run_cli, backends):
"""
Modify cc, assignee, qa_contact for existing bug
"""
bz = _open_bz(**backends)
bug = _BugCache.get_bug(run_cli, bz)
user = bug.creator
have_dev = _check_have_dev(bug)
cmd = "bugzilla modify %s " % bug.id
# Test CC list and reset it
email1 = "triage@lists.fedoraproject.org"
run_cli(cmd + "--cc %s --cc %s" % (email1, user), bz)
bug.refresh()
assert email1 in bug.cc
assert user in bug.cc
# Remove CC via command line
# Unprivileged user can only add/remove their own CC value
run_cli(cmd + "--cc=-%s" % user, bz)
bug.refresh()
assert user not in bug.cc
# Re-add CC via API
bug.addcc(user)
bug.refresh()
assert user in bug.cc
# Remove it again, via API
bug.deletecc(user)
bug.refresh()
assert user not in bug.cc
assert bug.cc
perm_error = "required permissions may change that field"
# Test assigned and QA target
try:
run_cli(cmd + "--assignee %s --qa_contact %s" % (email1, email1), bz)
bug.refresh()
assert bug.assigned_to == email1
assert bug.qa_contact == email1
except RuntimeError as e:
if have_dev:
raise
assert perm_error in str(e)
# Test --reset options
try:
run_cli(cmd + "--reset-qa-contact --reset-assignee", bz)
bug.refresh()
assert bug.assigned_to != email1
assert bug.qa_contact != email1
except RuntimeError as e:
if have_dev:
raise
assert perm_error in str(e)
def test070ModifyMultiFlags(run_cli, backends):
"""
Modify flags and fixed_in for 2 bugs
"""
bz = _open_bz(**backends)
bugid1 = _BugCache.get_bug(run_cli, bz).id
bugid2 = _makebug(run_cli, bz).id
cmd = "bugzilla modify %s %s " % (bugid1, bugid2)
def flagstr(b):
ret = []
for flag in b.flags:
ret.append(flag["name"] + flag["status"])
return " ".join(sorted(ret))
def cleardict_old(b):
"""
Clear flag dictionary, for format meant for bug.updateflags
"""
clearflags = {}
for flag in b.flags:
clearflags[flag["name"]] = "X"
return clearflags
def cleardict_new(b):
"""
Clear flag dictionary, for format meant for update_bugs
"""
clearflags = []
for flag in b.flags:
clearflags.append({"name": flag["name"], "status": "X"})
return clearflags
bug1 = bz.getbug(bugid1)
if cleardict_old(bug1):
bug1.updateflags(cleardict_old(bug1))
bug2 = bz.getbug(bugid2)
if cleardict_old(bug2):
bug2.updateflags(cleardict_old(bug2))
# Set flags and confirm
setflags = "fedora_prioritized_bug? needinfo+"
run_cli(cmd +
" ".join([(" --flag " + f) for f in setflags.split()]), bz)
bug1.refresh()
bug2.refresh()
assert flagstr(bug1) == setflags
assert flagstr(bug2) == setflags
assert bug1.get_flags("needinfo")[0]["status"] == "+"
assert bug1.get_flag_status("fedora_prioritized_bug") == "?"
# Clear flags
if cleardict_new(bug1):
bz.update_flags(bug1.id, cleardict_new(bug1))
bug1.refresh()
if cleardict_new(bug2):
bz.update_flags(bug2.id, cleardict_new(bug2))
bug2.refresh()
# pylint: disable=use-implicit-booleaness-not-comparison
assert cleardict_old(bug1) == {}
assert cleardict_old(bug2) == {}
# Set "Fixed In" field
origfix1 = bug1.fixed_in
origfix2 = bug2.fixed_in
newfix = origfix1 and (origfix1 + "-new1") or "blippy1"
if newfix == origfix2:
newfix = origfix2 + "-2"
run_cli(cmd + "--fixed_in '%s'" % newfix, bz)
bug1.refresh()
bug2.refresh()
assert bug1.fixed_in == newfix
assert bug2.fixed_in == newfix
# Reset fixed_in
run_cli(cmd + "--fixed_in \"-\"", bz)
bug1.refresh()
bug2.refresh()
assert bug1.fixed_in == "-"
assert bug2.fixed_in == "-"
def test071ModifyMisc(run_cli, backends):
bz = _open_bz(**backends)
bug = _BugCache.get_bug(run_cli, bz)
have_dev = _check_have_dev(bug)
cmd = "bugzilla modify %s " % bug.id
# modify --dependson
run_cli(cmd + "--dependson 123456", bz)
bug.refresh()
assert 123456 in bug.depends_on
run_cli(cmd + "--dependson =111222", bz)
bug.refresh()
assert [111222] == bug.depends_on
run_cli(cmd + "--dependson=-111222", bz)
bug.refresh()
assert [] == bug.depends_on
# modify --blocked
run_cli(cmd + "--blocked 123,456", bz)
bug.refresh()
assert [123, 456] == bug.blocks
run_cli(cmd + "--blocked =", bz)
bug.refresh()
assert [] == bug.blocks
# modify --keywords
origkw = bug.keywords
run_cli(cmd + "--keywords +Documentation --keywords EasyFix", bz)
bug.refresh()
assert set(["Documentation", "EasyFix"] + origkw) == set(bug.keywords)
run_cli(cmd + "--keywords=-EasyFix --keywords=-Documentation", bz)
bug.refresh()
assert origkw == bug.keywords
perm_error = "user with the required permissions"
try:
# modify --target_release
# modify --target_milestone
targetbugid = 492463
targetbug = bz.getbug(targetbugid)
targetcmd = "bugzilla modify %s " % targetbugid
run_cli(targetcmd +
"--target_milestone beta --target_release 6.2", bz)
targetbug.refresh()
assert targetbug.target_milestone == "beta"
assert targetbug.target_release == ["6.2"]
run_cli(targetcmd +
"--target_milestone rc --target_release 6.10", bz)
targetbug.refresh()
assert targetbug.target_milestone == "rc"
assert targetbug.target_release == ["6.10"]
except RuntimeError as e:
if have_dev:
raise
assert perm_error in str(e)
try:
# modify --priority
# modify --severity
run_cli(cmd + "--priority low --severity high", bz)
bug.refresh()
assert bug.priority == "low"
assert bug.severity == "high"
run_cli(cmd + "--priority medium --severity medium", bz)
bug.refresh()
assert bug.priority == "medium"
assert bug.severity == "medium"
except RuntimeError as e:
if have_dev:
raise
assert perm_error in str(e)
# modify --os
# modify --platform
# modify --version
run_cli(cmd + "--version rawhide --os Windows --arch ppc "
"--url http://example.com", bz)
bug.refresh()
assert bug.version == "rawhide"
assert bug.op_sys == "Windows"
assert bug.platform == "ppc"
assert bug.url == "http://example.com"
run_cli(cmd + "--version rawhide --os Linux --arch s390 "
"--url http://example.com/fribby", bz)
bug.refresh()
assert bug.version == "rawhide"
assert bug.op_sys == "Linux"
assert bug.platform == "s390"
assert bug.url == "http://example.com/fribby"
# modify --field
run_cli(cmd + "--field cf_fixed_in=foo-bar-1.2.3 \
--field=cf_release_notes=baz", bz)
bug.refresh()
assert bug.fixed_in == "foo-bar-1.2.3"
assert bug.cf_release_notes == "baz"
def test08Attachments(run_cli, backends):
tmpdir = "__test_attach_output"
if tmpdir in os.listdir("."):
os.system("rm -r %s" % tmpdir)
os.mkdir(tmpdir)
os.chdir(tmpdir)
try:
_test8Attachments(run_cli, backends)
finally:
os.chdir("..")
os.system("rm -r %s" % tmpdir)
def _test8Attachments(run_cli, backends):
"""
Get and set attachments for a bug
"""
bz = _open_bz(**backends)
cmd = "bugzilla attach "
testfile = "../tests/data/bz-attach-get1.txt"
# Add attachment as CLI option
setbug = _BugCache.get_bug(run_cli, bz)
setbug = bz.getbug(setbug.id, extra_fields=["attachments"])
orignumattach = len(setbug.attachments)
# Add attachment from CLI with mime guessing
desc1 = "python-bugzilla cli upload %s" % datetime.datetime.today()
out1 = run_cli(cmd + "%s --description \"%s\" --file %s" %
(setbug.id, desc1, testfile), bz)
out1 = out1.splitlines()[-1]
desc2 = "python-bugzilla cli upload %s" % datetime.datetime.today()
out2 = run_cli(cmd + "%s --file test --summary \"%s\"" %
(setbug.id, desc2), bz, stdin=open(testfile).read())
# Expected output format:
# Created attachment <attachid> on bug <bugid>
setbug.refresh()
assert len(setbug.attachments) == (orignumattach + 2)
att1 = setbug.attachments[-2]
attachid = att1["id"]
assert att1["summary"] == desc1
assert att1["id"] == int(out1.splitlines()[0].split()[2])
assert att1["content_type"] == "text/plain"
att2 = setbug.attachments[-1]
assert att2["summary"] == desc2
assert att2["id"] == int(out2.splitlines()[0].split()[2])
assert att2["content_type"] == "application/octet-stream"
# Set attachment flags
assert att1["flags"] == []
bz.updateattachmentflags(setbug.id, att2["id"], "review", status="+")
setbug.refresh()
assert len(setbug.attachments[-1]["flags"]) == 1
assert setbug.attachments[-1]["flags"][0]["name"] == "review"
assert setbug.attachments[-1]["flags"][0]["status"] == "+"
bz.updateattachmentflags(setbug.id, setbug.attachments[-1]["id"],
"review", status="X")
setbug.refresh()
assert setbug.attachments[-1]["flags"] == []
# Set attachment obsolete
bz._backend.bug_attachment_update( # pylint: disable=protected-access
[setbug.attachments[-1]["id"]],
{"is_obsolete": 1})
setbug.refresh()
assert setbug.attachments[-1]["is_obsolete"] == 1
# Get attachment, verify content
out = run_cli(cmd + "--get %s" % attachid, bz).splitlines()
# Expect format:
# Wrote <filename>
fname = out[0].split()[1].strip()
assert len(out) == 1
assert fname == "bz-attach-get1.txt"
assert open(fname).read() == open(testfile).read()
os.unlink(fname)
# Get all attachments
getbug = bz.getbug(setbug.id)
getbug.autorefresh = True
numattach = len(getbug.attachments)
out = run_cli(cmd + "--getall %s" % getbug.id, bz).splitlines()
assert len(out) == numattach
fnames = [line.split(" ", 1)[1].strip() for line in out]
assert len(fnames) == numattach
for f in fnames:
if not os.path.exists(f):
raise AssertionError("filename '%s' not found" % f)
os.unlink(f)
# Get all attachments, but ignore obsolete
ignorecmd = cmd + "--getall %s --ignore-obsolete" % getbug.id
out = run_cli(ignorecmd, bz).splitlines()
assert len(out) == (numattach - 1)
fnames = [line.split(" ", 1)[1].strip() for line in out]
assert len(fnames) == (numattach - 1)
for f in fnames:
if not os.path.exists(f):
raise AssertionError("filename '%s' not found" % f)
os.unlink(f)
def test09Whiteboards(run_cli, backends):
bz = _open_bz(**backends)
bug = _BugCache.get_bug(run_cli, bz)
have_dev = _check_have_dev(bug)
cmd = "bugzilla modify %s " % bug.id
# Set all whiteboards
initval = str(random.randint(1, 1024))
statusstr = initval + "foo, bar, baz bar1"
devstr = initval + "devel"
internalstr = initval + "internal"
qastr = initval + "qa"
run_cmd = (cmd + f"--whiteboard '{statusstr}' ")
if have_dev:
run_cmd += (
f"--devel_whiteboard '{devstr}' "
f"--internal_whiteboard '{internalstr}' "
f"--qa_whiteboard '{qastr}' ")
run_cli(run_cmd, bz)
bug.refresh()
assert bug.whiteboard == statusstr
if have_dev:
assert bug.qa_whiteboard == qastr
assert bug.devel_whiteboard == devstr
assert bug.internal_whiteboard == internalstr
# Remove a tag
run_cli(cmd + "--whiteboard=-bar, ", bz)
bug.refresh()
statusstr = statusstr.replace("bar, ", "")
assert bug.status_whiteboard == statusstr
run_cli(cmd + "--whiteboard NEWBIT", bz)
bug.refresh()
statusstr += " NEWBIT"
assert bug.whiteboard == statusstr
# Clear whiteboards
update = bz.build_update(
whiteboard="", devel_whiteboard="",
internal_whiteboard="", qa_whiteboard="")
bz.update_bugs(bug.id, update)
bug.refresh()
assert bug.whiteboard == ""
if have_dev:
assert bug.qa_whiteboard == ""
assert bug.devel_whiteboard == ""
assert bug.internal_whiteboard == ""
def test10Login(run_cli, monkeypatch):
"""
Failed login test, gives us a bit more coverage
"""
tests.utils.monkeypatch_getpass(monkeypatch)
cmd = "bugzilla --no-cache-credentials --bugzilla %s" % RHURL
# Implied login with --username and --password
ret = run_cli("%s --user foobar@example.com "
"--password foobar query -b 123456" % cmd,
None, expectfail=True)
assert "Login failed: " in ret
# 'login' with explicit options
ret = run_cli("%s --user foobar@example.com "
"--password foobar login" % cmd,
None, expectfail=True)
assert "Login failed: " in ret
# 'login' with positional options
ret = run_cli("%s login foobar@example.com foobar" % cmd,
None, expectfail=True)
assert "Login failed: " in ret
# bare 'login'
stdinstr = "foobar@example.com\n\rfoobar\n\r"
ret = run_cli("%s login" % cmd,
None, expectfail=True, stdin=stdinstr)
assert "Bugzilla Username:" in ret
assert "Bugzilla Password:" in ret
assert "Login failed: " in ret
def test11UserUpdate(backends):
# This won't work if run by the same user we are using
bz = _open_bz(**backends)
email = "anaconda-maint-list@redhat.com"
group = "fedora_contrib"
have_admin = _check_have_admin(bz)
user = bz.getuser(email)
if have_admin:
assert group in user.groupnames
origgroups = user.groupnames
# Test group_get
try:
groupobj = bz.getgroup(group)
groupobj.refresh()
except Exception as e:
if have_admin:
raise
assert bugzilla.BugzillaError.get_bugzilla_error_code(e) == 805
# Remove the group
try:
bz.updateperms(email, "remove", [group])
user.refresh()
assert group not in user.groupnames
except Exception as e:
if have_admin:
raise
assert "Sorry, you aren't a member" in str(e)
# Re add it
try:
bz.updateperms(email, "add", group)
user.refresh()
assert group in user.groupnames
except Exception as e:
if have_admin:
raise
assert "Sorry, you aren't a member" in str(e)
# Set groups
try:
newgroups = user.groupnames[:]
if have_admin:
newgroups.remove(group)
bz.updateperms(email, "set", newgroups)
user.refresh()
assert group not in user.groupnames
except Exception as e:
if have_admin:
raise
assert "Sorry, you aren't a member" in str(e)
# Reset everything
try:
bz.updateperms(email, "set", origgroups)
except Exception as e:
if have_admin:
raise
assert "Sorry, you aren't a member" in str(e)
user.refresh()
assert user.groupnames == origgroups
# Try user create
try:
name = "pythonbugzilla-%s" % datetime.datetime.today()
bz.createuser(name + "@example.com", name, name)
except Exception as e:
if have_admin:
raise
assert "Sorry, you aren't a member" in str(e)
def test11ComponentEditing(backends):
bz = _open_bz(**backends)
component = ("python-bugzilla-testcomponent-%s" %
str(random.randint(1, 1024 * 1024 * 1024)))
basedata = {
"product": "Fedora Documentation",
"component": component,
}
have_admin = _check_have_admin(bz)
def compare(data, newid):
# pylint: disable=protected-access
products = bz._proxy.Product.get({"names": [basedata["product"]]})
compdata = None
for c in products["products"][0]["components"]:
if int(c["id"]) == int(newid):
compdata = c
break
assert bool(compdata)
assert data["component"] == compdata["name"]
assert data["description"] == compdata["description"]
assert data["initialowner"] == compdata["default_assigned_to"]
assert data["initialqacontact"] == compdata["default_qa_contact"]
assert data["is_active"] == compdata["is_active"]
# Create component
data = basedata.copy()
data.update({
"description": "foo test bar",
"initialowner": "crobinso@redhat.com",
"initialqacontact": "extras-qa@fedoraproject.org",
"initialcclist": ["wwoods@redhat.com", "toshio@fedoraproject.org"],
"is_active": True,
})
newid = None
try:
newid = bz.addcomponent(data)['id']
print("Created product=%s component=%s" % (
basedata["product"], basedata["component"]))
compare(data, newid)
except Exception as e:
if have_admin:
raise
assert (("Sorry, you aren't a member" in str(e)) or
# bugzilla 5 error string
("You are not allowed" in str(e)))
# Edit component
data = basedata.copy()
data.update({
"description": "hey new desc!",
"initialowner": "extras-qa@fedoraproject.org",
"initialqacontact": "virt-mgr-maint@redhat.com",
"initialcclist": ["libvirt-maint@redhat.com",
"virt-maint@lists.fedoraproject.org"],
"is_active": False,
})
try:
bz.editcomponent(data)
if newid is not None:
compare(data, newid)
except Exception as e:
if bz.is_rest():
# redhat REST does not support component editing
assert "A REST API resource was not found" in str(e)
elif have_admin:
raise
else:
assert (("Sorry, you aren't a member" in str(e)) or
# bugzilla 5 error string
("You are not allowed" in str(e)))
def test13SubComponents(run_cli, backends):
bz = _open_bz(**backends)
bug = _make_subcomponent_bug(run_cli, bz)
bug.autorefresh = True
assert bug.component == "Extensions"
bz.update_bugs(bug.id, bz.build_update(
component="Extensions", sub_component="RedHat"))
bug.refresh()
assert bug.sub_components == {"Extensions": ["RedHat"]}
bz.update_bugs(bug.id, bz.build_update(
component="Extensions", sub_component="AgileTools"))
bug.refresh()
assert bug.sub_components == {"Extensions": ["AgileTools"]}
def _testExternalTrackers(run_cli, bz):
bugid = _BugCache.get_bug(run_cli, bz).id
ext_bug_id = 380489
# Delete any existing external trackers to get to a known state
ids = [bug['id'] for bug in bz.getbug(bugid).external_bugs]
if ids != []:
bz.remove_external_tracker(ids=ids)
url = "https://bugzilla.mozilla.org"
if bz.bz_ver_major < 5:
url = "http://bugzilla.mozilla.org"
# test adding tracker
kwargs = {
'ext_type_id': 6,
'ext_type_url': url,
'ext_type_description': 'Mozilla Foundation',
}
bz.add_external_tracker(bugid, ext_bug_id, **kwargs)
added_bug = bz.getbug(bugid).external_bugs[0]
assert added_bug['type']['id'] == kwargs['ext_type_id']
assert added_bug['type']['url'] == kwargs['ext_type_url']
assert (added_bug['type']['description'] ==
kwargs['ext_type_description'])
# test updating status, description, and priority by id
kwargs = {
'ids': bz.getbug(bugid).external_bugs[0]['id'],
'ext_status': 'New Status',
'ext_description': 'New Description',
'ext_priority': 'New Priority'
}
bz.update_external_tracker(**kwargs)
updated_bug = bz.getbug(bugid).external_bugs[0]
assert updated_bug['ext_bz_bug_id'] == str(ext_bug_id)
assert updated_bug['ext_status'] == kwargs['ext_status']
assert updated_bug['ext_description'] == kwargs['ext_description']
assert updated_bug['ext_priority'] == kwargs['ext_priority']
# test removing tracker
ids = [bug['id'] for bug in bz.getbug(bugid).external_bugs]
assert len(ids) == 1
bz.remove_external_tracker(ids=ids)
ids = [bug['id'] for bug in bz.getbug(bugid).external_bugs]
assert len(ids) == 0
def test14ExternalTrackersAddUpdateRemoveQuery(run_cli, backends):
bz = _open_bz(**backends)
try:
_testExternalTrackers(run_cli, bz)
except Exception as e:
if not bz.is_rest():
raise
assert "No REST API available" in str(e)
def test15EnsureLoggedIn(run_cli, backends):
bz = _open_bz(**backends)
comm = "bugzilla --ensure-logged-in query --bug_id 979546"
run_cli(comm, bz)
# Test that we don't pollute the query dict with auth info
query = {"id": [1234567]}
origquery = query.copy()
bz.query(query)
assert query == origquery
def test16ModifyTags(run_cli, backends):
bz = _open_bz(**backends)
bug = _BugCache.get_bug(run_cli, bz)
cmd = "bugzilla modify %s " % bug.id
try:
if bug.tags:
bz.update_tags(bug.id, tags_remove=bug.tags)
bug.refresh()
assert bug.tags == []
run_cli(cmd + "--tags foo --tags +bar --tags baz", bz)
bug.refresh()
assert bug.tags == ["foo", "bar", "baz"]
run_cli(cmd + "--tags=-bar", bz)
bug.refresh()
assert bug.tags == ["foo", "baz"]
bz.update_tags(bug.id, tags_remove=bug.tags)
bug.refresh()
assert bug.tags == []
except Exception as e:
if not bz.is_rest():
raise
assert "No REST API available" in str(e)
def test17LoginAPIKey(backends):
api_key = "somefakeapikey1234"
bz = _open_bz(use_creds=False, api_key=api_key, **backends)
try:
assert bz.logged_in is False
# Use this to trigger a warning about api_key
bz.createbug(bz.build_createbug())
except Exception as e:
assert "The API key you specified is invalid" in str(e)
070701000000C4000081A400000000000000000000000166EC671500000DDB000000000000000000000000000000000000003C00000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tests/utils.py# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import difflib
import getpass
import inspect
import io
import os
import pprint
import shlex
import sys
from bugzilla import Bugzilla
import bugzilla._cli
import tests
def get_funcname():
# Return calling function name
return inspect.stack()[1][3]
def tests_path(filename):
testdir = os.path.dirname(__file__)
if testdir not in filename:
return os.path.join(testdir, filename)
return filename
def monkeypatch_getpass(monkeypatch):
monkeypatch.setattr(getpass, "getpass", input)
def sanitize_json(rawout):
# py2.7 leaves trailing whitespace after commas. strip it so
# tests pass on both python versions
return "\n".join([line.rstrip() for line in rawout.splitlines()])
def open_functional_bz(bzclass, url, kwargs):
bz = bzclass(url, **kwargs)
if kwargs.get("force_rest", False):
assert bz.is_rest() is True
if kwargs.get("force_xmlrpc", False):
assert bz.is_xmlrpc() is True
# Set a request timeout of 60 seconds
os.environ["PYTHONBUGZILLA_REQUESTS_TIMEOUT"] = "60"
return bz
def open_bz(url, bzclass=Bugzilla, **kwargs):
return open_functional_bz(bzclass=bzclass, url=url, kwargs=kwargs)
def diff_compare(inputdata, filename, expect_out=None):
"""Compare passed string output to contents of filename"""
def _process(data):
if isinstance(data, tuple) and len(data) == 1:
data = data[0]
if isinstance(data, (dict, tuple)):
out = pprint.pformat(data, width=81)
else:
out = str(data)
if not out.endswith("\n"):
out += "\n"
return out
actual_out = _process(inputdata)
if filename:
filename = tests_path(filename)
if not os.path.exists(filename) or tests.CLICONFIG.REGENERATE_OUTPUT:
open(filename, "w").write(actual_out)
expect_out = open(filename).read()
else:
expect_out = _process(expect_out)
diff = "".join(difflib.unified_diff(expect_out.splitlines(1),
actual_out.splitlines(1),
fromfile=filename or "Manual input",
tofile="Generated Output"))
if diff:
raise AssertionError("Conversion outputs did not match.\n%s" % diff)
def do_run_cli(capsys, monkeypatch,
argvstr, bzinstance,
expectfail=False, stdin=None):
"""
Run bin/bugzilla.main() directly with passed argv
"""
argv = shlex.split(argvstr)
monkeypatch.setattr(sys, "argv", argv)
if stdin:
monkeypatch.setattr(sys, "stdin", io.StringIO(stdin))
else:
monkeypatch.setattr(sys.stdin, "isatty", lambda: True)
ret = 0
try:
# pylint: disable=protected-access
if bzinstance is None:
bugzilla._cli.cli()
else:
bugzilla._cli.main(unittest_bz_instance=bzinstance)
except SystemExit as sys_e:
ret = sys_e.code
out, err = capsys.readouterr()
outstr = out + err
if ret != 0 and not expectfail:
raise RuntimeError("Command failed with %d\ncmd=%s\nout=%s" %
(ret, argvstr, outstr))
if ret == 0 and expectfail:
raise RuntimeError("Command succeeded but we expected success\n"
"ret=%d\ncmd=%s\nout=%s" %
(ret, argvstr, outstr))
return outstr
070701000000C5000081A400000000000000000000000166EC671500000382000000000000000000000000000000000000003500000000python-bugzilla-3.2.0+git.1726768917.5eedea3/tox.ini[tox]
envlist = py34,py35,py36,py37,py38,py39,py310
[testenv]
deps =
-rrequirements.txt
-rtest-requirements.txt
commands =
pytest []
[pytest]
addopts = -q --tb=native tests/
[coverage:run]
source = bugzilla/
[coverage:report]
skip_covered = yes
exclude_lines =
# Have to re-enable the standard pragma
pragma: no cover
# Don't complain if tests don't hit defensive assertion code:
raise NotImplementedError
[pycodestyle]
# [E125] Continuation indent isn't different from next block
# [E128] Not indented for visual style
# [E129] visually indented line with same indent as next logical line
# [E301] Blank lines between function definitions
# [E303] Too many blank lines
# [E402] module level import not at top of file
# [E731] do not assign a lambda expression, use a def
# [W504] line break after binary operator
ignore=E125,E128,E129,E301,E303,E402,E731,W504
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!1663 blocks