diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 20:03:01 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 20:03:01 +0000 |
commit | a453ac31f3428614cceb99027f8efbdb9258a40b (patch) | |
tree | f61f87408f32a8511cbd91799f9cececb53e0374 /collections-debian-merged/ansible_collections/community/mysql | |
parent | Initial commit. (diff) | |
download | ansible-upstream.tar.xz ansible-upstream.zip |
Adding upstream version 2.10.7+merged+base+2.10.8+dfsg.upstream/2.10.7+merged+base+2.10.8+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'collections-debian-merged/ansible_collections/community/mysql')
105 files changed, 13042 insertions, 0 deletions
diff --git a/collections-debian-merged/ansible_collections/community/mysql/.github/workflows/ansible-test-plugins.yml b/collections-debian-merged/ansible_collections/community/mysql/.github/workflows/ansible-test-plugins.yml new file mode 100644 index 00000000..cd27b107 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/.github/workflows/ansible-test-plugins.yml @@ -0,0 +1,145 @@ +name: Plugins CI +on: + push: + paths: + - 'plugins/**' + - 'tests/**' + - '.github/workflows/ansible-test-plugins.yml' + pull_request: + paths: + - 'plugins/**' + - 'tests/**' + - '.github/workflows/ansible-test-plugins.yml' + schedule: + - cron: '0 6 * * *' + + +env: + mysql_version_file: "./ansible_collections/community/mysql/tests/integration/targets/setup_mysql/defaults/main.yml" + connector_version_file: "./ansible_collections/community/mysql/tests/integration/targets/setup_mysql/vars/main.yml" + +jobs: + sanity: + name: "Sanity (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }})" + runs-on: ubuntu-latest + strategy: + matrix: + ansible: + - stable-2.9 + - stable-2.10 + - devel + steps: + + - name: Check out code + uses: actions/checkout@v2 + with: + path: ansible_collections/community/mysql + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Install ansible-base (${{ matrix.ansible }}) + run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check + + - name: Run sanity tests + run: ansible-test sanity --docker -v --color + working-directory: ./ansible_collections/community/mysql + + integration: + name: "Integration (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }}, MySQL: ${{ matrix.mysql }}, Connector: ${{ matrix.connector }})" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + mysql: + - 5.7.31 + - 8.0.22 + ansible: + - stable-2.9 + - stable-2.10 + - devel + python: + - 3.6 + connector: + - pymysql==0.7.10 + - pymysql==0.9.3 + - mysqlclient==2.0.1 + exclude: + - mysql: 8.0.22 + connector: pymysql==0.7.10 + steps: + + - name: Check out code + uses: actions/checkout@v2 + with: + path: ansible_collections/community/mysql + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Install ansible-base (${{ matrix.ansible }}) + run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check + + - name: Set MySQL version (${{ matrix.mysql }}) + run: "sed -i 's/^mysql_version:.*/mysql_version: \"${{ matrix.mysql }}\"/g' ${{ env.mysql_version_file }}" + + - name: Set Connector version (${{ matrix.connector }}) + run: "sed -i 's/^python_packages:.*/python_packages: [${{ matrix.connector }}]/' ${{ env.connector_version_file }}" + + - name: Run integration tests + run: ansible-test integration --docker -v --color --retry-on-error --continue-on-error --python ${{ matrix.python }} --diff --coverage + working-directory: ./ansible_collections/community/mysql + + - name: Generate coverage report. + run: ansible-test coverage xml -v --requirements --group-by command --group-by version + working-directory: ./ansible_collections/community/mysql + + - uses: codecov/codecov-action@v1 + with: + fail_ci_if_error: false + + units: + runs-on: ubuntu-latest + name: Units (â’¶${{ matrix.ansible }}) + strategy: + # As soon as the first unit test fails, + # cancel the others to free up the CI queue + fail-fast: true + matrix: + ansible: + - stable-2.9 + - stable-2.10 + - devel + + steps: + - name: Check out code + uses: actions/checkout@v2 + with: + path: ./ansible_collections/community/mysql + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Install ansible-base (${{matrix.ansible}}) + run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check + + # Run the unit tests + - name: Run unit test + run: ansible-test units -v --color --docker --coverage + working-directory: ./ansible_collections/community/mysql + + # ansible-test support producing code coverage date + - name: Generate coverage report + run: ansible-test coverage xml -v --requirements --group-by command --group-by version + working-directory: ./ansible_collections/community/mysql + + # See the reports at https://codecov.io/gh/GITHUBORG/REPONAME + - uses: codecov/codecov-action@v1 + with: + fail_ci_if_error: false diff --git a/collections-debian-merged/ansible_collections/community/mysql/.github/workflows/ansible-test-roles.yml b/collections-debian-merged/ansible_collections/community/mysql/.github/workflows/ansible-test-roles.yml new file mode 100644 index 00000000..0bc32f6c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/.github/workflows/ansible-test-roles.yml @@ -0,0 +1,56 @@ +name: Roles CI +on: + push: + paths: + - 'roles/**' + - '.github/workflows/ansible-test-roles.yml' + pull_request: + paths: + - 'roles/**' + - '.github/workflows/ansible-test-roles.yml' + schedule: + - cron: '0 6 * * *' + +jobs: + molecule: + name: "Molecule (Python: ${{ matrix.python }}, Ansible: ${{ matrix.ansible }}, MySQL: ${{ matrix.mysql }})" + runs-on: ubuntu-latest + env: + PY_COLORS: 1 + ANSIBLE_FORCE_COLOR: 1 + strategy: + matrix: + mysql: + - 2.0.12 + ansible: + - stable-2.9 + ### it looks like there's errors for 2.10+ with ansible-lint (https://github.com/ansible/ansible-lint/pull/878) + ### and molecule (_maybe_ relating to https://github.com/ansible-community/molecule/pull/2547) + # - stable-2.10 + # - devel + python: + - 2.7 + - 3.8 + + steps: + + - name: Check out code + uses: actions/checkout@v2 + with: + path: ansible_collections/community/mysql + + - name: Set up Python ${{ matrix.python }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + + - name: Install ansible-base (${{ matrix.ansible }}) + run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check + + - name: Install molecule and related dependencies + run: | + pip install ansible-lint docker flake8 molecule testinfra yamllint + + # - name: Run molecule default test scenario + # run: for d in roles/*/; do (cd "$d" && molecule --version && molecule test) done + # working-directory: ./ansible_collections/community/mysql diff --git a/collections-debian-merged/ansible_collections/community/mysql/.gitignore b/collections-debian-merged/ansible_collections/community/mysql/.gitignore new file mode 100644 index 00000000..f4407229 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/.gitignore @@ -0,0 +1,135 @@ +/tests/output/ +/changelogs/.plugin-cache.yaml + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# MacOS +.DS_Store diff --git a/collections-debian-merged/ansible_collections/community/mysql/COPYING b/collections-debian-merged/ansible_collections/community/mysql/COPYING new file mode 100644 index 00000000..f288702d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. 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 +them 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 prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. 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. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey 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; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + 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. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +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. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + 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 +state 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 3 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, see <https://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program 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, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<https://www.gnu.org/licenses/>. + + The GNU 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. But first, please read +<https://www.gnu.org/licenses/why-not-lgpl.html>. diff --git a/collections-debian-merged/ansible_collections/community/mysql/FILES.json b/collections-debian-merged/ansible_collections/community/mysql/FILES.json new file mode 100644 index 00000000..50a541e1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/FILES.json @@ -0,0 +1,1118 @@ +{ + "files": [ + { + "name": ".", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".gitignore", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4173598eb51b77c32e7d2b31d13e97ebfd353c4037b1d771c17d740727c97b92", + "format": 1 + }, + { + "name": ".github", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".github/workflows", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".github/workflows/ansible-test-plugins.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7ee6bec0a191ba7d9e34f2617d817c36dfa5145c83f0e2bcc3904e4d7213e0e2", + "format": 1 + }, + { + "name": ".github/workflows/ansible-test-roles.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cac5855d222e18d6d9719e095605418d872d8d219427c9144d538b202c60d5f9", + "format": 1 + }, + { + "name": "meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "meta/runtime.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "df18179bb2f5447a56ac92261a911649b96821c0b2c08eea62d5cc6b0195203f", + "format": 1 + }, + { + "name": "codecov.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "74ef69719758a63944e959504d5396e442ed023c1c589ed987d9fc4676096d53", + "format": 1 + }, + { + "name": "plugins", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/mysql.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "12ee683d4c29d91a5c417bf150b4a365e9576a34b9e1155d4c39ddc9e653db21", + "format": 1 + }, + { + "name": "plugins/module_utils/database.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bf286e7fb228799c46b66d7e34bacb613838f65d8faa42efd2a1f29ee6759c1b", + "format": 1 + }, + { + "name": "plugins/doc_fragments", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/doc_fragments/mysql.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2b229a7470c782933b1821d207ef1604d6e19257d68dc55085b262bf07f45d67", + "format": 1 + }, + { + "name": "plugins/modules", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/modules/mysql_query.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "692a41e039e78a2f1cdd04d74ffc420e198984a8de9a1ffd70178de5ff9ed34f", + "format": 1 + }, + { + "name": "plugins/modules/mysql_db.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dd4e11f7c08a921d4e213d74332ef2538fcb7ca7093e16d13faeb7d2e77fb333", + "format": 1 + }, + { + "name": "plugins/modules/mysql_variables.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c3bb6c5f18937a765c08112019e0af75e7feb9ea0e70d7dce105cfdfd3d2465e", + "format": 1 + }, + { + "name": "plugins/modules/mysql_replication.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a95c1a5b10f6f7d46756e0864f0b3a7e4680dca4776522e7370a8254244d514e", + "format": 1 + }, + { + "name": "plugins/modules/mysql_user.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dba5eb7d4c3356ec61154f873fa2e6fe7392b9180513e36d438b5ed2a54162af", + "format": 1 + }, + { + "name": "plugins/modules/mysql_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1a292c3492c42c7020dc80c4bc09db0b3c018a70c25c8244e83c7306d5559c27", + "format": 1 + }, + { + "name": "plugins/README.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "533dcaafc018b89297cf72098c36ea97dd2352a3f734fb8cc7568af8818fd7f1", + "format": 1 + }, + { + "name": "tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/sanity", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.10.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "26cf6bdacb30652f6105a97ed3695fbfcdce39966654c8f1179427f5ccd10267", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.9.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "563f5bc68b05205c5545785c81b9245355ed09596c5ab5f01dcc92fd3a54c876", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.11.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "26cf6bdacb30652f6105a97ed3695fbfcdce39966654c8f1179427f5ccd10267", + "format": 1 + }, + { + "name": "tests/integration", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_info", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_info/templates", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_info/templates/my.cnf.j2", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "28ce9e82a1512ce76200e2953ccfaf069f01a7ec915afbeec8c10a677c4f68d8", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_info/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_info/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7134f696d30f85b5e664425adf33a405686fbd05e00b6bfc4e74445298d692c8", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_info/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_info/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "960ad89a5857c865b2b26151b82b0df4bb877ee24faea2cde9fa2ff612cd7604", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_info/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_info/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7cf1f6c0a2f5ce3d8071f4811e14a1c661c39564cf69df83bf9bd394a8bb5538", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_info/tasks/issue-28.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "052b4bf6ed05482ae3de02bb4b191d6bba441905bba24d0349718e504171a71b", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_variables", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_variables/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_variables/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "60880581b8217fe6a3e8f1c2c3db334efb3fda0358eec8a70aa12375fdbda23b", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_variables/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_variables/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eec8d6142beb55aa9cb81e0c777e81a1150e77f9e7362147274def8a08a62721", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_variables/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_variables/tasks/assert_var.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c5e415bc7d84bb9473dd92fdf55978e039ed6f1663f930514f87a7badd921c46", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_variables/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d1bb5e0c23980c724051ddcd1bd3e69f5b3fec2ad10b6c6530f691fed676d574", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_variables/tasks/assert_var_output.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5d2e6e2cc11027c3c50e8125a71fcffaf6a42a6a4569fea3f8983f7652a24af6", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_variables/tasks/assert_fail_msg.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e5f806fc413f2a63986657c7b07a6bb6fe016da206d5eccf8fe1d217ce2fff6e", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_variables/tasks/issue-28.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f7e9ce89d1ff9dcc901eb088745dcb022be866d2f8907562a7688d176e08ee98", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "63a70ef9cf2f4fe7f27cabc5d084ed742d0f05b5ffc2e66a05a17753568309ef", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/files", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/files/create-function.sql", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f9ba9df87bc460be46fb77cabaceef9f93a73474d30e07d3d90648909d3c63f1", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/files/create-procedure.sql", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8386a2c5a55abf33d19484765d8354947773a81141e92434f8e736f766e6932d", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7134f696d30f85b5e664425adf33a405686fbd05e00b6bfc4e74445298d692c8", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2f06b510846ba09c579c74790cb1a3bbf767eb6393377f2c7650fe2e97b751b9", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "985efddbd9c391eb914b28622d10a8d09cced51f78ebfc31984b6de202aa9fca", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "af6476045ffe9c84a6177bd151c2e72fccf102cc5f6dace48dc8ff9680aae650", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/tasks/resource_limits.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3d1c15602321921d2cbed6fef60895586c4e0d343bc9f972aec623c41ccc95b8", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/tasks/remove_user.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "65323965a44c6ad75ff6eefa19a8e44f316c4f9a7b106f2d6cf423f3ef0e45a7", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/tasks/assert_user.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8f9d7cee387e3305757dd0dda7726418683f8e5e2fe95c501b96471e38007b42", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/tasks/create_user.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "408781add05c8da3c25a32f8b448d681fe8cc298756da23cb844dd3f570ff187", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/tasks/test_priv_dict.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d359516ae1db55bd7eba631e6648bdd42d63f743730f2a0c03efe69ef459c0b0", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/tasks/issue-28.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "47c0add7c5f604913ea7f2825be546e30456c34a67c9d80e0c72c1570f5c1740", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/tasks/test_privs.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2fd72e53d7b4f4462259df2a6dbfaa094e7b73c939eb6f86845fa2bf94836698", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/tasks/issue-64560.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f64b1fce4343b6241116a1eb047cc32a47bfa568f1ffe74bf6134c624854adb0", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/tasks/assert_no_user.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a58e6271928c420c2152b96ee8d2e633ad0014603b53397217a8154234d1b479", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/tasks/issue-29511.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d8217bdeda7811c559d7b307e132c57f9c71c2f216750f90665785bd19484e7c", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/tasks/test_priv_append.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6113d89dc494855c495dd64032fc26c7b56351d18ac2fefc8555f52b5b8eeffb", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/tasks/tls_requirements.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a2828bf065501f398d13a8cbb6b444e36e026dd56a89df6adf4962cb5c733806", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_user/tasks/test_user_password.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4a96934cdae644523768cd6ada405b915399e037d81561ba6aa53b39f757fdee", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_tmp_dir", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_tmp_dir/handlers", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_tmp_dir/handlers/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "050157a29c48915cf220b3cdcf5a032e53e359bdc4a210cd457c4836e8e32a4d", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_tmp_dir/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_tmp_dir/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bcb3221a68dc87c7eae0c7ea50c0c0e81380932bf2e21a3dfdee1acc2266c3f3", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_tmp_dir/tasks/default.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2441ac1753320d2cd3bea299c160540e6ae31739ed235923ca478284d1fcfe09", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_tmp_dir/tasks/default-cleanup.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e273324ab90d72180a971d99b9ab69f08689c8be2e6adb991154fc294cf1056e", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_db", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_db/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_db/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "60880581b8217fe6a3e8f1c2c3db334efb3fda0358eec8a70aa12375fdbda23b", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_db/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_db/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0d071894d4222537457256b7e182297d5320673c8a2d0d861d76cc4744f3a3ba", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_db/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_db/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bfa2b6892f4d9ebc688c8402bae5ca8874f25443405e48d096e83e2680e1b583", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "df6493282bf3f755ab68064c8b2274358abaf9d34488c6fdd0635c24fcb77436", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_db/tasks/issue-28.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ccf8129fb5e1f862f7ff6895879be19965d7d30bdabdd9cd60f5073f695f3f4e", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_db/tasks/multi_db_create_delete.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a763b489d5505a93b8077b4b3f5c6485441559b284fc2f90716feb739ad9e324", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_db/tasks/config_overrides_defaults.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fbfb028039d91608ce298dea1d2db4dd51b34d602a8a2b879ed20376e031fad4", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_db/tasks/encoding_dump_import.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d1df019e7552691341672c9f5a9197d8f31c0ddf295521b22176e1bfb4429ab3", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_replication", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_replication/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_replication/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "34abefbbfb4c1805c1b0cbc9e1d6f12e5d1ad95bf54fe594e3c09b4d3ede586d", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_replication/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_replication/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "78c7018a77755f45b1c3c3910607fcf85bf9101e6a2ef8258b367e7a511d9cb2", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_replication/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_replication/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0e4d3adf69babe324829102178fedda78da7b81b307155bddace17abe71e990d", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_replication/tasks/mysql_replication_master_delay.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f9b32d2ff5a44d247d142c8ef07e32923f0ab6ea98754a6df89dddd767096ead", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_replication/tasks/issue-28.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "92fe48b5a3432a675c2261f4069010aa3f8c474d888aeac6155b19afa7bbf583", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_replication/tasks/mysql_replication_initial.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0f7c61173ec459b7c51fcd574a4c705ac55f7ba911e2e07384e29bd5fc22f293", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetmaster_mode.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "108b216f31d36ce17de9de4b8406ec0a01826f2823f24c3283d82b361701a8e8", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "126a7e473ea923a8ae45b09090111530489111fcfe01101dcb17a8145e717513", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_mysql", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_mysql/templates", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_mysql/templates/installed_file.j2", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "838bee046d60c7cf9707aba8f8f33eaa4962aa4798b9d9cb5b66d7648dcf790d", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_mysql/vars", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_mysql/vars/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "010d577b5077e75b93a758e5684a541bdc812b5d292f0448974da65bc1fa7f06", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_mysql/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_mysql/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "09ee5a41655ddad9ac0b6eb81fd27eabadd2e0ec2c68a50c1d33fa818a7306e0", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_mysql/handlers", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_mysql/handlers/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d2d84831a7ceff91296440763b396692baf3ce3d0027cf1884d205dac31b36d6", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_mysql/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_mysql/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f72b32f08568947efa72b811dfec154b66a6cdf0e5bb169b02d0d902f4a0f3c6", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_mysql/tasks/config.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2cc75e51ab0dd44f14b269cedd9f70df61d41ba8968342ed2f4868fcd892f5b8", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_mysql/tasks/install.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2acc35c5783ca591aac07e375758a43238130d4260127f72f41af3dee244ff0b", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_mysql/tasks/verify.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c49c298c50f85b99525cc6cfed8445594afa6e05c5009a00136838d7a46597f9", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_mysql/tasks/setvars.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4c6f650681985cb8d366ce865ec0b682a3a38a4162c67cfb6a4bf8cbba852c7f", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_mysql/tasks/dir.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ebeb28b1ad217cb860646d17484f788091c33634365f59f6016f92742345b1b6", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_query", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_query/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_query/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "12b1878a7556f7ef5ee2146014c54d1e0a490ba6a8889c426377246522d5ce97", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_query/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_query/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "00072ee6cf98e8246594d9455e6d0714fc4d21b145ec00dd52a5fbe226c3903f", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_query/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_query/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "325ea1e1dc136098d51af19b3aa194a5d5082e5426f7dd421fcc12661e60fdb6", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dde0a80e078e1a878250f27a22df8a1802faa48edfcf5f812a1dc3eda83bf33f", + "format": 1 + }, + { + "name": "tests/integration/targets/test_mysql_query/tasks/issue-28.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1d2c5af3bd669045ae6e292a7fc338b96ef7cbcb48fd8b98739f9b7436109a8e", + "format": 1 + }, + { + "name": "tests/integration/old_mariadb_replication", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/old_mariadb_replication/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/old_mariadb_replication/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4dd18f7c3640db3c083acd4bccd974f5ee92ab73485be0b71fd3431feefcd0e3", + "format": 1 + }, + { + "name": "tests/integration/old_mariadb_replication/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/old_mariadb_replication/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "615fbd5ffed3471edd2f8f6417532a4f25ccfb973a3c1f00e89a955ceee1f8b6", + "format": 1 + }, + { + "name": "tests/integration/old_mariadb_replication/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/old_mariadb_replication/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "44e2227e478af9f735498ec9805b1a1076885e4159dcaa21be440115948f0221", + "format": 1 + }, + { + "name": "tests/integration/old_mariadb_replication/tasks/mariadb_master_use_gtid.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f3b513a57566d276388c2613fa9e1f94066d7ea657f87bad8d25c3abb2fb0cf6", + "format": 1 + }, + { + "name": "tests/integration/old_mariadb_replication/tasks/mariadb_replication_initial.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fab156b92a4f740f1032b629c2c02726b716d570e6e306f36e3a7d904b702c83", + "format": 1 + }, + { + "name": "tests/integration/old_mariadb_replication/tasks/mariadb_replication_connection_name.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "033d2e2921c996279846f6d60a91335a8df55ebce7a1ca0428ad33029f21869e", + "format": 1 + }, + { + "name": "tests/integration/old_mariadb_replication/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8598fbccb4b6eaa7150d066f699e7bd937338c9de6cabbe1d630c7b9cf057ca4", + "format": 1 + }, + { + "name": "tests/unit", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/plugins", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/test_mysql.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "20d9ead509f38c46b09e931439e186ce1b678f9ca4ca9cc3242c725c20d05f57", + "format": 1 + }, + { + "name": "tests/unit/plugins/utils.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ba7638619ea90f4ac4f0f0a7639db544f4c4ca8c224bb1ccb4f7fd9cee953d66", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_mysql_user.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "73c4ae75827ac173f8452a864776299aa731cf433a13d2351905780c1006436c", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_mysql_replication.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "091a1941b905c6047c2ac8067fabdb386f810840a25829dbca0d4c53bf11a343", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "changelogs", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "changelogs/CHANGELOG.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3899e95f900720abaad6d3311400193716b3cde93ade77dbebc1cd386f122f8c", + "format": 1 + }, + { + "name": "changelogs/config.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ecadf8e8a36a265fea678ebfaed9ea226d805d9c40e1076f47bf2251c439a123", + "format": 1 + }, + { + "name": "changelogs/changelog.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2d396684c7fa227f10efb6a1745820e64e9635a40bfea2e3a6e3d930bd4029a4", + "format": 1 + }, + { + "name": "COPYING", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986", + "format": 1 + }, + { + "name": "README.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1518261d9b56867ca7e98060bd12ee0bd636df68539569e9d6de901416bc8773", + "format": 1 + } + ], + "format": 1 +}
\ No newline at end of file diff --git a/collections-debian-merged/ansible_collections/community/mysql/MANIFEST.json b/collections-debian-merged/ansible_collections/community/mysql/MANIFEST.json new file mode 100644 index 00000000..a978f332 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/MANIFEST.json @@ -0,0 +1,32 @@ +{ + "collection_info": { + "namespace": "community", + "name": "mysql", + "version": "1.2.0", + "authors": [ + "MySQL Working Group (https://github.com/ansible-collections/community.mysql/wiki/MySQL-Working-Group)" + ], + "readme": "README.md", + "tags": [ + "database", + "mysql", + "mariadb" + ], + "description": "MySQL collection for Ansible", + "license": [], + "license_file": "COPYING", + "dependencies": {}, + "repository": "https://github.com/ansible-collections/community.mysql", + "documentation": "https://github.com/ansible-collections/community.mysql", + "homepage": "https://github.com/ansible-collections/community.mysql", + "issues": "https://github.com/ansible-collections/community.mysql/issues" + }, + "file_manifest_file": { + "name": "FILES.json", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "10670665fed7c2ccd73a3261420b18eeae119eb81883141750ee00f319f339b1", + "format": 1 + }, + "format": 1 +}
\ No newline at end of file diff --git a/collections-debian-merged/ansible_collections/community/mysql/README.md b/collections-debian-merged/ansible_collections/community/mysql/README.md new file mode 100644 index 00000000..3ca1b0f9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/README.md @@ -0,0 +1,54 @@ +# MySQL collection for Ansible +[![Plugins CI](https://github.com/ansible-collections/community.mysql/workflows/Plugins%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Plugins+CI") [![Roles CI](https://github.com/ansible-collections/community.mysql/workflows/Roles%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.mysql/actions?query=workflow%3A"Roles+CI") [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/community.mysql)](https://codecov.io/gh/ansible-collections/community.mysql) + +## Included content + +- **Modules**: + - [mysql_db](https://docs.ansible.com/ansible/latest/modules/mysql_db_module.html) + - [mysql_info](https://docs.ansible.com/ansible/latest/modules/mysql_info_module.html) + - [mysql_query](https://docs.ansible.com/ansible/latest/modules/mysql_query_module.html) + - [mysql_replication](https://docs.ansible.com/ansible/latest/modules/mysql_replication_module.html) + - [mysql_user](https://docs.ansible.com/ansible/latest/modules/mysql_user_module.html) + - [mysql_variables](https://docs.ansible.com/ansible/latest/modules/mysql_variables_module.html) + +## Tested with Ansible + +- 2.9 +- 2.10 +- devel + +## External requirements + +The MySQL modules rely on a MySQL connector. The list of supported drivers is below: + +- [PyMySQL](https://github.com/PyMySQL/PyMySQL) +- [MySQLdb](https://github.com/PyMySQL/mysqlclient-python) +- Support for other Python MySQL connectors may be added in a future release. + +## Using this collection + +### Installing the Collection from Ansible Galaxy + +Before using the MySQL collection, you need to install it with the Ansible Galaxy CLI: + +```bash +ansible-galaxy collection install community.mysql +``` + +You can also include it in a `requirements.yml` file and install it via `ansible-galaxy collection install -r requirements.yml`, using the format: + +```yaml +--- +collections: + - name: community.mysql +``` + +See [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details. + +## Licensing + +<!-- Include the appropriate license information here and a pointer to the full licensing details. If the collection contains modules migrated from the ansible/ansible repo, you must use the same license that existed in the ansible/ansible repo. See the GNU license example below. --> + +GNU General Public License v3.0 or later. + +See [LICENSE](https://www.gnu.org/licenses/gpl-3.0.txt) to see the full text. diff --git a/collections-debian-merged/ansible_collections/community/mysql/changelogs/CHANGELOG.rst b/collections-debian-merged/ansible_collections/community/mysql/changelogs/CHANGELOG.rst new file mode 100644 index 00000000..aad2202a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/changelogs/CHANGELOG.rst @@ -0,0 +1,179 @@ +======================================== +Community MySQL Collection Release Notes +======================================== + +.. contents:: Topics + + +v1.2.0 +====== + +Release Summary +--------------- + +This is the minor release of the ``community.mysql`` collection. +This changelog contains all changes to the modules in this collection +that have been added after the release of ``community.mysql`` 1.1.2. + +Minor Changes +------------- + +- mysql_user - refactor to reduce cursor.execute() calls in preparation for adding query logging (https://github.com/ansible-collections/community.mysql/pull/76). + +Bugfixes +-------- + +- mysql_user - add ``SHOW_ROUTINE`` privilege support (https://github.com/ansible-collections/community.mysql/issues/86). +- mysql_user - fixed creating user with encrypted password in MySQL 8.0 (https://github.com/ansible-collections/community.mysql/pull/79). + +v1.1.2 +====== + +Release Summary +--------------- + +This is the patch release of the ``community.mysql`` collection. +This changelog contains all changes to the modules in this collection that +have been added after the release of ``community.mysql`` 1.1.1. + +Minor Changes +------------- + +- mysql_query - simple refactoring of query type check (https://github.com/ansible-collections/community.mysql/pull/58). +- mysql_user - simple refactoring of priv type check (https://github.com/ansible-collections/community.mysql/pull/58). + +Bugfixes +-------- + +- mysql_db - fix false warning related to ``unsafe_login_password`` option (https://github.com/ansible-collections/community.mysql/issues/33). +- mysql_replication - fix crashes of mariadb >= 10.5.1 and mysql >= 8.0.22 caused by using deprecated terminology (https://github.com/ansible-collections/community.mysql/issues/70). +- mysql_user - fixed change detection when using append_privs (https://github.com/ansible-collections/community.mysql/pull/72). + +v1.1.1 +====== + +Release Summary +--------------- + +This is the patch release of the ``community.mysql`` collection. +This changelog contains all changes to the modules in this collection that +have been added after the release of ``community.mysql`` 1.1.0. + + +Bugfixes +-------- + +- mysql_query - fix failing when single-row query contains commas (https://github.com/ansible-collections/community.mysql/issues/51). + +v1.1.0 +====== + +Release Summary +--------------- + +This is the minor release of the ``community.mysql`` collection. +This changelog contains all changes to the modules in this collection that have been added after the release of ``community.mysql`` 1.0.2. + + +Minor Changes +------------- + +- mysql modules - add the ``check_hostname`` option (https://github.com/ansible-collections/community.mysql/issues/28). +- mysql modules - patch the ``Connection`` class to add a destructor that ensures connections to the server are explicitly closed (https://github.com/ansible-collections/community.mysql/pull/44). + +Bugfixes +-------- + +- mysql modules - fix crash when ``!includedir`` option is in config file (https://github.com/ansible-collections/community.mysql/issues/46). + +v1.0.2 +====== + +Release Summary +--------------- + +This is the patch release of the ``community.mysql`` collection. +This changelog contains all changes to the modules in this collection that have been added after the release of ``community.mysql`` 1.0.1. + + +Bugfixes +-------- + +- mysql_user - fix module's crash when modifying a user with ``host_all`` (https://github.com/ansible-collections/community.mysql/issues/39). + +v1.0.1 +====== + +Release Summary +--------------- + +This is the patch release of the ``community.mysql`` collection. +This changelog contains all changes to the modules in this collection that have been added after the release of ``community.mysql`` 1.0.0. + + +Bugfixes +-------- + +- mysql_db - fix false warning related to ``unsafe_login_password`` option (https://github.com/ansible-collections/community.mysql/issues/33). +- mysql_user - added tests to verify that TLS requirements are removed with an empty ``tls_requires`` option (https://github.com/ansible-collections/community.mysql/issues/20). +- mysql_user - correct procedure to check existing TLS requirements (https://github.com/ansible-collections/community.mysql/pull/26). +- mysql_user - minor syntax fixes (https://github.com/ansible-collections/community.mysql/pull/26). + +v1.0.0 +====== + +Release Summary +--------------- + +This is the first proper release of the ``community.mysql`` collection. +This changelog contains all changes to the modules in this collection that were added after the release of Ansible 2.9.0. + + +Minor Changes +------------- + +- mysql_db - add ``master_data`` parameter (https://github.com/ansible/ansible/pull/66048). +- mysql_db - add ``skip_lock_tables`` option (https://github.com/ansible/ansible/pull/66688). +- mysql_db - add the ``check_implicit_admin`` parameter (https://github.com/ansible/ansible/issues/24418). +- mysql_db - add the ``dump_extra_args`` parameter (https://github.com/ansible/ansible/pull/67747). +- mysql_db - add the ``executed_commands`` returned value (https://github.com/ansible/ansible/pull/65498). +- mysql_db - add the ``force`` parameter (https://github.com/ansible/ansible/pull/65547). +- mysql_db - add the ``restrict_config_file`` parameter (https://github.com/ansible/ansible/issues/34488). +- mysql_db - add the ``unsafe_login_password`` parameter (https://github.com/ansible/ansible/issues/63955). +- mysql_db - add the ``use_shell`` parameter (https://github.com/ansible/ansible/issues/20196). +- mysql_info - add ``exclude_fields`` parameter (https://github.com/ansible/ansible/issues/63319). +- mysql_info - add ``global_status`` filter parameter option and return (https://github.com/ansible/ansible/pull/63189). +- mysql_info - add ``return_empty_dbs`` parameter to list empty databases (https://github.com/ansible/ansible/issues/65727). +- mysql_replication - add ``channel`` parameter (https://github.com/ansible/ansible/issues/29311). +- mysql_replication - add ``connection_name`` parameter (https://github.com/ansible/ansible/issues/46243). +- mysql_replication - add ``fail_on_error`` parameter (https://github.com/ansible/ansible/pull/66252). +- mysql_replication - add ``master_delay`` parameter (https://github.com/ansible/ansible/issues/51326). +- mysql_replication - add ``master_use_gtid`` parameter (https://github.com/ansible/ansible/pull/62648). +- mysql_replication - add ``queries`` return value (https://github.com/ansible/ansible/pull/63036). +- mysql_replication - add support of ``resetmaster`` choice to ``mode`` parameter (https://github.com/ansible/ansible/issues/42870). +- mysql_user - ``priv`` parameter can be string or dictionary (https://github.com/ansible/ansible/issues/57533). +- mysql_user - add TLS REQUIRES parameters (https://github.com/ansible-collections/community.mysql/pull/9). +- mysql_user - add ``plugin_auth_string`` parameter (https://github.com/ansible/ansible/pull/44267). +- mysql_user - add ``plugin_hash_string`` parameter (https://github.com/ansible/ansible/pull/44267). +- mysql_user - add ``plugin`` parameter (https://github.com/ansible/ansible/pull/44267). +- mysql_user - add the resource_limits parameter (https://github.com/ansible-collections/community.general/issues/133). +- mysql_variables - add ``mode`` parameter (https://github.com/ansible/ansible/issues/60119). + +Bugfixes +-------- + +- mysql - dont mask ``mysql_connect`` function errors from modules (https://github.com/ansible/ansible/issues/64560). +- mysql_db - fix Broken pipe error appearance when state is import and the target file is compressed (https://github.com/ansible/ansible/issues/20196). +- mysql_db - fix bug in the ``db_import`` function introduced by https://github.com/ansible/ansible/pull/56721 (https://github.com/ansible/ansible/issues/65351). +- mysql_info - add parameter for __collect to get only what are wanted (https://github.com/ansible-collections/community.general/pull/136). +- mysql_replication - allow to pass empty values to parameters (https://github.com/ansible/ansible/issues/23976). +- mysql_user - Fix idempotence when long grant lists are used (https://github.com/ansible/ansible/issues/68044) +- mysql_user - Remove false positive ``no_log`` warning for ``update_password`` option +- mysql_user - add ``INVOKE LAMBDA`` privilege support (https://github.com/ansible-collections/community.general/issues/283). +- mysql_user - add missed privileges to support (https://github.com/ansible-collections/community.general/issues/617). +- mysql_user - fix ``host_all`` arguments conversion string formatting error (https://github.com/ansible/ansible/issues/29644). +- mysql_user - fix overriding password to the same (https://github.com/ansible-collections/community.general/issues/543). +- mysql_user - fix support privileges with underscore (https://github.com/ansible/ansible/issues/66974). +- mysql_user - fix the error No database selected (https://github.com/ansible/ansible/issues/68070). +- mysql_user - make sure current_pass_hash is a string before using it in comparison (https://github.com/ansible/ansible/issues/60567). +- mysql_variable - fix the module doesn't support variables name with dot (https://github.com/ansible/ansible/issues/54239). diff --git a/collections-debian-merged/ansible_collections/community/mysql/changelogs/changelog.yaml b/collections-debian-merged/ansible_collections/community/mysql/changelogs/changelog.yaml new file mode 100644 index 00000000..426fef59 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/changelogs/changelog.yaml @@ -0,0 +1,211 @@ +ancestor: null +releases: + 1.0.0: + changes: + bugfixes: + - mysql - dont mask ``mysql_connect`` function errors from modules (https://github.com/ansible/ansible/issues/64560). + - mysql_db - fix Broken pipe error appearance when state is import and the target + file is compressed (https://github.com/ansible/ansible/issues/20196). + - mysql_db - fix bug in the ``db_import`` function introduced by https://github.com/ansible/ansible/pull/56721 + (https://github.com/ansible/ansible/issues/65351). + - mysql_info - add parameter for __collect to get only what are wanted (https://github.com/ansible-collections/community.general/pull/136). + - mysql_replication - allow to pass empty values to parameters (https://github.com/ansible/ansible/issues/23976). + - mysql_user - Fix idempotence when long grant lists are used (https://github.com/ansible/ansible/issues/68044) + - mysql_user - Remove false positive ``no_log`` warning for ``update_password`` + option + - mysql_user - add ``INVOKE LAMBDA`` privilege support (https://github.com/ansible-collections/community.general/issues/283). + - mysql_user - add missed privileges to support (https://github.com/ansible-collections/community.general/issues/617). + - mysql_user - fix ``host_all`` arguments conversion string formatting error + (https://github.com/ansible/ansible/issues/29644). + - mysql_user - fix overriding password to the same (https://github.com/ansible-collections/community.general/issues/543). + - mysql_user - fix support privileges with underscore (https://github.com/ansible/ansible/issues/66974). + - mysql_user - fix the error No database selected (https://github.com/ansible/ansible/issues/68070). + - mysql_user - make sure current_pass_hash is a string before using it in comparison + (https://github.com/ansible/ansible/issues/60567). + - mysql_variable - fix the module doesn't support variables name with dot (https://github.com/ansible/ansible/issues/54239). + minor_changes: + - mysql_db - add ``master_data`` parameter (https://github.com/ansible/ansible/pull/66048). + - mysql_db - add ``skip_lock_tables`` option (https://github.com/ansible/ansible/pull/66688). + - mysql_db - add the ``check_implicit_admin`` parameter (https://github.com/ansible/ansible/issues/24418). + - mysql_db - add the ``dump_extra_args`` parameter (https://github.com/ansible/ansible/pull/67747). + - mysql_db - add the ``executed_commands`` returned value (https://github.com/ansible/ansible/pull/65498). + - mysql_db - add the ``force`` parameter (https://github.com/ansible/ansible/pull/65547). + - mysql_db - add the ``restrict_config_file`` parameter (https://github.com/ansible/ansible/issues/34488). + - mysql_db - add the ``unsafe_login_password`` parameter (https://github.com/ansible/ansible/issues/63955). + - mysql_db - add the ``use_shell`` parameter (https://github.com/ansible/ansible/issues/20196). + - mysql_info - add ``exclude_fields`` parameter (https://github.com/ansible/ansible/issues/63319). + - mysql_info - add ``global_status`` filter parameter option and return (https://github.com/ansible/ansible/pull/63189). + - mysql_info - add ``return_empty_dbs`` parameter to list empty databases (https://github.com/ansible/ansible/issues/65727). + - mysql_replication - add ``channel`` parameter (https://github.com/ansible/ansible/issues/29311). + - mysql_replication - add ``connection_name`` parameter (https://github.com/ansible/ansible/issues/46243). + - mysql_replication - add ``fail_on_error`` parameter (https://github.com/ansible/ansible/pull/66252). + - mysql_replication - add ``master_delay`` parameter (https://github.com/ansible/ansible/issues/51326). + - mysql_replication - add ``master_use_gtid`` parameter (https://github.com/ansible/ansible/pull/62648). + - mysql_replication - add ``queries`` return value (https://github.com/ansible/ansible/pull/63036). + - mysql_replication - add support of ``resetmaster`` choice to ``mode`` parameter + (https://github.com/ansible/ansible/issues/42870). + - mysql_user - ``priv`` parameter can be string or dictionary (https://github.com/ansible/ansible/issues/57533). + - mysql_user - add TLS REQUIRES parameters (https://github.com/ansible-collections/community.mysql/pull/9). + - mysql_user - add ``plugin_auth_string`` parameter (https://github.com/ansible/ansible/pull/44267). + - mysql_user - add ``plugin_hash_string`` parameter (https://github.com/ansible/ansible/pull/44267). + - mysql_user - add ``plugin`` parameter (https://github.com/ansible/ansible/pull/44267). + - mysql_user - add the resource_limits parameter (https://github.com/ansible-collections/community.general/issues/133). + - mysql_variables - add ``mode`` parameter (https://github.com/ansible/ansible/issues/60119). + release_summary: 'This is the first proper release of the ``community.mysql`` + collection. + + This changelog contains all changes to the modules in this collection that + were added after the release of Ansible 2.9.0. + + ' + fragments: + - 1.0.0.yml + - 142-mysql_user_add_resource_limit_parameter.yml + - 151-mysql_db_add_use_shell_parameter.yml + - 18-mysql_user-update_password-no_log.yml + - 225-mysql_user_fix_no_database_selected.yml + - 285-mysql_user_invoke_lambda_support.yml + - 369-mysql_user_add_tls_requires.yml + - 428-mysql_db_add_unsafe_login_password_param.yml + - 468-mysql_db_add_restrict_config_file_param.yml + - 486-mysql_db_add_check_implicit_admin_parameter.yml + - 490-mysql_user_fix_cursor_errors.yml + - 609-mysql_user_fix_overriding_password_to_the_same.yml + - 618-mysql_user_add_missed_privileges.yml + - 62648-mysql_replication_add_master_use_gtid_param.yml + - 63036-mysql_replication_add_return_value.yml + - 63130-mysql_replication_add_master_delay_parameter.yml + - 63189-mysql_info-global-status.yml + - 63229-mysql_replication_add_connection_name_parameter.yml + - 63271-mysql_replication_add_channel_parameter.yml + - 63321-mysql_replication_add_resetmaster_to_mode.yml + - 63371-mysql_info_add_exclude_fields_parameter.yml + - 63546-mysql_replication_allow_to_pass_empty_values.yml + - 63547-mysql_variables_add_mode_param.yml + - 64059-mysql_user_fix_password_comparison.yaml + - 64585-mysql_dont_mask_mysql_connect_errors_from_modules.yml + - 65498-mysql_db_add_executed_commands_return_val.yml + - 65547-mysql_db_add_force_param.yml + - 65755-mysql_info_doesnt_list_empty_dbs.yml + - 65789-mysql_user_add_plugin_authentication_parameters.yml + - 66048-mysql_add_master_data_parameter.yml + - 66252-mysql_replication_fail_on_error.yml + - 66688-mysql_db_add_skip_lock_tables_option.yml + - 66801-mysql_user_priv_can_be_dict.yml + - 66806-mysql_variables_not_support_variables_with_dot.yml + - 66974-mysql_user_doesnt_support_privs_with_underscore.yml + - 67337-fix-proxysql-mysql-cursor.yaml + - 67747-mysql_db_add_dump_extra_args_param.yml + - 67767-mysql_db_fix_bug_introduced_by_56721.yml + - mysql_info_add_parameter.yml + - mysql_user_idempotency.yml + release_date: '2020-08-17' + 1.0.1: + changes: + bugfixes: + - mysql_db - fix false warning related to ``unsafe_login_password`` option (https://github.com/ansible-collections/community.mysql/issues/33). + - mysql_user - added tests to verify that TLS requirements are removed with + an empty ``tls_requires`` option (https://github.com/ansible-collections/community.mysql/issues/20). + - mysql_user - correct procedure to check existing TLS requirements (https://github.com/ansible-collections/community.mysql/pull/26). + - mysql_user - minor syntax fixes (https://github.com/ansible-collections/community.mysql/pull/26). + release_summary: 'This is the patch release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules in this collection that + have been added after the release of ``community.mysql`` 1.0.0. + + ' + fragments: + - 1.0.1.yml + - 26-remove_tls_requirements.yml + - 34-mysql_db_fix_false_warning.yml + release_date: '2020-09-29' + 1.0.2: + changes: + bugfixes: + - mysql_user - fix module's crash when modifying a user with ``host_all`` (https://github.com/ansible-collections/community.mysql/issues/39). + release_summary: 'This is the patch release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules in this collection that + have been added after the release of ``community.mysql`` 1.0.1. + + ' + fragments: + - 1.0.2.yml + - 40-mysql_user_fix_error_when_host_all_used.yml + release_date: '2020-10-01' + 1.1.0: + changes: + bugfixes: + - mysql modules - fix crash when ``!includedir`` option is in config file (https://github.com/ansible-collections/community.mysql/issues/46). + minor_changes: + - mysql modules - add the ``check_hostname`` option (https://github.com/ansible-collections/community.mysql/issues/28). + - mysql modules - patch the ``Connection`` class to add a destructor that ensures + connections to the server are explicitly closed (https://github.com/ansible-collections/community.mysql/pull/44). + release_summary: 'This is the minor release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules in this collection that + have been added after the release of ``community.mysql`` 1.0.2. + + ' + fragments: + - 1.1.0.yml + - 35-disable-hostname-check.yml + - 44-close-connection.yml + - 47-mysql_modules_fix_failings_when_include_dir_in_config_file.yml + release_date: '2020-10-13' + 1.1.1: + changes: + bugfixes: + - mysql_query - fix failing when single-row query contains commas (https://github.com/ansible-collections/community.mysql/issues/51). + release_summary: 'This is the patch release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules in this collection that + + have been added after the release of ``community.mysql`` 1.1.0. + + ' + fragments: + - 1.1.1.yml + - 53-mysql_query_fix_single_query_with_commas.yml + release_date: '2020-11-03' + 1.1.2: + changes: + bugfixes: + - mysql_db - fix false warning related to ``unsafe_login_password`` option (https://github.com/ansible-collections/community.mysql/issues/33). + - mysql_replication - fix crashes of mariadb >= 10.5.1 and mysql >= 8.0.22 caused + by using deprecated terminology (https://github.com/ansible-collections/community.mysql/issues/70). + - mysql_user - fixed change detection when using append_privs (https://github.com/ansible-collections/community.mysql/pull/72). + minor_changes: + - mysql_query - simple refactoring of query type check (https://github.com/ansible-collections/community.mysql/pull/58). + - mysql_user - simple refactoring of priv type check (https://github.com/ansible-collections/community.mysql/pull/58). + release_summary: 'This is the patch release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules in this collection that + + have been added after the release of ``community.mysql`` 1.1.1.' + fragments: + - 1.1.2.yml + - 58-mysql_query_refactoring.yml + - 71-mysql_replication_add_replica_keyword_support.yml + - 72-mysql_db_fix_false_warning.yml + - 72-mysql_user_change_detection.yml + release_date: '2020-12-18' + 1.2.0: + changes: + bugfixes: + - mysql_user - add ``SHOW_ROUTINE`` privilege support (https://github.com/ansible-collections/community.mysql/issues/86). + - mysql_user - fixed creating user with encrypted password in MySQL 8.0 (https://github.com/ansible-collections/community.mysql/pull/79). + minor_changes: + - mysql_user - refactor to reduce cursor.execute() calls in preparation for + adding query logging (https://github.com/ansible-collections/community.mysql/pull/76). + release_summary: 'This is the minor release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules in this collection + + that have been added after the release of ``community.mysql`` 1.1.2.' + fragments: + - 1.2.0.yml + - 76-mysql-user-query-refact.yaml + - 79-mysql-user-tests-and-fixes.yml + - 87-mysql_user_show_routine_support.yml + release_date: '2021-01-18' diff --git a/collections-debian-merged/ansible_collections/community/mysql/changelogs/config.yaml b/collections-debian-merged/ansible_collections/community/mysql/changelogs/config.yaml new file mode 100644 index 00000000..559e6c4e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/changelogs/config.yaml @@ -0,0 +1,29 @@ +changelog_filename_template: CHANGELOG.rst +changelog_filename_version_depth: 0 +changes_file: changelog.yaml +changes_format: combined +keep_fragments: false +mention_ancestor: true +new_plugins_after_name: removed_features +notesdir: fragments +prelude_section_name: release_summary +prelude_section_title: Release Summary +sections: +- - major_changes + - Major Changes +- - minor_changes + - Minor Changes +- - breaking_changes + - Breaking Changes / Porting Guide +- - deprecated_features + - Deprecated Features +- - removed_features + - Removed Features (previously deprecated) +- - security_fixes + - Security Fixes +- - bugfixes + - Bugfixes +- - known_issues + - Known Issues +title: Community MySQL Collection +trivial_section_name: trivial diff --git a/collections-debian-merged/ansible_collections/community/mysql/codecov.yml b/collections-debian-merged/ansible_collections/community/mysql/codecov.yml new file mode 100644 index 00000000..e832c21f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/codecov.yml @@ -0,0 +1,2 @@ +fixes: + - "/ansible_collections/community/mysql/::" diff --git a/collections-debian-merged/ansible_collections/community/mysql/meta/runtime.yml b/collections-debian-merged/ansible_collections/community/mysql/meta/runtime.yml new file mode 100644 index 00000000..2ee3c9fa --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/meta/runtime.yml @@ -0,0 +1,2 @@ +--- +requires_ansible: '>=2.9.10' diff --git a/collections-debian-merged/ansible_collections/community/mysql/plugins/README.md b/collections-debian-merged/ansible_collections/community/mysql/plugins/README.md new file mode 100644 index 00000000..5b4711b5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/plugins/README.md @@ -0,0 +1,31 @@ +# Collections Plugins Directory + +This directory can be used to ship various plugins inside an Ansible collection. Each plugin is placed in a folder that +is named after the type of plugin it is in. It can also include the `module_utils` and `modules` directory that +would contain module utils and modules respectively. + +Here is an example directory of the majority of plugins currently supported by Ansible: + +``` +└── plugins + ├── action + ├── become + ├── cache + ├── callback + ├── cliconf + ├── connection + ├── filter + ├── httpapi + ├── inventory + ├── lookup + ├── module_utils + ├── modules + ├── netconf + ├── shell + ├── strategy + ├── terminal + ├── test + └── vars +``` + +A full list of plugin types can be found at [Working With Plugins](https://docs.ansible.com/ansible/latest/plugins/plugins.html). diff --git a/collections-debian-merged/ansible_collections/community/mysql/plugins/doc_fragments/mysql.py b/collections-debian-merged/ansible_collections/community/mysql/plugins/doc_fragments/mysql.py new file mode 100644 index 00000000..a9c3a991 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/plugins/doc_fragments/mysql.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2015, Jonathan Mainguy <jon@soh.re> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +class ModuleDocFragment(object): + + # Standard mysql documentation fragment + DOCUMENTATION = r''' +options: + login_user: + description: + - The username used to authenticate with. + type: str + login_password: + description: + - The password used to authenticate with. + type: str + login_host: + description: + - Host running the database. + - In some cases for local connections the I(login_unix_socket=/path/to/mysqld/socket), + that is usually C(/var/run/mysqld/mysqld.sock), needs to be used instead of I(login_host=localhost). + type: str + default: localhost + login_port: + description: + - Port of the MySQL server. Requires I(login_host) be defined as other than localhost if login_port is used. + type: int + default: 3306 + login_unix_socket: + description: + - The path to a Unix domain socket for local connections. + type: str + connect_timeout: + description: + - The connection timeout when connecting to the MySQL server. + type: int + default: 30 + config_file: + description: + - Specify a config file from which user and password are to be read. + type: path + default: '~/.my.cnf' + ca_cert: + description: + - The path to a Certificate Authority (CA) certificate. This option, if used, must specify the same certificate + as used by the server. + type: path + aliases: [ ssl_ca ] + client_cert: + description: + - The path to a client public key certificate. + type: path + aliases: [ ssl_cert ] + client_key: + description: + - The path to the client private key. + type: path + aliases: [ ssl_key ] + check_hostname: + description: + - Whether to validate the server host name when an SSL connection is required. + - Setting this to C(false) disables hostname verification. Use with caution. + - Requires pymysql >= 0.7.11. + - This optoin has no effect on MySQLdb. + type: bool + version_added: '1.1.0' +requirements: + - PyMySQL (Python 2.7 and Python 3.X), or + - MySQLdb (Python 2.x) +notes: + - Requires the PyMySQL (Python 2.7 and Python 3.X) or MySQL-python (Python 2.X) package installed on the remote host. + The Python package may be installed with apt-get install python-pymysql (Ubuntu; see M(ansible.builtin.apt)) or + yum install python2-PyMySQL (RHEL/CentOS/Fedora; see M(ansible.builtin.yum)). You can also use dnf install python2-PyMySQL + for newer versions of Fedora; see M(ansible.builtin.dnf). + - Be sure you have PyMySQL or MySQLdb library installed on the target machine + for the Python interpreter Ansible uses, for example, if it is Python 3, + you must install the library for Python 3. You can also change the interpreter. + For more information, see U(https://docs.ansible.com/ansible/latest/reference_appendices/interpreter_discovery.html). + - Both C(login_password) and C(login_user) are required when you are + passing credentials. If none are present, the module will attempt to read + the credentials from C(~/.my.cnf), and finally fall back to using the MySQL + default login of 'root' with no password. + - If there are problems with local connections, using I(login_unix_socket=/path/to/mysqld/socket) + instead of I(login_host=localhost) might help. As an example, the default MariaDB installation of version 10.4 + and later uses the unix_socket authentication plugin by default that + without using I(login_unix_socket=/var/run/mysqld/mysqld.sock) (the default path) + causes the error ``Host '127.0.0.1' is not allowed to connect to this MariaDB server``. + - Alternatively, you can use the mysqlclient library instead of MySQL-python (MySQLdb) + which supports both Python 2.X and Python >=3.5. + See U(https://pypi.org/project/mysqlclient/) how to install it. +''' diff --git a/collections-debian-merged/ansible_collections/community/mysql/plugins/module_utils/database.py b/collections-debian-merged/ansible_collections/community/mysql/plugins/module_utils/database.py new file mode 100644 index 00000000..67850308 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/plugins/module_utils/database.py @@ -0,0 +1,189 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# Copyright (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# +# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + + +# Input patterns for is_input_dangerous function: +# +# 1. '"' in string and '--' in string or +# "'" in string and '--' in string +PATTERN_1 = re.compile(r'(\'|\").*--') + +# 2. union \ intersect \ except + select +PATTERN_2 = re.compile(r'(UNION|INTERSECT|EXCEPT).*SELECT', re.IGNORECASE) + +# 3. ';' and any KEY_WORDS +PATTERN_3 = re.compile(r';.*(SELECT|UPDATE|INSERT|DELETE|DROP|TRUNCATE|ALTER)', re.IGNORECASE) + + +class SQLParseError(Exception): + pass + + +class UnclosedQuoteError(SQLParseError): + pass + + +# maps a type of identifier to the maximum number of dot levels that are +# allowed to specify that identifier. For example, a database column can be +# specified by up to 4 levels: database.schema.table.column +_PG_IDENTIFIER_TO_DOT_LEVEL = dict( + database=1, + schema=2, + table=3, + column=4, + role=1, + tablespace=1, + sequence=3, + publication=1, +) +_MYSQL_IDENTIFIER_TO_DOT_LEVEL = dict(database=1, table=2, column=3, role=1, vars=1) + + +def _find_end_quote(identifier, quote_char): + accumulate = 0 + while True: + try: + quote = identifier.index(quote_char) + except ValueError: + raise UnclosedQuoteError + accumulate = accumulate + quote + try: + next_char = identifier[quote + 1] + except IndexError: + return accumulate + if next_char == quote_char: + try: + identifier = identifier[quote + 2:] + accumulate = accumulate + 2 + except IndexError: + raise UnclosedQuoteError + else: + return accumulate + + +def _identifier_parse(identifier, quote_char): + if not identifier: + raise SQLParseError('Identifier name unspecified or unquoted trailing dot') + + already_quoted = False + if identifier.startswith(quote_char): + already_quoted = True + try: + end_quote = _find_end_quote(identifier[1:], quote_char=quote_char) + 1 + except UnclosedQuoteError: + already_quoted = False + else: + if end_quote < len(identifier) - 1: + if identifier[end_quote + 1] == '.': + dot = end_quote + 1 + first_identifier = identifier[:dot] + next_identifier = identifier[dot + 1:] + further_identifiers = _identifier_parse(next_identifier, quote_char) + further_identifiers.insert(0, first_identifier) + else: + raise SQLParseError('User escaped identifiers must escape extra quotes') + else: + further_identifiers = [identifier] + + if not already_quoted: + try: + dot = identifier.index('.') + except ValueError: + identifier = identifier.replace(quote_char, quote_char * 2) + identifier = ''.join((quote_char, identifier, quote_char)) + further_identifiers = [identifier] + else: + if dot == 0 or dot >= len(identifier) - 1: + identifier = identifier.replace(quote_char, quote_char * 2) + identifier = ''.join((quote_char, identifier, quote_char)) + further_identifiers = [identifier] + else: + first_identifier = identifier[:dot] + next_identifier = identifier[dot + 1:] + further_identifiers = _identifier_parse(next_identifier, quote_char) + first_identifier = first_identifier.replace(quote_char, quote_char * 2) + first_identifier = ''.join((quote_char, first_identifier, quote_char)) + further_identifiers.insert(0, first_identifier) + + return further_identifiers + + +def pg_quote_identifier(identifier, id_type): + identifier_fragments = _identifier_parse(identifier, quote_char='"') + if len(identifier_fragments) > _PG_IDENTIFIER_TO_DOT_LEVEL[id_type]: + raise SQLParseError('PostgreSQL does not support %s with more than %i dots' % (id_type, _PG_IDENTIFIER_TO_DOT_LEVEL[id_type])) + return '.'.join(identifier_fragments) + + +def mysql_quote_identifier(identifier, id_type): + identifier_fragments = _identifier_parse(identifier, quote_char='`') + if (len(identifier_fragments) - 1) > _MYSQL_IDENTIFIER_TO_DOT_LEVEL[id_type]: + raise SQLParseError('MySQL does not support %s with more than %i dots' % (id_type, _MYSQL_IDENTIFIER_TO_DOT_LEVEL[id_type])) + + special_cased_fragments = [] + for fragment in identifier_fragments: + if fragment == '`*`': + special_cased_fragments.append('*') + else: + special_cased_fragments.append(fragment) + + return '.'.join(special_cased_fragments) + + +def is_input_dangerous(string): + """Check if the passed string is potentially dangerous. + Can be used to prevent SQL injections. + + Note: use this function only when you can't use + psycopg2's cursor.execute method parametrized + (typically with DDL queries). + """ + if not string: + return False + + for pattern in (PATTERN_1, PATTERN_2, PATTERN_3): + if re.search(pattern, string): + return True + + return False + + +def check_input(module, *args): + """Wrapper for is_input_dangerous function.""" + needs_to_check = args + + dangerous_elements = [] + + for elem in needs_to_check: + if isinstance(elem, str): + if is_input_dangerous(elem): + dangerous_elements.append(elem) + + elif isinstance(elem, list): + for e in elem: + if is_input_dangerous(e): + dangerous_elements.append(e) + + elif elem is None or isinstance(elem, bool): + pass + + else: + elem = str(elem) + if is_input_dangerous(elem): + dangerous_elements.append(elem) + + if dangerous_elements: + module.fail_json(msg="Passed input '%s' is " + "potentially dangerous" % ', '.join(dangerous_elements)) diff --git a/collections-debian-merged/ansible_collections/community/mysql/plugins/module_utils/mysql.py b/collections-debian-merged/ansible_collections/community/mysql/plugins/module_utils/mysql.py new file mode 100644 index 00000000..5af9c202 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/plugins/module_utils/mysql.py @@ -0,0 +1,148 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# Copyright (c), Jonathan Mainguy <jon@soh.re>, 2015 +# Most of this was originally added by Sven Schliesing @muffl0n in the mysql_user.py module +# +# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) + +from __future__ import (absolute_import, division, print_function) +from functools import reduce +__metaclass__ = type + +import os + +from ansible.module_utils.six.moves import configparser +from ansible.module_utils._text import to_native + +try: + import pymysql as mysql_driver + _mysql_cursor_param = 'cursor' +except ImportError: + try: + import MySQLdb as mysql_driver + import MySQLdb.cursors + _mysql_cursor_param = 'cursorclass' + except ImportError: + mysql_driver = None + +mysql_driver_fail_msg = 'The PyMySQL (Python 2.7 and Python 3.X) or MySQL-python (Python 2.X) module is required.' + + +def parse_from_mysql_config_file(cnf): + # Default values of comment_prefix is '#' and ';'. + # '!' added to prevent a parsing error + # when a config file contains !includedir parameter. + cp = configparser.ConfigParser(comment_prefixes=('#', ';', '!')) + cp.read(cnf) + return cp + + +def mysql_connect(module, login_user=None, login_password=None, config_file='', ssl_cert=None, + ssl_key=None, ssl_ca=None, db=None, cursor_class=None, connect_timeout=30, + autocommit=False, config_overrides_defaults=False, check_hostname=None): + config = {} + + if config_file and os.path.exists(config_file): + config['read_default_file'] = config_file + + if config_overrides_defaults: + try: + cp = parse_from_mysql_config_file(config_file) + except Exception as e: + module.fail_json(msg="Failed to parse %s: %s" % (config_file, to_native(e))) + + # Override some commond defaults with values from config file if needed + if cp and cp.has_section('client'): + try: + module.params['login_host'] = cp.get('client', 'host', fallback=module.params['login_host']) + module.params['login_port'] = cp.getint('client', 'port', fallback=module.params['login_port']) + except Exception as e: + if "got an unexpected keyword argument 'fallback'" in e.message: + module.fail_json(msg='To use config_overrides_defaults, ' + 'it needs Python 3.5+ as the default interpreter on a target host') + + if ssl_ca is not None or ssl_key is not None or ssl_cert is not None or check_hostname is not None: + config['ssl'] = {} + + if module.params['login_unix_socket']: + config['unix_socket'] = module.params['login_unix_socket'] + else: + config['host'] = module.params['login_host'] + config['port'] = module.params['login_port'] + + # If login_user or login_password are given, they should override the + # config file + if login_user is not None: + config['user'] = login_user + if login_password is not None: + config['passwd'] = login_password + if ssl_cert is not None: + config['ssl']['cert'] = ssl_cert + if ssl_key is not None: + config['ssl']['key'] = ssl_key + if ssl_ca is not None: + config['ssl']['ca'] = ssl_ca + if db is not None: + config['db'] = db + if connect_timeout is not None: + config['connect_timeout'] = connect_timeout + if check_hostname is not None: + if mysql_driver.__name__ == "pymysql": + version_tuple = (n for n in mysql_driver.__version__.split('.') if n != 'None') + if reduce(lambda x, y: int(x) * 100 + int(y), version_tuple) >= 711: + config['ssl']['check_hostname'] = check_hostname + else: + module.fail_json(msg='To use check_hostname, pymysql >= 0.7.11 is required on the target host') + + if _mysql_cursor_param == 'cursor': + # In case of PyMySQL driver: + db_connection = mysql_driver.connect(autocommit=autocommit, **config) + else: + # In case of MySQLdb driver + db_connection = mysql_driver.connect(**config) + if autocommit: + db_connection.autocommit(True) + + # Monkey patch the Connection class to close the connection when garbage collected + def _conn_patch(conn_self): + conn_self.close() + db_connection.__class__.__del__ = _conn_patch + # Patched + + if cursor_class == 'DictCursor': + return db_connection.cursor(**{_mysql_cursor_param: mysql_driver.cursors.DictCursor}), db_connection + else: + return db_connection.cursor(), db_connection + + +def mysql_common_argument_spec(): + return dict( + login_user=dict(type='str', default=None), + login_password=dict(type='str', no_log=True), + login_host=dict(type='str', default='localhost'), + login_port=dict(type='int', default=3306), + login_unix_socket=dict(type='str'), + config_file=dict(type='path', default='~/.my.cnf'), + connect_timeout=dict(type='int', default=30), + client_cert=dict(type='path', aliases=['ssl_cert']), + client_key=dict(type='path', aliases=['ssl_key']), + ca_cert=dict(type='path', aliases=['ssl_ca']), + check_hostname=dict(type='bool', default=None), + ) + + +def get_server_version(cursor): + """Returns a string representation of the server version.""" + cursor.execute("SELECT VERSION() AS version") + result = cursor.fetchone() + + if isinstance(result, dict): + version_str = result['version'] + else: + version_str = result[0] + + return version_str diff --git a/collections-debian-merged/ansible_collections/community/mysql/plugins/modules/mysql_db.py b/collections-debian-merged/ansible_collections/community/mysql/plugins/modules/mysql_db.py new file mode 100644 index 00000000..aa9ade06 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/plugins/modules/mysql_db.py @@ -0,0 +1,720 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2012, Mark Theunissen <mark.theunissen@gmail.com> +# Sponsored by Four Kitchens http://fourkitchens.com. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: mysql_db +short_description: Add or remove MySQL databases from a remote host +description: +- Add or remove MySQL databases from a remote host. +options: + name: + description: + - Name of the database to add or remove. + - I(name=all) may only be provided if I(state) is C(dump) or C(import). + - List of databases is provided with I(state=dump), I(state=present) and I(state=absent). + - If I(name=all) it works like --all-databases option for mysqldump (Added in 2.0). + required: true + type: list + elements: str + aliases: [db] + state: + description: + - The database state. + type: str + default: present + choices: ['absent', 'dump', 'import', 'present'] + collation: + description: + - Collation mode (sorting). This only applies to new table/databases and + does not update existing ones, this is a limitation of MySQL. + type: str + default: '' + encoding: + description: + - Encoding mode to use, examples include C(utf8) or C(latin1_swedish_ci), + at creation of database, dump or importation of sql script. + type: str + default: '' + target: + description: + - Location, on the remote host, of the dump file to read from or write to. + - Uncompressed SQL files (C(.sql)) as well as bzip2 (C(.bz2)), gzip (C(.gz)) and + xz (Added in 2.0) compressed files are supported. + type: path + single_transaction: + description: + - Execute the dump in a single transaction. + type: bool + default: no + quick: + description: + - Option used for dumping large tables. + type: bool + default: yes + ignore_tables: + description: + - A list of table names that will be ignored in the dump + of the form database_name.table_name. + type: list + elements: str + default: [] + hex_blob: + description: + - Dump binary columns using hexadecimal notation. + type: bool + default: no + version_added: '0.1.0' + force: + description: + - Continue dump or import even if we get an SQL error. + - Used only when I(state) is C(dump) or C(import). + type: bool + default: no + version_added: '0.1.0' + master_data: + description: + - Option to dump a master replication server to produce a dump file + that can be used to set up another server as a slave of the master. + - C(0) to not include master data. + - C(1) to generate a 'CHANGE MASTER TO' statement + required on the slave to start the replication process. + - C(2) to generate a commented 'CHANGE MASTER TO'. + - Can be used when I(state=dump). + type: int + choices: [0, 1, 2] + default: 0 + version_added: '0.1.0' + skip_lock_tables: + description: + - Skip locking tables for read. Used when I(state=dump), ignored otherwise. + type: bool + default: no + version_added: '0.1.0' + dump_extra_args: + description: + - Provide additional arguments for mysqldump. + Used when I(state=dump) only, ignored otherwise. + type: str + version_added: '0.1.0' + use_shell: + description: + - Used to prevent C(Broken pipe) errors when the imported I(target) file is compressed. + - If C(yes), the module will internally execute commands via a shell. + - Used when I(state=import), ignored otherwise. + type: bool + default: no + version_added: '0.1.0' + unsafe_login_password: + description: + - If C(no), the module will safely use a shell-escaped + version of the I(login_password) value. + - It makes sense to use C(yes) only if there are special + symbols in the value and errors C(Access denied) occur. + - Used only when I(state) is C(import) or C(dump) and + I(login_password) is passed, ignored otherwise. + type: bool + default: no + version_added: '0.1.0' + restrict_config_file: + description: + - Read only passed I(config_file). + - When I(state) is C(dump) or C(import), + by default the module passes I(config_file) parameter + using C(--defaults-extra-file) command-line argument to C(mysql/mysqldump) utilities + under the hood that read named option file in addition to usual option files. + - If this behavior is undesirable, use C(yes) to read only named option file. + type: bool + default: no + version_added: '0.1.0' + check_implicit_admin: + description: + - Check if mysql allows login as root/nopassword before trying supplied credentials. + - If success, passed I(login_user)/I(login_password) will be ignored. + type: bool + default: no + version_added: '0.1.0' + config_overrides_defaults: + description: + - If C(yes), connection parameters from I(config_file) will override the default + values of I(login_host) and I(login_port) parameters. + - Used when I(stat) is C(present) or C(absent), ignored otherwise. + - It needs Python 3.5+ as the default interpreter on a target host. + type: bool + default: no + version_added: '0.1.0' + +seealso: +- module: community.mysql.mysql_info +- module: community.mysql.mysql_variables +- module: community.mysql.mysql_user +- module: community.mysql.mysql_replication +- name: MySQL command-line client reference + description: Complete reference of the MySQL command-line client documentation. + link: https://dev.mysql.com/doc/refman/8.0/en/mysql.html +- name: mysqldump reference + description: Complete reference of the ``mysqldump`` client utility documentation. + link: https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html +- name: CREATE DATABASE reference + description: Complete reference of the CREATE DATABASE command documentation. + link: https://dev.mysql.com/doc/refman/8.0/en/create-database.html +- name: DROP DATABASE reference + description: Complete reference of the DROP DATABASE command documentation. + link: https://dev.mysql.com/doc/refman/8.0/en/drop-database.html +author: "Ansible Core Team" +requirements: + - mysql (command line binary) + - mysqldump (command line binary) +notes: + - Supports C(check_mode). + - Requires the mysql and mysqldump binaries on the remote host. + - This module is B(not idempotent) when I(state) is C(import), + and will import the dump file each time if run more than once. +extends_documentation_fragment: +- community.mysql.mysql + +''' + +EXAMPLES = r''' +- name: Create a new database with name 'bobdata' + community.mysql.mysql_db: + name: bobdata + state: present + +- name: Create new databases with names 'foo' and 'bar' + community.mysql.mysql_db: + name: + - foo + - bar + state: present + +# Copy database dump file to remote host and restore it to database 'my_db' +- name: Copy database dump file + copy: + src: dump.sql.bz2 + dest: /tmp + +- name: Restore database + community.mysql.mysql_db: + name: my_db + state: import + target: /tmp/dump.sql.bz2 + +- name: Restore database ignoring errors + community.mysql.mysql_db: + name: my_db + state: import + target: /tmp/dump.sql.bz2 + force: yes + +- name: Dump multiple databases + community.mysql.mysql_db: + state: dump + name: db_1,db_2 + target: /tmp/dump.sql + +- name: Dump multiple databases + community.mysql.mysql_db: + state: dump + name: + - db_1 + - db_2 + target: /tmp/dump.sql + +- name: Dump all databases to hostname.sql + community.mysql.mysql_db: + state: dump + name: all + target: /tmp/dump.sql + +- name: Dump all databases to hostname.sql including master data + community.mysql.mysql_db: + state: dump + name: all + target: /tmp/dump.sql + master_data: 1 + +# Import of sql script with encoding option +- name: > + Import dump.sql with specific latin1 encoding, + similar to mysql -u <username> --default-character-set=latin1 -p <password> < dump.sql + community.mysql.mysql_db: + state: import + name: all + encoding: latin1 + target: /tmp/dump.sql + +# Dump of database with encoding option +- name: > + Dump of Databse with specific latin1 encoding, + similar to mysqldump -u <username> --default-character-set=latin1 -p <password> <database> + community.mysql.mysql_db: + state: dump + name: db_1 + encoding: latin1 + target: /tmp/dump.sql + +- name: Delete database with name 'bobdata' + community.mysql.mysql_db: + name: bobdata + state: absent + +- name: Make sure there is neither a database with name 'foo', nor one with name 'bar' + community.mysql.mysql_db: + name: + - foo + - bar + state: absent + +# Dump database with argument not directly supported by this module +# using dump_extra_args parameter +- name: Dump databases without including triggers + community.mysql.mysql_db: + state: dump + name: foo + target: /tmp/dump.sql + dump_extra_args: --skip-triggers + +- name: Try to create database as root/nopassword first. If not allowed, pass the credentials + community.mysql.mysql_db: + check_implicit_admin: yes + login_user: bob + login_password: 123456 + name: bobdata + state: present +''' + +RETURN = r''' +db: + description: Database names in string format delimited by white space. + returned: always + type: str + sample: "foo bar" +db_list: + description: List of database names. + returned: always + type: list + sample: ["foo", "bar"] +executed_commands: + description: List of commands which tried to run. + returned: if executed + type: list + sample: ["CREATE DATABASE acme"] + version_added: '0.1.0' +''' + +import os +import subprocess +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.mysql.plugins.module_utils.database import mysql_quote_identifier +from ansible_collections.community.mysql.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg, mysql_common_argument_spec +from ansible.module_utils.six.moves import shlex_quote +from ansible.module_utils._text import to_native + +executed_commands = [] + +# =========================================== +# MySQL module specific support methods. +# + + +def db_exists(cursor, db): + res = 0 + for each_db in db: + res += cursor.execute("SHOW DATABASES LIKE %s", (each_db.replace("_", r"\_"),)) + return res == len(db) + + +def db_delete(cursor, db): + if not db: + return False + for each_db in db: + query = "DROP DATABASE %s" % mysql_quote_identifier(each_db, 'database') + executed_commands.append(query) + cursor.execute(query) + return True + + +def db_dump(module, host, user, password, db_name, target, all_databases, port, + config_file, socket=None, ssl_cert=None, ssl_key=None, ssl_ca=None, + single_transaction=None, quick=None, ignore_tables=None, hex_blob=None, + encoding=None, force=False, master_data=0, skip_lock_tables=False, + dump_extra_args=None, unsafe_password=False, restrict_config_file=False, + check_implicit_admin=False): + cmd = module.get_bin_path('mysqldump', True) + # If defined, mysqldump demands --defaults-extra-file be the first option + if config_file: + if restrict_config_file: + cmd += " --defaults-file=%s" % shlex_quote(config_file) + else: + cmd += " --defaults-extra-file=%s" % shlex_quote(config_file) + + if check_implicit_admin: + cmd += " --user=root --password=''" + else: + if user is not None: + cmd += " --user=%s" % shlex_quote(user) + + if password is not None: + if not unsafe_password: + cmd += " --password=%s" % shlex_quote(password) + else: + cmd += " --password=%s" % password + + if ssl_cert is not None: + cmd += " --ssl-cert=%s" % shlex_quote(ssl_cert) + if ssl_key is not None: + cmd += " --ssl-key=%s" % shlex_quote(ssl_key) + if ssl_ca is not None: + cmd += " --ssl-ca=%s" % shlex_quote(ssl_ca) + if force: + cmd += " --force" + if socket is not None: + cmd += " --socket=%s" % shlex_quote(socket) + else: + cmd += " --host=%s --port=%i" % (shlex_quote(host), port) + + if all_databases: + cmd += " --all-databases" + elif len(db_name) > 1: + cmd += " --databases {0}".format(' '.join(db_name)) + else: + cmd += " %s" % shlex_quote(' '.join(db_name)) + + if skip_lock_tables: + cmd += " --skip-lock-tables" + if (encoding is not None) and (encoding != ""): + cmd += " --default-character-set=%s" % shlex_quote(encoding) + if single_transaction: + cmd += " --single-transaction=true" + if quick: + cmd += " --quick" + if ignore_tables: + for an_ignored_table in ignore_tables: + cmd += " --ignore-table={0}".format(an_ignored_table) + if hex_blob: + cmd += " --hex-blob" + if master_data: + cmd += " --master-data=%s" % master_data + if dump_extra_args is not None: + cmd += " " + dump_extra_args + + path = None + if os.path.splitext(target)[-1] == '.gz': + path = module.get_bin_path('gzip', True) + elif os.path.splitext(target)[-1] == '.bz2': + path = module.get_bin_path('bzip2', True) + elif os.path.splitext(target)[-1] == '.xz': + path = module.get_bin_path('xz', True) + + if path: + cmd = '%s | %s > %s' % (cmd, path, shlex_quote(target)) + else: + cmd += " > %s" % shlex_quote(target) + + executed_commands.append(cmd) + rc, stdout, stderr = module.run_command(cmd, use_unsafe_shell=True) + return rc, stdout, stderr + + +def db_import(module, host, user, password, db_name, target, all_databases, port, config_file, + socket=None, ssl_cert=None, ssl_key=None, ssl_ca=None, encoding=None, force=False, + use_shell=False, unsafe_password=False, restrict_config_file=False, + check_implicit_admin=False): + if not os.path.exists(target): + return module.fail_json(msg="target %s does not exist on the host" % target) + + cmd = [module.get_bin_path('mysql', True)] + # --defaults-file must go first, or errors out + if config_file: + if restrict_config_file: + cmd.append("--defaults-file=%s" % shlex_quote(config_file)) + else: + cmd.append("--defaults-extra-file=%s" % shlex_quote(config_file)) + + if check_implicit_admin: + cmd += " --user=root --password=''" + else: + if user: + cmd.append("--user=%s" % shlex_quote(user)) + + if password: + if not unsafe_password: + cmd.append("--password=%s" % shlex_quote(password)) + else: + cmd.append("--password=%s" % password) + + if ssl_cert is not None: + cmd.append("--ssl-cert=%s" % shlex_quote(ssl_cert)) + if ssl_key is not None: + cmd.append("--ssl-key=%s" % shlex_quote(ssl_key)) + if ssl_ca is not None: + cmd.append("--ssl-ca=%s" % shlex_quote(ssl_ca)) + if force: + cmd.append("-f") + if socket is not None: + cmd.append("--socket=%s" % shlex_quote(socket)) + else: + cmd.append("--host=%s" % shlex_quote(host)) + cmd.append("--port=%i" % port) + if (encoding is not None) and (encoding != ""): + cmd.append("--default-character-set=%s" % shlex_quote(encoding)) + if not all_databases: + cmd.append("--one-database") + cmd.append(shlex_quote(''.join(db_name))) + + comp_prog_path = None + if os.path.splitext(target)[-1] == '.gz': + comp_prog_path = module.get_bin_path('gzip', required=True) + elif os.path.splitext(target)[-1] == '.bz2': + comp_prog_path = module.get_bin_path('bzip2', required=True) + elif os.path.splitext(target)[-1] == '.xz': + comp_prog_path = module.get_bin_path('xz', required=True) + if comp_prog_path: + # The line below is for returned data only: + executed_commands.append('%s -dc %s | %s' % (comp_prog_path, target, cmd)) + + if not use_shell: + p1 = subprocess.Popen([comp_prog_path, '-dc', target], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p2 = subprocess.Popen(cmd, stdin=p1.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdout2, stderr2) = p2.communicate() + p1.stdout.close() + p1.wait() + + if p1.returncode != 0: + stderr1 = p1.stderr.read() + return p1.returncode, '', stderr1 + else: + return p2.returncode, stdout2, stderr2 + else: + # Used to prevent 'Broken pipe' errors that + # occasionaly occur when target files are compressed. + # FYI: passing the `shell=True` argument to p2 = subprocess.Popen() + # doesn't solve the problem. + cmd = " ".join(cmd) + cmd = "%s -dc %s | %s" % (comp_prog_path, shlex_quote(target), cmd) + rc, stdout, stderr = module.run_command(cmd, use_unsafe_shell=True) + return rc, stdout, stderr + + else: + cmd = ' '.join(cmd) + cmd += " < %s" % shlex_quote(target) + executed_commands.append(cmd) + rc, stdout, stderr = module.run_command(cmd, use_unsafe_shell=True) + return rc, stdout, stderr + + +def db_create(cursor, db, encoding, collation): + if not db: + return False + query_params = dict(enc=encoding, collate=collation) + res = 0 + for each_db in db: + query = ['CREATE DATABASE %s' % mysql_quote_identifier(each_db, 'database')] + if encoding: + query.append("CHARACTER SET %(enc)s") + if collation: + query.append("COLLATE %(collate)s") + query = ' '.join(query) + res += cursor.execute(query, query_params) + try: + executed_commands.append(cursor.mogrify(query, query_params)) + except AttributeError: + executed_commands.append(cursor._executed) + except Exception: + executed_commands.append(query) + return res > 0 + + +# =========================================== +# Module execution. +# + + +def main(): + argument_spec = mysql_common_argument_spec() + argument_spec.update( + name=dict(type='list', required=True, aliases=['db']), + encoding=dict(type='str', default=''), + collation=dict(type='str', default=''), + target=dict(type='path'), + state=dict(type='str', default='present', choices=['absent', 'dump', 'import', 'present']), + single_transaction=dict(type='bool', default=False), + quick=dict(type='bool', default=True), + ignore_tables=dict(type='list', default=[]), + hex_blob=dict(default=False, type='bool'), + force=dict(type='bool', default=False), + master_data=dict(type='int', default=0, choices=[0, 1, 2]), + skip_lock_tables=dict(type='bool', default=False), + dump_extra_args=dict(type='str'), + use_shell=dict(type='bool', default=False), + unsafe_login_password=dict(type='bool', default=False, no_log=True), + restrict_config_file=dict(type='bool', default=False), + check_implicit_admin=dict(type='bool', default=False), + config_overrides_defaults=dict(type='bool', default=False), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + if mysql_driver is None: + module.fail_json(msg=mysql_driver_fail_msg) + + db = module.params["name"] + if not db: + module.exit_json(changed=False, db=db, db_list=[]) + db = [each_db.strip() for each_db in db] + + encoding = module.params["encoding"] + collation = module.params["collation"] + state = module.params["state"] + target = module.params["target"] + socket = module.params["login_unix_socket"] + login_port = module.params["login_port"] + if login_port < 0 or login_port > 65535: + module.fail_json(msg="login_port must be a valid unix port number (0-65535)") + ssl_cert = module.params["client_cert"] + ssl_key = module.params["client_key"] + ssl_ca = module.params["ca_cert"] + check_hostname = module.params["check_hostname"] + connect_timeout = module.params['connect_timeout'] + config_file = module.params['config_file'] + login_password = module.params["login_password"] + unsafe_login_password = module.params["unsafe_login_password"] + login_user = module.params["login_user"] + login_host = module.params["login_host"] + ignore_tables = module.params["ignore_tables"] + for a_table in ignore_tables: + if a_table == "": + module.fail_json(msg="Name of ignored table cannot be empty") + single_transaction = module.params["single_transaction"] + quick = module.params["quick"] + hex_blob = module.params["hex_blob"] + force = module.params["force"] + master_data = module.params["master_data"] + skip_lock_tables = module.params["skip_lock_tables"] + dump_extra_args = module.params["dump_extra_args"] + use_shell = module.params["use_shell"] + restrict_config_file = module.params["restrict_config_file"] + check_implicit_admin = module.params['check_implicit_admin'] + config_overrides_defaults = module.params['config_overrides_defaults'] + + if len(db) > 1 and state == 'import': + module.fail_json(msg="Multiple databases are not supported with state=import") + db_name = ' '.join(db) + + all_databases = False + if state in ['dump', 'import']: + if target is None: + module.fail_json(msg="with state=%s target is required" % state) + if db == ['all']: + all_databases = True + else: + if db == ['all']: + module.fail_json(msg="name is not allowed to equal 'all' unless state equals import, or dump.") + try: + cursor = None + if check_implicit_admin: + try: + cursor, db_conn = mysql_connect(module, 'root', '', config_file, ssl_cert, ssl_key, ssl_ca, + connect_timeout=connect_timeout, check_hostname=check_hostname, + config_overrides_defaults=config_overrides_defaults) + except Exception as e: + check_implicit_admin = False + pass + + if not cursor: + cursor, db_conn = mysql_connect(module, login_user, login_password, config_file, ssl_cert, ssl_key, ssl_ca, + connect_timeout=connect_timeout, config_overrides_defaults=config_overrides_defaults, + check_hostname=check_hostname) + except Exception as e: + if os.path.exists(config_file): + module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or %s has the credentials. " + "Exception message: %s" % (config_file, to_native(e))) + else: + module.fail_json(msg="unable to find %s. Exception message: %s" % (config_file, to_native(e))) + + changed = False + if not os.path.exists(config_file): + config_file = None + + existence_list = [] + non_existence_list = [] + + if not all_databases: + for each_database in db: + if db_exists(cursor, [each_database]): + existence_list.append(each_database) + else: + non_existence_list.append(each_database) + + if state == "absent": + if module.check_mode: + module.exit_json(changed=bool(existence_list), db=db_name, db_list=db) + try: + changed = db_delete(cursor, existence_list) + except Exception as e: + module.fail_json(msg="error deleting database: %s" % to_native(e)) + module.exit_json(changed=changed, db=db_name, db_list=db, executed_commands=executed_commands) + elif state == "present": + if module.check_mode: + module.exit_json(changed=bool(non_existence_list), db=db_name, db_list=db) + changed = False + if non_existence_list: + try: + changed = db_create(cursor, non_existence_list, encoding, collation) + except Exception as e: + module.fail_json(msg="error creating database: %s" % to_native(e), + exception=traceback.format_exc()) + module.exit_json(changed=changed, db=db_name, db_list=db, executed_commands=executed_commands) + elif state == "dump": + if non_existence_list and not all_databases: + module.fail_json(msg="Cannot dump database(s) %r - not found" % (', '.join(non_existence_list))) + if module.check_mode: + module.exit_json(changed=True, db=db_name, db_list=db) + rc, stdout, stderr = db_dump(module, login_host, login_user, + login_password, db, target, all_databases, + login_port, config_file, socket, ssl_cert, ssl_key, + ssl_ca, single_transaction, quick, ignore_tables, + hex_blob, encoding, force, master_data, skip_lock_tables, + dump_extra_args, unsafe_login_password, restrict_config_file, + check_implicit_admin) + if rc != 0: + module.fail_json(msg="%s" % stderr) + module.exit_json(changed=True, db=db_name, db_list=db, msg=stdout, + executed_commands=executed_commands) + elif state == "import": + if module.check_mode: + module.exit_json(changed=True, db=db_name, db_list=db) + if non_existence_list and not all_databases: + try: + db_create(cursor, non_existence_list, encoding, collation) + except Exception as e: + module.fail_json(msg="error creating database: %s" % to_native(e), + exception=traceback.format_exc()) + rc, stdout, stderr = db_import(module, login_host, login_user, + login_password, db, target, + all_databases, + login_port, config_file, + socket, ssl_cert, ssl_key, ssl_ca, + encoding, force, use_shell, unsafe_login_password, + restrict_config_file, check_implicit_admin) + if rc != 0: + module.fail_json(msg="%s" % stderr) + module.exit_json(changed=True, db=db_name, db_list=db, msg=stdout, + executed_commands=executed_commands) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/mysql/plugins/modules/mysql_info.py b/collections-debian-merged/ansible_collections/community/mysql/plugins/modules/mysql_info.py new file mode 100644 index 00000000..c7d9e89b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/plugins/modules/mysql_info.py @@ -0,0 +1,545 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: mysql_info +short_description: Gather information about MySQL servers +description: +- Gathers information about MySQL servers. + +options: + filter: + description: + - Limit the collected information by comma separated string or YAML list. + - Allowable values are C(version), C(databases), C(settings), C(global_status), + C(users), C(engines), C(master_status), C(slave_status), C(slave_hosts). + - By default, collects all subsets. + - You can use '!' before value (for example, C(!settings)) to exclude it from the information. + - If you pass including and excluding values to the filter, for example, I(filter=!settings,version), + the excluding values, C(!settings) in this case, will be ignored. + type: list + elements: str + login_db: + description: + - Database name to connect to. + - It makes sense if I(login_user) is allowed to connect to a specific database only. + type: str + exclude_fields: + description: + - List of fields which are not needed to collect. + - "Supports elements: C(db_size). Unsupported elements will be ignored." + type: list + elements: str + version_added: '0.1.0' + return_empty_dbs: + description: + - Includes names of empty databases to returned dictionary. + type: bool + default: no + +notes: +- Calculating the size of a database might be slow, depending on the number and size of tables in it. + To avoid this, use I(exclude_fields=db_size). +- Supports C(check_mode). + +seealso: +- module: community.mysql.mysql_variables +- module: community.mysql.mysql_db +- module: community.mysql.mysql_user +- module: community.mysql.mysql_replication + +author: +- Andrew Klychkov (@Andersson007) + +extends_documentation_fragment: +- community.mysql.mysql +''' + +EXAMPLES = r''' +# Display info from mysql-hosts group (using creds from ~/.my.cnf to connect): +# ansible mysql-hosts -m mysql_info + +# Display only databases and users info: +# ansible mysql-hosts -m mysql_info -a 'filter=databases,users' + +# Display only slave status: +# ansible standby -m mysql_info -a 'filter=slave_status' + +# Display all info from databases group except settings: +# ansible databases -m mysql_info -a 'filter=!settings' + +- name: Collect all possible information using passwordless root access + community.mysql.mysql_info: + login_user: root + +- name: Get MySQL version with non-default credentials + community.mysql.mysql_info: + login_user: mysuperuser + login_password: mysuperpass + filter: version + +- name: Collect all info except settings and users by root + community.mysql.mysql_info: + login_user: root + login_password: rootpass + filter: "!settings,!users" + +- name: Collect info about databases and version using ~/.my.cnf as a credential file + become: yes + community.mysql.mysql_info: + filter: + - databases + - version + +- name: Collect info about databases and version using ~alice/.my.cnf as a credential file + become: yes + community.mysql.mysql_info: + config_file: /home/alice/.my.cnf + filter: + - databases + - version + +- name: Collect info about databases including empty and excluding their sizes + become: yes + community.mysql.mysql_info: + config_file: /home/alice/.my.cnf + filter: + - databases + exclude_fields: db_size + return_empty_dbs: yes +''' + +RETURN = r''' +version: + description: Database server version. + returned: if not excluded by filter + type: dict + sample: { "version": { "major": 5, "minor": 5, "release": 60 } } + contains: + major: + description: Major server version. + returned: if not excluded by filter + type: int + sample: 5 + minor: + description: Minor server version. + returned: if not excluded by filter + type: int + sample: 5 + release: + description: Release server version. + returned: if not excluded by filter + type: int + sample: 60 +databases: + description: Information about databases. + returned: if not excluded by filter + type: dict + sample: + - { "mysql": { "size": 656594 }, "information_schema": { "size": 73728 } } + contains: + size: + description: Database size in bytes. + returned: if not excluded by filter + type: dict + sample: { 'size': 656594 } +settings: + description: Global settings (variables) information. + returned: if not excluded by filter + type: dict + sample: + - { "innodb_open_files": 300, innodb_page_size": 16384 } +global_status: + description: Global status information. + returned: if not excluded by filter + type: dict + sample: + - { "Innodb_buffer_pool_read_requests": 123, "Innodb_buffer_pool_reads": 32 } +users: + description: Users information. + returned: if not excluded by filter + type: dict + sample: + - { "localhost": { "root": { "Alter_priv": "Y", "Alter_routine_priv": "Y" } } } +engines: + description: Information about the server's storage engines. + returned: if not excluded by filter + type: dict + sample: + - { "CSV": { "Comment": "CSV storage engine", "Savepoints": "NO", "Support": "YES", "Transactions": "NO", "XA": "NO" } } +master_status: + description: Master status information. + returned: if master + type: dict + sample: + - { "Binlog_Do_DB": "", "Binlog_Ignore_DB": "mysql", "File": "mysql-bin.000001", "Position": 769 } +slave_status: + description: Slave status information. + returned: if standby + type: dict + sample: + - { "192.168.1.101": { "3306": { "replication_user": { "Connect_Retry": 60, "Exec_Master_Log_Pos": 769, "Last_Errno": 0 } } } } +slave_hosts: + description: Slave status information. + returned: if master + type: dict + sample: + - { "2": { "Host": "", "Master_id": 1, "Port": 3306 } } +''' + +from decimal import Decimal + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.mysql.plugins.module_utils.mysql import ( + mysql_connect, + mysql_common_argument_spec, + mysql_driver, + mysql_driver_fail_msg, +) +from ansible.module_utils.six import iteritems +from ansible.module_utils._text import to_native + + +# =========================================== +# MySQL module specific support methods. +# + +class MySQL_Info(object): + + """Class for collection MySQL instance information. + + Arguments: + module (AnsibleModule): Object of AnsibleModule class. + cursor (pymysql/mysql-python): Cursor class for interaction with + the database. + + Note: + If you need to add a new subset: + 1. add a new key with the same name to self.info attr in self.__init__() + 2. add a new private method to get the information + 3. add invocation of the new method to self.__collect() + 4. add info about the new subset to the DOCUMENTATION block + 5. add info about the new subset with an example to RETURN block + """ + + def __init__(self, module, cursor): + self.module = module + self.cursor = cursor + self.info = { + 'version': {}, + 'databases': {}, + 'settings': {}, + 'global_status': {}, + 'engines': {}, + 'users': {}, + 'master_status': {}, + 'slave_hosts': {}, + 'slave_status': {}, + } + + def get_info(self, filter_, exclude_fields, return_empty_dbs): + """Get MySQL instance information based on filter_. + + Arguments: + filter_ (list): List of collected subsets (e.g., databases, users, etc.), + when it is empty, return all available information. + """ + + inc_list = [] + exc_list = [] + + if filter_: + partial_info = {} + + for fi in filter_: + if fi.lstrip('!') not in self.info: + self.module.warn('filter element: %s is not allowable, ignored' % fi) + continue + + if fi[0] == '!': + exc_list.append(fi.lstrip('!')) + + else: + inc_list.append(fi) + + if inc_list: + self.__collect(exclude_fields, return_empty_dbs, set(inc_list)) + + for i in self.info: + if i in inc_list: + partial_info[i] = self.info[i] + + else: + not_in_exc_list = list(set(self.info) - set(exc_list)) + self.__collect(exclude_fields, return_empty_dbs, set(not_in_exc_list)) + + for i in self.info: + if i not in exc_list: + partial_info[i] = self.info[i] + + return partial_info + + else: + self.__collect(exclude_fields, return_empty_dbs, set(self.info)) + return self.info + + def __collect(self, exclude_fields, return_empty_dbs, wanted): + """Collect all possible subsets.""" + if 'version' in wanted or 'settings' in wanted: + self.__get_global_variables() + + if 'databases' in wanted: + self.__get_databases(exclude_fields, return_empty_dbs) + + if 'global_status' in wanted: + self.__get_global_status() + + if 'engines' in wanted: + self.__get_engines() + + if 'users' in wanted: + self.__get_users() + + if 'master_status' in wanted: + self.__get_master_status() + + if 'slave_status' in wanted: + self.__get_slave_status() + + if 'slave_hosts' in wanted: + self.__get_slaves() + + def __get_engines(self): + """Get storage engines info.""" + res = self.__exec_sql('SHOW ENGINES') + + if res: + for line in res: + engine = line['Engine'] + self.info['engines'][engine] = {} + + for vname, val in iteritems(line): + if vname != 'Engine': + self.info['engines'][engine][vname] = val + + def __convert(self, val): + """Convert unserializable data.""" + try: + if isinstance(val, Decimal): + val = float(val) + else: + val = int(val) + + except ValueError: + pass + + except TypeError: + pass + + return val + + def __get_global_variables(self): + """Get global variables (instance settings).""" + res = self.__exec_sql('SHOW GLOBAL VARIABLES') + + if res: + for var in res: + self.info['settings'][var['Variable_name']] = self.__convert(var['Value']) + + ver = self.info['settings']['version'].split('.') + release = ver[2].split('-')[0] + + self.info['version'] = dict( + major=int(ver[0]), + minor=int(ver[1]), + release=int(release), + ) + + def __get_global_status(self): + """Get global status.""" + res = self.__exec_sql('SHOW GLOBAL STATUS') + + if res: + for var in res: + self.info['global_status'][var['Variable_name']] = self.__convert(var['Value']) + + def __get_master_status(self): + """Get master status if the instance is a master.""" + res = self.__exec_sql('SHOW MASTER STATUS') + if res: + for line in res: + for vname, val in iteritems(line): + self.info['master_status'][vname] = self.__convert(val) + + def __get_slave_status(self): + """Get slave status if the instance is a slave.""" + res = self.__exec_sql('SHOW SLAVE STATUS') + if res: + for line in res: + host = line['Master_Host'] + if host not in self.info['slave_status']: + self.info['slave_status'][host] = {} + + port = line['Master_Port'] + if port not in self.info['slave_status'][host]: + self.info['slave_status'][host][port] = {} + + user = line['Master_User'] + if user not in self.info['slave_status'][host][port]: + self.info['slave_status'][host][port][user] = {} + + for vname, val in iteritems(line): + if vname not in ('Master_Host', 'Master_Port', 'Master_User'): + self.info['slave_status'][host][port][user][vname] = self.__convert(val) + + def __get_slaves(self): + """Get slave hosts info if the instance is a master.""" + res = self.__exec_sql('SHOW SLAVE HOSTS') + if res: + for line in res: + srv_id = line['Server_id'] + if srv_id not in self.info['slave_hosts']: + self.info['slave_hosts'][srv_id] = {} + + for vname, val in iteritems(line): + if vname != 'Server_id': + self.info['slave_hosts'][srv_id][vname] = self.__convert(val) + + def __get_users(self): + """Get user info.""" + res = self.__exec_sql('SELECT * FROM mysql.user') + if res: + for line in res: + host = line['Host'] + if host not in self.info['users']: + self.info['users'][host] = {} + + user = line['User'] + self.info['users'][host][user] = {} + + for vname, val in iteritems(line): + if vname not in ('Host', 'User'): + self.info['users'][host][user][vname] = self.__convert(val) + + def __get_databases(self, exclude_fields, return_empty_dbs): + """Get info about databases.""" + if not exclude_fields: + query = ('SELECT table_schema AS "name", ' + 'SUM(data_length + index_length) AS "size" ' + 'FROM information_schema.TABLES GROUP BY table_schema') + else: + if 'db_size' in exclude_fields: + query = ('SELECT table_schema AS "name" ' + 'FROM information_schema.TABLES GROUP BY table_schema') + + res = self.__exec_sql(query) + + if res: + for db in res: + self.info['databases'][db['name']] = {} + + if not exclude_fields or 'db_size' not in exclude_fields: + self.info['databases'][db['name']]['size'] = int(db['size']) + + # If empty dbs are not needed in the returned dict, exit from the method + if not return_empty_dbs: + return None + + # Add info about empty databases (issue #65727): + res = self.__exec_sql('SHOW DATABASES') + if res: + for db in res: + if db['Database'] not in self.info['databases']: + self.info['databases'][db['Database']] = {} + + if not exclude_fields or 'db_size' not in exclude_fields: + self.info['databases'][db['Database']]['size'] = 0 + + def __exec_sql(self, query, ddl=False): + """Execute SQL. + + Arguments: + ddl (bool): If True, return True or False. + Used for queries that don't return any rows + (mainly for DDL queries) (default False). + """ + try: + self.cursor.execute(query) + + if not ddl: + res = self.cursor.fetchall() + return res + return True + + except Exception as e: + self.module.fail_json(msg="Cannot execute SQL '%s': %s" % (query, to_native(e))) + return False + + +# =========================================== +# Module execution. +# + + +def main(): + argument_spec = mysql_common_argument_spec() + argument_spec.update( + login_db=dict(type='str'), + filter=dict(type='list'), + exclude_fields=dict(type='list'), + return_empty_dbs=dict(type='bool', default=False), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + db = module.params['login_db'] + connect_timeout = module.params['connect_timeout'] + login_user = module.params['login_user'] + login_password = module.params['login_password'] + ssl_cert = module.params['client_cert'] + ssl_key = module.params['client_key'] + ssl_ca = module.params['ca_cert'] + check_hostname = module.params['check_hostname'] + config_file = module.params['config_file'] + filter_ = module.params['filter'] + exclude_fields = module.params['exclude_fields'] + return_empty_dbs = module.params['return_empty_dbs'] + + if filter_: + filter_ = [f.strip() for f in filter_] + + if exclude_fields: + exclude_fields = set([f.strip() for f in exclude_fields]) + + if mysql_driver is None: + module.fail_json(msg=mysql_driver_fail_msg) + + try: + cursor, db_conn = mysql_connect(module, login_user, login_password, + config_file, ssl_cert, ssl_key, ssl_ca, db, + check_hostname=check_hostname, + connect_timeout=connect_timeout, cursor_class='DictCursor') + except Exception as e: + module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or %s has the credentials. " + "Exception message: %s" % (config_file, to_native(e))) + + ############################### + # Create object and do main job + + mysql = MySQL_Info(module, cursor) + + module.exit_json(changed=False, **mysql.get_info(filter_, exclude_fields, return_empty_dbs)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/mysql/plugins/modules/mysql_query.py b/collections-debian-merged/ansible_collections/community/mysql/plugins/modules/mysql_query.py new file mode 100644 index 00000000..ed3ace4a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/plugins/modules/mysql_query.py @@ -0,0 +1,253 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: mysql_query +short_description: Run MySQL queries +description: +- Runs arbitrary MySQL queries. +- Pay attention, the module does not support check mode! + All queries will be executed in autocommit mode. +- To run SQL queries from a file, use M(community.mysql.mysql_db) module. +version_added: '0.1.0' +options: + query: + description: + - SQL query to run. Multiple queries can be passed using YAML list syntax. + - Must be a string or YAML list containing strings. + type: raw + required: yes + positional_args: + description: + - List of values to be passed as positional arguments to the query. + - Mutually exclusive with I(named_args). + type: list + named_args: + description: + - Dictionary of key-value arguments to pass to the query. + - Mutually exclusive with I(positional_args). + type: dict + login_db: + description: + - Name of database to connect to and run queries against. + type: str + single_transaction: + description: + - Where passed queries run in a single transaction (C(yes)) or commit them one-by-one (C(no)). + type: bool + default: no +seealso: +- module: community.mysql.mysql_db +author: +- Andrew Klychkov (@Andersson007) +extends_documentation_fragment: +- community.mysql.mysql + +''' + +EXAMPLES = r''' +- name: Simple select query to acme db + community.mysql.mysql_query: + login_db: acme + query: SELECT * FROM orders + +- name: Select query to db acme with positional arguments + community.mysql.mysql_query: + login_db: acme + query: SELECT * FROM acme WHERE id = %s AND story = %s + positional_args: + - 1 + - test + +- name: Select query to test_db with named_args + community.mysql.mysql_query: + login_db: test_db + query: SELECT * FROM test WHERE id = %(id_val)s AND story = %(story_val)s + named_args: + id_val: 1 + story_val: test + +- name: Run several insert queries against db test_db in single transaction + community.mysql.mysql_query: + login_db: test_db + query: + - INSERT INTO articles (id, story) VALUES (2, 'my_long_story') + - INSERT INTO prices (id, price) VALUES (123, '100.00') + single_transaction: yes +''' + +RETURN = r''' +executed_queries: + description: List of executed queries. + returned: always + type: list + sample: ['SELECT * FROM bar', 'UPDATE bar SET id = 1 WHERE id = 2'] +query_result: + description: + - List of lists (sublist for each query) containing dictionaries + in column:value form representing returned rows. + returned: changed + type: list + sample: [[{"Column": "Value1"},{"Column": "Value2"}], [{"ID": 1}, {"ID": 2}]] +rowcount: + description: Number of affected rows for each subquery. + returned: changed + type: list + sample: [5, 1] +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.mysql.plugins.module_utils.mysql import ( + mysql_connect, + mysql_common_argument_spec, + mysql_driver, + mysql_driver_fail_msg, +) +from ansible.module_utils._text import to_native + +DML_QUERY_KEYWORDS = ('INSERT', 'UPDATE', 'DELETE') +# TRUNCATE is not DDL query but it also returns 0 rows affected: +DDL_QUERY_KEYWORDS = ('CREATE', 'DROP', 'ALTER', 'RENAME', 'TRUNCATE') + + +# =========================================== +# Module execution. +# + +def main(): + argument_spec = mysql_common_argument_spec() + argument_spec.update( + query=dict(type='raw', required=True), + login_db=dict(type='str'), + positional_args=dict(type='list'), + named_args=dict(type='dict'), + single_transaction=dict(type='bool', default=False), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=( + ('positional_args', 'named_args'), + ), + ) + + db = module.params['login_db'] + connect_timeout = module.params['connect_timeout'] + login_user = module.params['login_user'] + login_password = module.params['login_password'] + ssl_cert = module.params['client_cert'] + ssl_key = module.params['client_key'] + ssl_ca = module.params['ca_cert'] + check_hostname = module.params['check_hostname'] + config_file = module.params['config_file'] + query = module.params["query"] + + if not isinstance(query, (str, list)): + module.fail_json(msg="the query option value must be a string or list, passed %s" % type(query)) + + if isinstance(query, str): + query = [query] + + for elem in query: + if not isinstance(elem, str): + module.fail_json(msg="the elements in query list must be strings, passed '%s' %s" % (elem, type(elem))) + + if module.params["single_transaction"]: + autocommit = False + else: + autocommit = True + # Prepare args: + if module.params.get("positional_args"): + arguments = module.params["positional_args"] + elif module.params.get("named_args"): + arguments = module.params["named_args"] + else: + arguments = None + + if mysql_driver is None: + module.fail_json(msg=mysql_driver_fail_msg) + + # Connect to DB: + try: + cursor, db_connection = mysql_connect(module, login_user, login_password, + config_file, ssl_cert, ssl_key, ssl_ca, db, + check_hostname=check_hostname, + connect_timeout=connect_timeout, + cursor_class='DictCursor', autocommit=autocommit) + except Exception as e: + module.fail_json(msg="unable to connect to database, check login_user and " + "login_password are correct or %s has the credentials. " + "Exception message: %s" % (config_file, to_native(e))) + + # Set defaults: + changed = False + + max_keyword_len = len(max(DML_QUERY_KEYWORDS + DDL_QUERY_KEYWORDS, key=len)) + + # Execute query: + query_result = [] + executed_queries = [] + rowcount = [] + + for q in query: + try: + cursor.execute(q, arguments) + + except Exception as e: + if not autocommit: + db_connection.rollback() + + cursor.close() + module.fail_json(msg="Cannot execute SQL '%s' args [%s]: %s" % (q, arguments, to_native(e))) + + try: + query_result.append([dict(row) for row in cursor.fetchall()]) + + except Exception as e: + if not autocommit: + db_connection.rollback() + + module.fail_json(msg="Cannot fetch rows from cursor: %s" % to_native(e)) + + # Check DML or DDL keywords in query and set changed accordingly: + q = q.lstrip()[0:max_keyword_len].upper() + for keyword in DML_QUERY_KEYWORDS: + if keyword in q and cursor.rowcount > 0: + changed = True + + for keyword in DDL_QUERY_KEYWORDS: + if keyword in q: + changed = True + + try: + executed_queries.append(cursor._last_executed) + except AttributeError: + # MySQLdb removed cursor._last_executed as a duplicate of cursor._executed + executed_queries.append(cursor._executed) + rowcount.append(cursor.rowcount) + + # When the module run with the single_transaction == True: + if not autocommit: + db_connection.commit() + + # Create dict with returned values: + kw = { + 'changed': changed, + 'executed_queries': executed_queries, + 'query_result': query_result, + 'rowcount': rowcount, + } + + # Exit: + module.exit_json(**kw) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/mysql/plugins/modules/mysql_replication.py b/collections-debian-merged/ansible_collections/community/mysql/plugins/modules/mysql_replication.py new file mode 100644 index 00000000..322a6a8c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/plugins/modules/mysql_replication.py @@ -0,0 +1,594 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2013, Balazs Pocze <banyek@gawker.com> +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru> +# Certain parts are taken from Mark Theunissen's mysqldb module +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: mysql_replication +short_description: Manage MySQL replication +description: +- Manages MySQL server replication, slave, master status, get and change master host. +author: +- Balazs Pocze (@banyek) +- Andrew Klychkov (@Andersson007) +options: + mode: + description: + - Module operating mode. Could be + C(changemaster) (CHANGE MASTER TO), + C(getmaster) (SHOW MASTER STATUS), + C(getslave) (SHOW SLAVE STATUS), + C(startslave) (START SLAVE), + C(stopslave) (STOP SLAVE), + C(resetmaster) (RESET MASTER) - supported since community.mysql 0.1.0, + C(resetslave) (RESET SLAVE), + C(resetslaveall) (RESET SLAVE ALL). + type: str + choices: + - changemaster + - getmaster + - getslave + - startslave + - stopslave + - resetmaster + - resetslave + - resetslaveall + default: getslave + master_host: + description: + - Same as mysql variable. + type: str + master_user: + description: + - Same as mysql variable. + type: str + master_password: + description: + - Same as mysql variable. + type: str + master_port: + description: + - Same as mysql variable. + type: int + master_connect_retry: + description: + - Same as mysql variable. + type: int + master_log_file: + description: + - Same as mysql variable. + type: str + master_log_pos: + description: + - Same as mysql variable. + type: int + relay_log_file: + description: + - Same as mysql variable. + type: str + relay_log_pos: + description: + - Same as mysql variable. + type: int + master_ssl: + description: + - Same as mysql variable. + type: bool + default: false + master_ssl_ca: + description: + - Same as mysql variable. + type: str + master_ssl_capath: + description: + - Same as mysql variable. + type: str + master_ssl_cert: + description: + - Same as mysql variable. + type: str + master_ssl_key: + description: + - Same as mysql variable. + type: str + master_ssl_cipher: + description: + - Same as mysql variable. + type: str + master_auto_position: + description: + - Whether the host uses GTID based replication or not. + type: bool + default: false + master_use_gtid: + description: + - Configures the slave to use the MariaDB Global Transaction ID. + - C(disabled) equals MASTER_USE_GTID=no command. + - To find information about available values see + U(https://mariadb.com/kb/en/library/change-master-to/#master_use_gtid). + - Available since MariaDB 10.0.2. + choices: [current_pos, slave_pos, disabled] + type: str + version_added: '0.1.0' + master_delay: + description: + - Time lag behind the master's state (in seconds). + - Available from MySQL 5.6. + - For more information see U(https://dev.mysql.com/doc/refman/8.0/en/replication-delayed.html). + type: int + version_added: '0.1.0' + connection_name: + description: + - Name of the master connection. + - Supported from MariaDB 10.0.1. + - Mutually exclusive with I(channel). + - For more information see U(https://mariadb.com/kb/en/library/multi-source-replication/). + type: str + version_added: '0.1.0' + channel: + description: + - Name of replication channel. + - Multi-source replication is supported from MySQL 5.7. + - Mutually exclusive with I(connection_name). + - For more information see U(https://dev.mysql.com/doc/refman/8.0/en/replication-multi-source.html). + type: str + version_added: '0.1.0' + fail_on_error: + description: + - Fails on error when calling mysql. + type: bool + default: False + version_added: '0.1.0' + +notes: +- If an empty value for the parameter of string type is needed, use an empty string. + +extends_documentation_fragment: +- community.mysql.mysql + + +seealso: +- module: community.mysql.mysql_info +- name: MySQL replication reference + description: Complete reference of the MySQL replication documentation. + link: https://dev.mysql.com/doc/refman/8.0/en/replication.html +- name: MariaDB replication reference + description: Complete reference of the MariaDB replication documentation. + link: https://mariadb.com/kb/en/library/setting-up-replication/ +''' + +EXAMPLES = r''' +- name: Stop mysql slave thread + community.mysql.mysql_replication: + mode: stopslave + +- name: Get master binlog file name and binlog position + community.mysql.mysql_replication: + mode: getmaster + +- name: Change master to master server 192.0.2.1 and use binary log 'mysql-bin.000009' with position 4578 + community.mysql.mysql_replication: + mode: changemaster + master_host: 192.0.2.1 + master_log_file: mysql-bin.000009 + master_log_pos: 4578 + +- name: Check slave status using port 3308 + community.mysql.mysql_replication: + mode: getslave + login_host: ansible.example.com + login_port: 3308 + +- name: On MariaDB change master to use GTID current_pos + community.mysql.mysql_replication: + mode: changemaster + master_use_gtid: current_pos + +- name: Change master to use replication delay 3600 seconds + community.mysql.mysql_replication: + mode: changemaster + master_host: 192.0.2.1 + master_delay: 3600 + +- name: Start MariaDB standby with connection name master-1 + community.mysql.mysql_replication: + mode: startslave + connection_name: master-1 + +- name: Stop replication in channel master-1 + community.mysql.mysql_replication: + mode: stopslave + channel: master-1 + +- name: > + Run RESET MASTER command which will delete all existing binary log files + and reset the binary log index file on the master + community.mysql.mysql_replication: + mode: resetmaster + +- name: Run start slave and fail the task on errors + community.mysql.mysql_replication: + mode: startslave + connection_name: master-1 + fail_on_error: yes + +- name: Change master and fail on error (like when slave thread is running) + community.mysql.mysql_replication: + mode: changemaster + fail_on_error: yes + +''' + +RETURN = r''' +queries: + description: List of executed queries which modified DB's state. + returned: always + type: list + sample: ["CHANGE MASTER TO MASTER_HOST='master2.example.com',MASTER_PORT=3306"] + version_added: '0.1.0' +''' + +import os +import warnings + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.mysql.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg, mysql_common_argument_spec +from ansible.module_utils._text import to_native +from distutils.version import LooseVersion + +executed_queries = [] + + +def uses_replica_terminology(cursor): + """Checks if REPLICA must be used instead of SLAVE""" + cursor.execute("SELECT VERSION() AS version") + result = cursor.fetchone() + + if isinstance(result, dict): + version_str = result['version'] + else: + version_str = result[0] + + version = LooseVersion(version_str) + + if 'mariadb' in version_str.lower(): + return version >= LooseVersion('10.5.1') + else: + return version >= LooseVersion('8.0.22') + + +def get_master_status(cursor): + cursor.execute("SHOW MASTER STATUS") + masterstatus = cursor.fetchone() + return masterstatus + + +def get_slave_status(cursor, connection_name='', channel='', term='REPLICA'): + if connection_name: + query = "SHOW %s '%s' STATUS" % (term, connection_name) + else: + query = "SHOW %s STATUS" % term + + if channel: + query += " FOR CHANNEL '%s'" % channel + + cursor.execute(query) + slavestatus = cursor.fetchone() + return slavestatus + + +def stop_slave(module, cursor, connection_name='', channel='', fail_on_error=False, term='REPLICA'): + if connection_name: + query = "STOP %s '%s'" % (term, connection_name) + else: + query = 'STOP %s' % term + + if channel: + query += " FOR CHANNEL '%s'" % channel + + try: + executed_queries.append(query) + cursor.execute(query) + stopped = True + except mysql_driver.Warning as e: + stopped = False + except Exception as e: + if fail_on_error: + module.fail_json(msg="STOP SLAVE failed: %s" % to_native(e)) + stopped = False + return stopped + + +def reset_slave(module, cursor, connection_name='', channel='', fail_on_error=False, term='REPLICA'): + if connection_name: + query = "RESET %s '%s'" % (term, connection_name) + else: + query = 'RESET %s' % term + + if channel: + query += " FOR CHANNEL '%s'" % channel + + try: + executed_queries.append(query) + cursor.execute(query) + reset = True + except mysql_driver.Warning as e: + reset = False + except Exception as e: + if fail_on_error: + module.fail_json(msg="RESET SLAVE failed: %s" % to_native(e)) + reset = False + return reset + + +def reset_slave_all(module, cursor, connection_name='', channel='', fail_on_error=False, term='REPLICA'): + if connection_name: + query = "RESET %s '%s' ALL" % (term, connection_name) + else: + query = 'RESET %s ALL' % term + + if channel: + query += " FOR CHANNEL '%s'" % channel + + try: + executed_queries.append(query) + cursor.execute(query) + reset = True + except mysql_driver.Warning as e: + reset = False + except Exception as e: + if fail_on_error: + module.fail_json(msg="RESET SLAVE ALL failed: %s" % to_native(e)) + reset = False + return reset + + +def reset_master(module, cursor, fail_on_error=False): + query = 'RESET MASTER' + try: + executed_queries.append(query) + cursor.execute(query) + reset = True + except mysql_driver.Warning as e: + reset = False + except Exception as e: + if fail_on_error: + module.fail_json(msg="RESET MASTER failed: %s" % to_native(e)) + reset = False + return reset + + +def start_slave(module, cursor, connection_name='', channel='', fail_on_error=False, term='REPLICA'): + if connection_name: + query = "START %s '%s'" % (term, connection_name) + else: + query = 'START %s' % term + + if channel: + query += " FOR CHANNEL '%s'" % channel + + try: + executed_queries.append(query) + cursor.execute(query) + started = True + except mysql_driver.Warning as e: + started = False + except Exception as e: + if fail_on_error: + module.fail_json(msg="START SLAVE failed: %s" % to_native(e)) + started = False + return started + + +def changemaster(cursor, chm, connection_name='', channel=''): + if connection_name: + query = "CHANGE MASTER '%s' TO %s" % (connection_name, ','.join(chm)) + else: + query = 'CHANGE MASTER TO %s' % ','.join(chm) + + if channel: + query += " FOR CHANNEL '%s'" % channel + + executed_queries.append(query) + cursor.execute(query) + + +def main(): + argument_spec = mysql_common_argument_spec() + argument_spec.update( + mode=dict(type='str', default='getslave', choices=[ + 'getmaster', 'getslave', 'changemaster', 'stopslave', + 'startslave', 'resetmaster', 'resetslave', 'resetslaveall']), + master_auto_position=dict(type='bool', default=False), + master_host=dict(type='str'), + master_user=dict(type='str'), + master_password=dict(type='str', no_log=True), + master_port=dict(type='int'), + master_connect_retry=dict(type='int'), + master_log_file=dict(type='str'), + master_log_pos=dict(type='int'), + relay_log_file=dict(type='str'), + relay_log_pos=dict(type='int'), + master_ssl=dict(type='bool', default=False), + master_ssl_ca=dict(type='str'), + master_ssl_capath=dict(type='str'), + master_ssl_cert=dict(type='str'), + master_ssl_key=dict(type='str'), + master_ssl_cipher=dict(type='str'), + master_use_gtid=dict(type='str', choices=['current_pos', 'slave_pos', 'disabled']), + master_delay=dict(type='int'), + connection_name=dict(type='str'), + channel=dict(type='str'), + fail_on_error=dict(type='bool', default=False), + ) + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=[ + ['connection_name', 'channel'] + ], + ) + mode = module.params["mode"] + master_host = module.params["master_host"] + master_user = module.params["master_user"] + master_password = module.params["master_password"] + master_port = module.params["master_port"] + master_connect_retry = module.params["master_connect_retry"] + master_log_file = module.params["master_log_file"] + master_log_pos = module.params["master_log_pos"] + relay_log_file = module.params["relay_log_file"] + relay_log_pos = module.params["relay_log_pos"] + master_ssl = module.params["master_ssl"] + master_ssl_ca = module.params["master_ssl_ca"] + master_ssl_capath = module.params["master_ssl_capath"] + master_ssl_cert = module.params["master_ssl_cert"] + master_ssl_key = module.params["master_ssl_key"] + master_ssl_cipher = module.params["master_ssl_cipher"] + master_auto_position = module.params["master_auto_position"] + ssl_cert = module.params["client_cert"] + ssl_key = module.params["client_key"] + ssl_ca = module.params["ca_cert"] + check_hostname = module.params["check_hostname"] + connect_timeout = module.params['connect_timeout'] + config_file = module.params['config_file'] + master_delay = module.params['master_delay'] + if module.params.get("master_use_gtid") == 'disabled': + master_use_gtid = 'no' + else: + master_use_gtid = module.params["master_use_gtid"] + connection_name = module.params["connection_name"] + channel = module.params['channel'] + fail_on_error = module.params['fail_on_error'] + + if mysql_driver is None: + module.fail_json(msg=mysql_driver_fail_msg) + else: + warnings.filterwarnings('error', category=mysql_driver.Warning) + + login_password = module.params["login_password"] + login_user = module.params["login_user"] + + try: + cursor, db_conn = mysql_connect(module, login_user, login_password, config_file, + ssl_cert, ssl_key, ssl_ca, None, cursor_class='DictCursor', + connect_timeout=connect_timeout, check_hostname=check_hostname) + except Exception as e: + if os.path.exists(config_file): + module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or %s has the credentials. " + "Exception message: %s" % (config_file, to_native(e))) + else: + module.fail_json(msg="unable to find %s. Exception message: %s" % (config_file, to_native(e))) + + # Since MySQL 8.0.22 and MariaDB 10.5.1, + # "REPLICA" must be used instead of "SLAVE" + if uses_replica_terminology(cursor): + replica_term = 'REPLICA' + else: + replica_term = 'SLAVE' + + if mode in "getmaster": + status = get_master_status(cursor) + if not isinstance(status, dict): + status = dict(Is_Master=False, msg="Server is not configured as mysql master") + else: + status['Is_Master'] = True + module.exit_json(queries=executed_queries, **status) + + elif mode in "getslave": + status = get_slave_status(cursor, connection_name, channel, replica_term) + if not isinstance(status, dict): + status = dict(Is_Slave=False, msg="Server is not configured as mysql slave") + else: + status['Is_Slave'] = True + module.exit_json(queries=executed_queries, **status) + + elif mode in "changemaster": + chm = [] + result = {} + if master_host is not None: + chm.append("MASTER_HOST='%s'" % master_host) + if master_user is not None: + chm.append("MASTER_USER='%s'" % master_user) + if master_password is not None: + chm.append("MASTER_PASSWORD='%s'" % master_password) + if master_port is not None: + chm.append("MASTER_PORT=%s" % master_port) + if master_connect_retry is not None: + chm.append("MASTER_CONNECT_RETRY=%s" % master_connect_retry) + if master_log_file is not None: + chm.append("MASTER_LOG_FILE='%s'" % master_log_file) + if master_log_pos is not None: + chm.append("MASTER_LOG_POS=%s" % master_log_pos) + if master_delay is not None: + chm.append("MASTER_DELAY=%s" % master_delay) + if relay_log_file is not None: + chm.append("RELAY_LOG_FILE='%s'" % relay_log_file) + if relay_log_pos is not None: + chm.append("RELAY_LOG_POS=%s" % relay_log_pos) + if master_ssl: + chm.append("MASTER_SSL=1") + if master_ssl_ca is not None: + chm.append("MASTER_SSL_CA='%s'" % master_ssl_ca) + if master_ssl_capath is not None: + chm.append("MASTER_SSL_CAPATH='%s'" % master_ssl_capath) + if master_ssl_cert is not None: + chm.append("MASTER_SSL_CERT='%s'" % master_ssl_cert) + if master_ssl_key is not None: + chm.append("MASTER_SSL_KEY='%s'" % master_ssl_key) + if master_ssl_cipher is not None: + chm.append("MASTER_SSL_CIPHER='%s'" % master_ssl_cipher) + if master_auto_position: + chm.append("MASTER_AUTO_POSITION=1") + if master_use_gtid is not None: + chm.append("MASTER_USE_GTID=%s" % master_use_gtid) + try: + changemaster(cursor, chm, connection_name, channel) + except mysql_driver.Warning as e: + result['warning'] = to_native(e) + except Exception as e: + module.fail_json(msg='%s. Query == CHANGE MASTER TO %s' % (to_native(e), chm)) + result['changed'] = True + module.exit_json(queries=executed_queries, **result) + elif mode in "startslave": + started = start_slave(module, cursor, connection_name, channel, fail_on_error, replica_term) + if started is True: + module.exit_json(msg="Slave started ", changed=True, queries=executed_queries) + else: + module.exit_json(msg="Slave already started (Or cannot be started)", changed=False, queries=executed_queries) + elif mode in "stopslave": + stopped = stop_slave(module, cursor, connection_name, channel, fail_on_error, replica_term) + if stopped is True: + module.exit_json(msg="Slave stopped", changed=True, queries=executed_queries) + else: + module.exit_json(msg="Slave already stopped", changed=False, queries=executed_queries) + elif mode in "resetmaster": + reset = reset_master(module, cursor, fail_on_error) + if reset is True: + module.exit_json(msg="Master reset", changed=True, queries=executed_queries) + else: + module.exit_json(msg="Master already reset", changed=False, queries=executed_queries) + elif mode in "resetslave": + reset = reset_slave(module, cursor, connection_name, channel, fail_on_error, replica_term) + if reset is True: + module.exit_json(msg="Slave reset", changed=True, queries=executed_queries) + else: + module.exit_json(msg="Slave already reset", changed=False, queries=executed_queries) + elif mode in "resetslaveall": + reset = reset_slave_all(module, cursor, connection_name, channel, fail_on_error, replica_term) + if reset is True: + module.exit_json(msg="Slave reset", changed=True, queries=executed_queries) + else: + module.exit_json(msg="Slave already reset", changed=False, queries=executed_queries) + + warnings.simplefilter("ignore") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/mysql/plugins/modules/mysql_user.py b/collections-debian-merged/ansible_collections/community/mysql/plugins/modules/mysql_user.py new file mode 100644 index 00000000..0c757fa8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/plugins/modules/mysql_user.py @@ -0,0 +1,1151 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2012, Mark Theunissen <mark.theunissen@gmail.com> +# Sponsored by Four Kitchens http://fourkitchens.com. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: mysql_user +short_description: Adds or removes a user from a MySQL database +description: + - Adds or removes a user from a MySQL database. +options: + name: + description: + - Name of the user (role) to add or remove. + type: str + required: true + password: + description: + - Set the user's password. + type: str + encrypted: + description: + - Indicate that the 'password' field is a `mysql_native_password` hash. + type: bool + default: no + host: + description: + - The 'host' part of the MySQL username. + type: str + default: localhost + host_all: + description: + - Override the host option, making ansible apply changes + to all hostnames for a given user. + - This option cannot be used when creating users. + type: bool + default: no + priv: + description: + - "MySQL privileges string in the format: C(db.table:priv1,priv2)." + - "Multiple privileges can be specified by separating each one using + a forward slash: C(db.table:priv/db.table:priv)." + - The format is based on MySQL C(GRANT) statement. + - Database and table names can be quoted, MySQL-style. + - If column privileges are used, the C(priv1,priv2) part must be + exactly as returned by a C(SHOW GRANT) statement. If not followed, + the module will always report changes. It includes grouping columns + by permission (C(SELECT(col1,col2)) instead of C(SELECT(col1),SELECT(col2))). + - Can be passed as a dictionary (see the examples). + type: raw + append_privs: + description: + - Append the privileges defined by priv to the existing ones for this + user instead of overwriting existing ones. + type: bool + default: no + tls_requires: + description: + - Set requirement for secure transport as a dictionary of requirements (see the examples). + - Valid requirements are SSL, X509, SUBJECT, ISSUER, CIPHER. + - SUBJECT, ISSUER and CIPHER are complementary, and mutually exclusive with SSL and X509. + - U(https://mariadb.com/kb/en/securing-connections-for-client-and-server/#requiring-tls). + type: dict + version_added: 1.0.0 + sql_log_bin: + description: + - Whether binary logging should be enabled or disabled for the connection. + type: bool + default: yes + state: + description: + - Whether the user should exist. + - When C(absent), removes the user. + type: str + choices: [ absent, present ] + default: present + check_implicit_admin: + description: + - Check if mysql allows login as root/nopassword before trying supplied credentials. + - If success, passed I(login_user)/I(login_password) will be ignored. + type: bool + default: no + update_password: + description: + - C(always) will update passwords if they differ. + - C(on_create) will only set the password for newly created users. + type: str + choices: [ always, on_create ] + default: always + plugin: + description: + - User's plugin to authenticate (``CREATE USER user IDENTIFIED WITH plugin``). + type: str + version_added: '0.1.0' + plugin_hash_string: + description: + - User's plugin hash string (``CREATE USER user IDENTIFIED WITH plugin AS plugin_hash_string``). + type: str + version_added: '0.1.0' + plugin_auth_string: + description: + - User's plugin auth_string (``CREATE USER user IDENTIFIED WITH plugin BY plugin_auth_string``). + type: str + version_added: '0.1.0' + resource_limits: + description: + - Limit the user for certain server resources. Provided since MySQL 5.6 / MariaDB 10.2. + - "Available options are C(MAX_QUERIES_PER_HOUR: num), C(MAX_UPDATES_PER_HOUR: num), + C(MAX_CONNECTIONS_PER_HOUR: num), C(MAX_USER_CONNECTIONS: num)." + - Used when I(state=present), ignored otherwise. + type: dict + version_added: '0.1.0' + +notes: + - "MySQL server installs with default I(login_user) of C(root) and no password. + To secure this user as part of an idempotent playbook, you must create at least two tasks: + 1) change the root user's password, without providing any I(login_user)/I(login_password) details, + 2) drop a C(~/.my.cnf) file containing the new root credentials. + Subsequent runs of the playbook will then succeed by reading the new credentials from the file." + - Currently, there is only support for the C(mysql_native_password) encrypted password hash module. + - Supports (check_mode). + +seealso: +- module: community.mysql.mysql_info +- name: MySQL access control and account management reference + description: Complete reference of the MySQL access control and account management documentation. + link: https://dev.mysql.com/doc/refman/8.0/en/access-control.html +- name: MySQL provided privileges reference + description: Complete reference of the MySQL provided privileges documentation. + link: https://dev.mysql.com/doc/refman/8.0/en/privileges-provided.html + +author: +- Jonathan Mainguy (@Jmainguy) +- Benjamin Malynovytch (@bmalynovytch) +- Lukasz Tomaszkiewicz (@tomaszkiewicz) +extends_documentation_fragment: +- community.mysql.mysql + +''' + +EXAMPLES = r''' +- name: Removes anonymous user account for localhost + community.mysql.mysql_user: + name: '' + host: localhost + state: absent + +- name: Removes all anonymous user accounts + community.mysql.mysql_user: + name: '' + host_all: yes + state: absent + +- name: Create database user with name 'bob' and password '12345' with all database privileges + community.mysql.mysql_user: + name: bob + password: 12345 + priv: '*.*:ALL' + state: present + +- name: Create database user using hashed password with all database privileges + community.mysql.mysql_user: + name: bob + password: '*EE0D72C1085C46C5278932678FBE2C6A782821B4' + encrypted: yes + priv: '*.*:ALL' + state: present + +- name: Create database user with password and all database privileges and 'WITH GRANT OPTION' + community.mysql.mysql_user: + name: bob + password: 12345 + priv: '*.*:ALL,GRANT' + state: present + +- name: Create user with password, all database privileges and 'WITH GRANT OPTION' in db1 and db2 + community.mysql.mysql_user: + state: present + name: bob + password: 12345dd + priv: + 'db1.*': 'ALL,GRANT' + 'db2.*': 'ALL,GRANT' + +# Note that REQUIRESSL is a special privilege that should only apply to *.* by itself. +# Setting this privilege in this manner is supported for backwards compatibility only. +# Use 'tls_requires' instead. +- name: Modify user to require SSL connections + community.mysql.mysql_user: + name: bob + append_privs: yes + priv: '*.*:REQUIRESSL' + state: present + +- name: Modify user to require TLS connection with a valid client certificate + community.mysql.mysql_user: + name: bob + tls_requires: + x509: + state: present + +- name: Modify user to require TLS connection with a specific client certificate and cipher + community.mysql.mysql_user: + name: bob + tls_requires: + subject: '/CN=alice/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' + cipher: 'ECDHE-ECDSA-AES256-SHA384' + +- name: Modify user to no longer require SSL + community.mysql.mysql_user: + name: bob + tls_requires: + +- name: Ensure no user named 'sally'@'localhost' exists, also passing in the auth credentials + community.mysql.mysql_user: + login_user: root + login_password: 123456 + name: sally + state: absent + +# check_implicit_admin example +- name: > + Ensure no user named 'sally'@'localhost' exists, also passing in the auth credentials. + If mysql allows root/nopassword login, try it without the credentials first. + If it's not allowed, pass the credentials + community.mysql.mysql_user: + check_implicit_admin: yes + login_user: root + login_password: 123456 + name: sally + state: absent + +- name: Ensure no user named 'sally' exists at all + community.mysql.mysql_user: + name: sally + host_all: yes + state: absent + +- name: Specify grants composed of more than one word + community.mysql.mysql_user: + name: replication + password: 12345 + priv: "*.*:REPLICATION CLIENT" + state: present + +- name: Revoke all privileges for user 'bob' and password '12345' + community.mysql.mysql_user: + name: bob + password: 12345 + priv: "*.*:USAGE" + state: present + +# Example privileges string format +# mydb.*:INSERT,UPDATE/anotherdb.*:SELECT/yetanotherdb.*:ALL + +- name: Example using login_unix_socket to connect to server + community.mysql.mysql_user: + name: root + password: abc123 + login_unix_socket: /var/run/mysqld/mysqld.sock + +- name: Example of skipping binary logging while adding user 'bob' + community.mysql.mysql_user: + name: bob + password: 12345 + priv: "*.*:USAGE" + state: present + sql_log_bin: no + +- name: Create user 'bob' authenticated with plugin 'AWSAuthenticationPlugin' + community.mysql.mysql_user: + name: bob + plugin: AWSAuthenticationPlugin + plugin_hash_string: RDS + priv: '*.*:ALL' + state: present + +- name: Limit bob's resources to 10 queries per hour and 5 connections per hour + community.mysql.mysql_user: + name: bob + resource_limits: + MAX_QUERIES_PER_HOUR: 10 + MAX_CONNECTIONS_PER_HOUR: 5 + +# Example .my.cnf file for setting the root password +# [client] +# user=root +# password=n<_665{vS43y +''' + +RETURN = '''#''' + +import re +import string +from distutils.version import LooseVersion + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.mysql.plugins.module_utils.database import SQLParseError +from ansible_collections.community.mysql.plugins.module_utils.mysql import ( + mysql_connect, mysql_driver, mysql_driver_fail_msg, mysql_common_argument_spec, get_server_version +) +from ansible.module_utils.six import iteritems +from ansible.module_utils._text import to_native + + +VALID_PRIVS = frozenset(('CREATE', 'DROP', 'GRANT', 'GRANT OPTION', + 'LOCK TABLES', 'REFERENCES', 'EVENT', 'ALTER', + 'DELETE', 'INDEX', 'INSERT', 'SELECT', 'UPDATE', + 'CREATE TEMPORARY TABLES', 'TRIGGER', 'CREATE VIEW', + 'SHOW VIEW', 'ALTER ROUTINE', 'CREATE ROUTINE', + 'EXECUTE', 'FILE', 'CREATE TABLESPACE', 'CREATE USER', + 'PROCESS', 'PROXY', 'RELOAD', 'REPLICATION CLIENT', + 'REPLICATION SLAVE', 'SHOW DATABASES', 'SHUTDOWN', + 'SUPER', 'ALL', 'ALL PRIVILEGES', 'USAGE', 'REQUIRESSL', + 'CREATE ROLE', 'DROP ROLE', 'APPLICATION_PASSWORD_ADMIN', + 'AUDIT_ADMIN', 'BACKUP_ADMIN', 'BINLOG_ADMIN', + 'BINLOG_ENCRYPTION_ADMIN', 'CLONE_ADMIN', 'CONNECTION_ADMIN', + 'ENCRYPTION_KEY_ADMIN', 'FIREWALL_ADMIN', 'FIREWALL_USER', + 'GROUP_REPLICATION_ADMIN', 'INNODB_REDO_LOG_ARCHIVE', + 'NDB_STORED_USER', 'PERSIST_RO_VARIABLES_ADMIN', + 'REPLICATION_APPLIER', 'REPLICATION_SLAVE_ADMIN', + 'RESOURCE_GROUP_ADMIN', 'RESOURCE_GROUP_USER', + 'ROLE_ADMIN', 'SESSION_VARIABLES_ADMIN', 'SET_USER_ID', + 'SYSTEM_USER', 'SYSTEM_VARIABLES_ADMIN', 'SYSTEM_USER', + 'TABLE_ENCRYPTION_ADMIN', 'VERSION_TOKEN_ADMIN', + 'XA_RECOVER_ADMIN', 'LOAD FROM S3', 'SELECT INTO S3', + 'INVOKE LAMBDA', + 'ALTER ROUTINE', + 'BINLOG ADMIN', + 'BINLOG MONITOR', + 'BINLOG REPLAY', + 'CONNECTION ADMIN', + 'READ_ONLY ADMIN', + 'REPLICATION MASTER ADMIN', + 'REPLICATION SLAVE', + 'REPLICATION SLAVE ADMIN', + 'SET USER', + 'SHOW_ROUTINE',)) + + +class InvalidPrivsError(Exception): + pass + +# =========================================== +# MySQL module specific support methods. +# + + +# User Authentication Management changed in MySQL 5.7 and MariaDB 10.2.0 +def use_old_user_mgmt(cursor): + cursor.execute("SELECT VERSION()") + result = cursor.fetchone() + version_str = result[0] + version = version_str.split('.') + + if 'mariadb' in version_str.lower(): + # Prior to MariaDB 10.2 + if int(version[0]) * 1000 + int(version[1]) < 10002: + return True + else: + return False + else: + # Prior to MySQL 5.7 + if int(version[0]) * 1000 + int(version[1]) < 5007: + return True + else: + return False + + +def supports_identified_by_password(cursor): + """ + Determines whether the 'CREATE USER %s@%s IDENTIFIED BY PASSWORD %s' syntax is supported. This was dropped in + MySQL 8.0. + """ + version_str = get_server_version(cursor) + + if 'mariadb' in version_str.lower(): + return True + else: + return LooseVersion(version_str) < LooseVersion('8') + + +def get_mode(cursor): + cursor.execute('SELECT @@GLOBAL.sql_mode') + result = cursor.fetchone() + mode_str = result[0] + if 'ANSI' in mode_str: + mode = 'ANSI' + else: + mode = 'NOTANSI' + return mode + + +def user_exists(cursor, user, host, host_all): + if host_all: + cursor.execute("SELECT count(*) FROM mysql.user WHERE user = %s", (user,)) + else: + cursor.execute("SELECT count(*) FROM mysql.user WHERE user = %s AND host = %s", (user, host)) + + count = cursor.fetchone() + return count[0] > 0 + + +def sanitize_requires(tls_requires): + sanitized_requires = {} + if tls_requires: + for key in tls_requires.keys(): + sanitized_requires[key.upper()] = tls_requires[key] + if any([key in ["CIPHER", "ISSUER", "SUBJECT"] for key in sanitized_requires.keys()]): + sanitized_requires.pop("SSL", None) + sanitized_requires.pop("X509", None) + return sanitized_requires + + if "X509" in sanitized_requires.keys(): + sanitized_requires = "X509" + else: + sanitized_requires = "SSL" + + return sanitized_requires + return None + + +def mogrify_requires(query, params, tls_requires): + if tls_requires: + if isinstance(tls_requires, dict): + k, v = zip(*tls_requires.items()) + requires_query = " AND ".join(("%s %%s" % key for key in k)) + params += v + else: + requires_query = tls_requires + query = " REQUIRE ".join((query, requires_query)) + return query, params + + +def do_not_mogrify_requires(query, params, tls_requires): + return query, params + + +def get_tls_requires(cursor, user, host): + if user: + if not use_old_user_mgmt(cursor): + query = "SHOW CREATE USER '%s'@'%s'" % (user, host) + else: + query = "SHOW GRANTS for '%s'@'%s'" % (user, host) + + cursor.execute(query) + require_list = [tuple[0] for tuple in filter(lambda x: "REQUIRE" in x[0], cursor.fetchall())] + require_line = require_list[0] if require_list else "" + pattern = r"(?<=\bREQUIRE\b)(.*?)(?=(?:\bPASSWORD\b|$))" + requires_match = re.search(pattern, require_line) + requires = requires_match.group().strip() if requires_match else "" + if any((requires.startswith(req) for req in ('SSL', 'X509', 'NONE'))): + requires = requires.split()[0] + if requires == 'NONE': + requires = None + else: + import shlex + + items = iter(shlex.split(requires)) + requires = dict(zip(items, items)) + return requires or None + + +def get_grants(cursor, user, host): + cursor.execute("SHOW GRANTS FOR %s@%s", (user, host)) + grants_line = list(filter(lambda x: "ON *.*" in x[0], cursor.fetchall()))[0] + pattern = r"(?<=\bGRANT\b)(.*?)(?=(?:\bON\b))" + grants = re.search(pattern, grants_line[0]).group().strip() + return grants.split(", ") + + +def user_add(cursor, user, host, host_all, password, encrypted, + plugin, plugin_hash_string, plugin_auth_string, new_priv, + tls_requires, check_mode): + # we cannot create users without a proper hostname + if host_all: + return False + + if check_mode: + return True + + # Determine what user management method server uses + old_user_mgmt = use_old_user_mgmt(cursor) + + mogrify = do_not_mogrify_requires if old_user_mgmt else mogrify_requires + + if password and encrypted: + if supports_identified_by_password(cursor): + query_with_args = "CREATE USER %s@%s IDENTIFIED BY PASSWORD %s", (user, host, password) + else: + query_with_args = "CREATE USER %s@%s IDENTIFIED WITH mysql_native_password AS %s", (user, host, password) + elif password and not encrypted: + if old_user_mgmt: + query_with_args = "CREATE USER %s@%s IDENTIFIED BY %s", (user, host, password) + else: + cursor.execute("SELECT CONCAT('*', UCASE(SHA1(UNHEX(SHA1(%s)))))", (password,)) + encrypted_password = cursor.fetchone()[0] + query_with_args = "CREATE USER %s@%s IDENTIFIED WITH mysql_native_password AS %s", (user, host, encrypted_password) + elif plugin and plugin_hash_string: + query_with_args = "CREATE USER %s@%s IDENTIFIED WITH %s AS %s", (user, host, plugin, plugin_hash_string) + elif plugin and plugin_auth_string: + query_with_args = "CREATE USER %s@%s IDENTIFIED WITH %s BY %s", (user, host, plugin, plugin_auth_string) + elif plugin: + query_with_args = "CREATE USER %s@%s IDENTIFIED WITH %s", (user, host, plugin) + else: + query_with_args = "CREATE USER %s@%s", (user, host) + + query_with_args_and_tls_requires = query_with_args + (tls_requires,) + cursor.execute(*mogrify(*query_with_args_and_tls_requires)) + + if new_priv is not None: + for db_table, priv in iteritems(new_priv): + privileges_grant(cursor, user, host, db_table, priv, tls_requires) + if tls_requires is not None: + privileges_grant(cursor, user, host, "*.*", get_grants(cursor, user, host), tls_requires) + return True + + +def is_hash(password): + ishash = False + if len(password) == 41 and password[0] == '*': + if frozenset(password[1:]).issubset(string.hexdigits): + ishash = True + return ishash + + +def user_mod(cursor, user, host, host_all, password, encrypted, + plugin, plugin_hash_string, plugin_auth_string, new_priv, + append_privs, tls_requires, module): + changed = False + msg = "User unchanged" + grant_option = False + + # Determine what user management method server uses + old_user_mgmt = use_old_user_mgmt(cursor) + + if host_all: + hostnames = user_get_hostnames(cursor, user) + else: + hostnames = [host] + + for host in hostnames: + # Handle clear text and hashed passwords. + if bool(password): + + # Get a list of valid columns in mysql.user table to check if Password and/or authentication_string exist + cursor.execute(""" + SELECT COLUMN_NAME FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = 'mysql' AND TABLE_NAME = 'user' AND COLUMN_NAME IN ('Password', 'authentication_string') + ORDER BY COLUMN_NAME DESC LIMIT 1 + """) + colA = cursor.fetchone() + + cursor.execute(""" + SELECT COLUMN_NAME FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = 'mysql' AND TABLE_NAME = 'user' AND COLUMN_NAME IN ('Password', 'authentication_string') + ORDER BY COLUMN_NAME ASC LIMIT 1 + """) + colB = cursor.fetchone() + + # Select hash from either Password or authentication_string, depending which one exists and/or is filled + cursor.execute(""" + SELECT COALESCE( + CASE WHEN %s = '' THEN NULL ELSE %s END, + CASE WHEN %s = '' THEN NULL ELSE %s END + ) + FROM mysql.user WHERE user = %%s AND host = %%s + """ % (colA[0], colA[0], colB[0], colB[0]), (user, host)) + current_pass_hash = cursor.fetchone()[0] + if isinstance(current_pass_hash, bytes): + current_pass_hash = current_pass_hash.decode('ascii') + + if encrypted: + encrypted_password = password + if not is_hash(encrypted_password): + module.fail_json(msg="encrypted was specified however it does not appear to be a valid hash expecting: *SHA1(SHA1(your_password))") + else: + if old_user_mgmt: + cursor.execute("SELECT PASSWORD(%s)", (password,)) + else: + cursor.execute("SELECT CONCAT('*', UCASE(SHA1(UNHEX(SHA1(%s)))))", (password,)) + encrypted_password = cursor.fetchone()[0] + + if current_pass_hash != encrypted_password: + msg = "Password updated" + if module.check_mode: + return (True, msg) + if old_user_mgmt: + cursor.execute("SET PASSWORD FOR %s@%s = %s", (user, host, encrypted_password)) + msg = "Password updated (old style)" + else: + try: + cursor.execute("ALTER USER %s@%s IDENTIFIED WITH mysql_native_password AS %s", (user, host, encrypted_password)) + msg = "Password updated (new style)" + except (mysql_driver.Error) as e: + # https://stackoverflow.com/questions/51600000/authentication-string-of-root-user-on-mysql + # Replacing empty root password with new authentication mechanisms fails with error 1396 + if e.args[0] == 1396: + cursor.execute( + "UPDATE mysql.user SET plugin = %s, authentication_string = %s, Password = '' WHERE User = %s AND Host = %s", + ('mysql_native_password', encrypted_password, user, host) + ) + cursor.execute("FLUSH PRIVILEGES") + msg = "Password forced update" + else: + raise e + changed = True + + # Handle plugin authentication + if plugin: + cursor.execute("SELECT plugin, authentication_string FROM mysql.user " + "WHERE user = %s AND host = %s", (user, host)) + current_plugin = cursor.fetchone() + + update = False + + if current_plugin[0] != plugin: + update = True + + if plugin_hash_string and current_plugin[1] != plugin_hash_string: + update = True + + if plugin_auth_string and current_plugin[1] != plugin_auth_string: + # this case can cause more updates than expected, + # as plugin can hash auth_string in any way it wants + # and there's no way to figure it out for + # a check, so I prefer to update more often than never + update = True + + if update: + if plugin_hash_string: + query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s AS %s", (user, host, plugin, plugin_hash_string) + elif plugin_auth_string: + query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s BY %s", (user, host, plugin, plugin_auth_string) + else: + query_with_args = "ALTER USER %s@%s IDENTIFIED WITH %s", (user, host, plugin) + + cursor.execute(*query_with_args) + changed = True + + # Handle privileges + if new_priv is not None: + curr_priv = privileges_get(cursor, user, host) + + # If the user has privileges on a db.table that doesn't appear at all in + # the new specification, then revoke all privileges on it. + for db_table, priv in iteritems(curr_priv): + # If the user has the GRANT OPTION on a db.table, revoke it first. + if "GRANT" in priv: + grant_option = True + if db_table not in new_priv: + if user != "root" and "PROXY" not in priv and not append_privs: + msg = "Privileges updated" + if module.check_mode: + return (True, msg) + privileges_revoke(cursor, user, host, db_table, priv, grant_option) + changed = True + + # If the user doesn't currently have any privileges on a db.table, then + # we can perform a straight grant operation. + for db_table, priv in iteritems(new_priv): + if db_table not in curr_priv: + msg = "New privileges granted" + if module.check_mode: + return (True, msg) + privileges_grant(cursor, user, host, db_table, priv, tls_requires) + changed = True + + # If the db.table specification exists in both the user's current privileges + # and in the new privileges, then we need to see if there's a difference. + db_table_intersect = set(new_priv.keys()) & set(curr_priv.keys()) + for db_table in db_table_intersect: + + # If appending privileges, only the set difference between new privileges and current privileges matter. + # The symmetric difference isn't relevant for append because existing privileges will not be revoked. + if append_privs: + priv_diff = set(new_priv[db_table]) - set(curr_priv[db_table]) + else: + priv_diff = set(new_priv[db_table]) ^ set(curr_priv[db_table]) + + if len(priv_diff) > 0: + msg = "Privileges updated" + if module.check_mode: + return (True, msg) + if not append_privs: + privileges_revoke(cursor, user, host, db_table, curr_priv[db_table], grant_option) + privileges_grant(cursor, user, host, db_table, new_priv[db_table], tls_requires) + changed = True + + # Handle TLS requirements + current_requires = get_tls_requires(cursor, user, host) + if current_requires != tls_requires: + msg = "TLS requires updated" + if module.check_mode: + return (True, msg) + if not old_user_mgmt: + pre_query = "ALTER USER" + else: + pre_query = "GRANT %s ON *.* TO" % ",".join(get_grants(cursor, user, host)) + + if tls_requires is not None: + query = " ".join((pre_query, "%s@%s")) + query_with_args = mogrify_requires(query, (user, host), tls_requires) + else: + query = " ".join((pre_query, "%s@%s REQUIRE NONE")) + query_with_args = query, (user, host) + + cursor.execute(*query_with_args) + changed = True + + return (changed, msg) + + +def user_delete(cursor, user, host, host_all, check_mode): + if check_mode: + return True + + if host_all: + hostnames = user_get_hostnames(cursor, user) + else: + hostnames = [host] + + for hostname in hostnames: + cursor.execute("DROP USER %s@%s", (user, hostname)) + + return True + + +def user_get_hostnames(cursor, user): + cursor.execute("SELECT Host FROM mysql.user WHERE user = %s", (user,)) + hostnames_raw = cursor.fetchall() + hostnames = [] + + for hostname_raw in hostnames_raw: + hostnames.append(hostname_raw[0]) + + return hostnames + + +def privileges_get(cursor, user, host): + """ MySQL doesn't have a better method of getting privileges aside from the + SHOW GRANTS query syntax, which requires us to then parse the returned string. + Here's an example of the string that is returned from MySQL: + + GRANT USAGE ON *.* TO 'user'@'localhost' IDENTIFIED BY 'pass'; + + This function makes the query and returns a dictionary containing the results. + The dictionary format is the same as that returned by privileges_unpack() below. + """ + output = {} + cursor.execute("SHOW GRANTS FOR %s@%s", (user, host)) + grants = cursor.fetchall() + + def pick(x): + if x == 'ALL PRIVILEGES': + return 'ALL' + else: + return x + + for grant in grants: + res = re.match("""GRANT (.+) ON (.+) TO (['`"]).*\\3@(['`"]).*\\4( IDENTIFIED BY PASSWORD (['`"]).+\\6)? ?(.*)""", grant[0]) + if res is None: + raise InvalidPrivsError('unable to parse the MySQL grant string: %s' % grant[0]) + privileges = res.group(1).split(",") + privileges = [pick(x.strip()) for x in privileges] + if "WITH GRANT OPTION" in res.group(7): + privileges.append('GRANT') + if 'REQUIRE SSL' in res.group(7): + privileges.append('REQUIRESSL') + db = res.group(2) + output.setdefault(db, []).extend(privileges) + return output + + +def privileges_unpack(priv, mode): + """ Take a privileges string, typically passed as a parameter, and unserialize + it into a dictionary, the same format as privileges_get() above. We have this + custom format to avoid using YAML/JSON strings inside YAML playbooks. Example + of a privileges string: + + mydb.*:INSERT,UPDATE/anotherdb.*:SELECT/yetanother.*:ALL + + The privilege USAGE stands for no privileges, so we add that in on *.* if it's + not specified in the string, as MySQL will always provide this by default. + """ + if mode == 'ANSI': + quote = '"' + else: + quote = '`' + output = {} + privs = [] + for item in priv.strip().split('/'): + pieces = item.strip().rsplit(':', 1) + dbpriv = pieces[0].rsplit(".", 1) + + # Check for FUNCTION or PROCEDURE object types + parts = dbpriv[0].split(" ", 1) + object_type = '' + if len(parts) > 1 and (parts[0] == 'FUNCTION' or parts[0] == 'PROCEDURE'): + object_type = parts[0] + ' ' + dbpriv[0] = parts[1] + + # Do not escape if privilege is for database or table, i.e. + # neither quote *. nor .* + for i, side in enumerate(dbpriv): + if side.strip('`') != '*': + dbpriv[i] = '%s%s%s' % (quote, side.strip('`'), quote) + pieces[0] = object_type + '.'.join(dbpriv) + + if '(' in pieces[1]: + output[pieces[0]] = re.split(r',\s*(?=[^)]*(?:\(|$))', pieces[1].upper()) + for i in output[pieces[0]]: + privs.append(re.sub(r'\s*\(.*\)', '', i)) + else: + output[pieces[0]] = pieces[1].upper().split(',') + privs = output[pieces[0]] + new_privs = frozenset(privs) + if not new_privs.issubset(VALID_PRIVS): + raise InvalidPrivsError('Invalid privileges specified: %s' % new_privs.difference(VALID_PRIVS)) + + if '*.*' not in output: + output['*.*'] = ['USAGE'] + + # if we are only specifying something like REQUIRESSL and/or GRANT (=WITH GRANT OPTION) in *.* + # we still need to add USAGE as a privilege to avoid syntax errors + if 'REQUIRESSL' in priv and not set(output['*.*']).difference(set(['GRANT', 'REQUIRESSL'])): + output['*.*'].append('USAGE') + + return output + + +def privileges_revoke(cursor, user, host, db_table, priv, grant_option): + # Escape '%' since mysql db.execute() uses a format string + db_table = db_table.replace('%', '%%') + if grant_option: + query = ["REVOKE GRANT OPTION ON %s" % db_table] + query.append("FROM %s@%s") + query = ' '.join(query) + cursor.execute(query, (user, host)) + priv_string = ",".join([p for p in priv if p not in ('GRANT', 'REQUIRESSL')]) + query = ["REVOKE %s ON %s" % (priv_string, db_table)] + query.append("FROM %s@%s") + query = ' '.join(query) + cursor.execute(query, (user, host)) + + +def privileges_grant(cursor, user, host, db_table, priv, tls_requires): + # Escape '%' since mysql db.execute uses a format string and the + # specification of db and table often use a % (SQL wildcard) + db_table = db_table.replace('%', '%%') + priv_string = ",".join([p for p in priv if p not in ('GRANT', 'REQUIRESSL')]) + query = ["GRANT %s ON %s" % (priv_string, db_table)] + query.append("TO %s@%s") + params = (user, host) + if tls_requires and use_old_user_mgmt(cursor): + query, params = mogrify_requires(" ".join(query), params, tls_requires) + query = [query] + if 'REQUIRESSL' in priv and not tls_requires: + query.append("REQUIRE SSL") + if 'GRANT' in priv: + query.append("WITH GRANT OPTION") + query = ' '.join(query) + cursor.execute(query, params) + + +def convert_priv_dict_to_str(priv): + """Converts privs dictionary to string of certain format. + + Args: + priv (dict): Dict of privileges that needs to be converted to string. + + Returns: + priv (str): String representation of input argument. + """ + priv_list = ['%s:%s' % (key, val) for key, val in iteritems(priv)] + + return '/'.join(priv_list) + + +# Alter user is supported since MySQL 5.6 and MariaDB 10.2.0 +def server_supports_alter_user(cursor): + """Check if the server supports ALTER USER statement or doesn't. + + Args: + cursor (cursor): DB driver cursor object. + + Returns: True if supports, False otherwise. + """ + cursor.execute("SELECT VERSION()") + version_str = cursor.fetchone()[0] + version = version_str.split('.') + + if 'mariadb' in version_str.lower(): + # MariaDB 10.2 and later + if int(version[0]) * 1000 + int(version[1]) >= 10002: + return True + else: + return False + else: + # MySQL 5.6 and later + if int(version[0]) * 1000 + int(version[1]) >= 5006: + return True + else: + return False + + +def get_resource_limits(cursor, user, host): + """Get user resource limits. + + Args: + cursor (cursor): DB driver cursor object. + user (str): User name. + host (str): User host name. + + Returns: Dictionary containing current resource limits. + """ + + query = ('SELECT max_questions AS MAX_QUERIES_PER_HOUR, ' + 'max_updates AS MAX_UPDATES_PER_HOUR, ' + 'max_connections AS MAX_CONNECTIONS_PER_HOUR, ' + 'max_user_connections AS MAX_USER_CONNECTIONS ' + 'FROM mysql.user WHERE User = %s AND Host = %s') + cursor.execute(query, (user, host)) + res = cursor.fetchone() + + if not res: + return None + + current_limits = { + 'MAX_QUERIES_PER_HOUR': res[0], + 'MAX_UPDATES_PER_HOUR': res[1], + 'MAX_CONNECTIONS_PER_HOUR': res[2], + 'MAX_USER_CONNECTIONS': res[3], + } + return current_limits + + +def match_resource_limits(module, current, desired): + """Check and match limits. + + Args: + module (AnsibleModule): Ansible module object. + current (dict): Dictionary with current limits. + desired (dict): Dictionary with desired limits. + + Returns: Dictionary containing parameters that need to change. + """ + + if not current: + # It means the user does not exists, so we need + # to set all limits after its creation + return desired + + needs_to_change = {} + + for key, val in iteritems(desired): + if key not in current: + # Supported keys are listed in the documentation + # and must be determined in the get_resource_limits function + # (follow 'AS' keyword) + module.fail_json(msg="resource_limits: key '%s' is unsupported." % key) + + try: + val = int(val) + except Exception: + module.fail_json(msg="Can't convert value '%s' to integer." % val) + + if val != current.get(key): + needs_to_change[key] = val + + return needs_to_change + + +def limit_resources(module, cursor, user, host, resource_limits, check_mode): + """Limit user resources. + + Args: + module (AnsibleModule): Ansible module object. + cursor (cursor): DB driver cursor object. + user (str): User name. + host (str): User host name. + resource_limit (dict): Dictionary with desired limits. + check_mode (bool): Run the function in check mode or not. + + Returns: True, if changed, False otherwise. + """ + if not server_supports_alter_user(cursor): + module.fail_json(msg="The server version does not match the requirements " + "for resource_limits parameter. See module's documentation.") + + current_limits = get_resource_limits(cursor, user, host) + + needs_to_change = match_resource_limits(module, current_limits, resource_limits) + + if not needs_to_change: + return False + + if needs_to_change and check_mode: + return True + + # If not check_mode + tmp = [] + for key, val in iteritems(needs_to_change): + tmp.append('%s %s' % (key, val)) + + query = "ALTER USER %s@%s" + query += ' WITH %s' % ' '.join(tmp) + cursor.execute(query, (user, host)) + return True + + +# =========================================== +# Module execution. +# + + +def main(): + argument_spec = mysql_common_argument_spec() + argument_spec.update( + user=dict(type='str', required=True, aliases=['name']), + password=dict(type='str', no_log=True), + encrypted=dict(type='bool', default=False), + host=dict(type='str', default='localhost'), + host_all=dict(type="bool", default=False), + state=dict(type='str', default='present', choices=['absent', 'present']), + priv=dict(type='raw'), + tls_requires=dict(type='dict'), + append_privs=dict(type='bool', default=False), + check_implicit_admin=dict(type='bool', default=False), + update_password=dict(type='str', default='always', choices=['always', 'on_create'], no_log=False), + sql_log_bin=dict(type='bool', default=True), + plugin=dict(default=None, type='str'), + plugin_hash_string=dict(default=None, type='str'), + plugin_auth_string=dict(default=None, type='str'), + resource_limits=dict(type='dict'), + ) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + login_user = module.params["login_user"] + login_password = module.params["login_password"] + user = module.params["user"] + password = module.params["password"] + encrypted = module.boolean(module.params["encrypted"]) + host = module.params["host"].lower() + host_all = module.params["host_all"] + state = module.params["state"] + priv = module.params["priv"] + tls_requires = sanitize_requires(module.params["tls_requires"]) + check_implicit_admin = module.params["check_implicit_admin"] + connect_timeout = module.params["connect_timeout"] + config_file = module.params["config_file"] + append_privs = module.boolean(module.params["append_privs"]) + update_password = module.params['update_password'] + ssl_cert = module.params["client_cert"] + ssl_key = module.params["client_key"] + ssl_ca = module.params["ca_cert"] + check_hostname = module.params["check_hostname"] + db = '' + sql_log_bin = module.params["sql_log_bin"] + plugin = module.params["plugin"] + plugin_hash_string = module.params["plugin_hash_string"] + plugin_auth_string = module.params["plugin_auth_string"] + resource_limits = module.params["resource_limits"] + if priv and not isinstance(priv, (str, dict)): + module.fail_json(msg="priv parameter must be str or dict but %s was passed" % type(priv)) + + if priv and isinstance(priv, dict): + priv = convert_priv_dict_to_str(priv) + + if mysql_driver is None: + module.fail_json(msg=mysql_driver_fail_msg) + + cursor = None + try: + if check_implicit_admin: + try: + cursor, db_conn = mysql_connect(module, "root", "", config_file, ssl_cert, ssl_key, ssl_ca, db, + connect_timeout=connect_timeout, check_hostname=check_hostname) + except Exception: + pass + + if not cursor: + cursor, db_conn = mysql_connect(module, login_user, login_password, config_file, ssl_cert, ssl_key, ssl_ca, db, + connect_timeout=connect_timeout, check_hostname=check_hostname) + except Exception as e: + module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or %s has the credentials. " + "Exception message: %s" % (config_file, to_native(e))) + + if not sql_log_bin: + cursor.execute("SET SQL_LOG_BIN=0;") + + if priv is not None: + try: + mode = get_mode(cursor) + except Exception as e: + module.fail_json(msg=to_native(e)) + try: + priv = privileges_unpack(priv, mode) + except Exception as e: + module.fail_json(msg="invalid privileges string: %s" % to_native(e)) + + if state == "present": + if user_exists(cursor, user, host, host_all): + try: + if update_password == "always": + changed, msg = user_mod(cursor, user, host, host_all, password, encrypted, + plugin, plugin_hash_string, plugin_auth_string, + priv, append_privs, tls_requires, module) + else: + changed, msg = user_mod(cursor, user, host, host_all, None, encrypted, + plugin, plugin_hash_string, plugin_auth_string, + priv, append_privs, tls_requires, module) + + except (SQLParseError, InvalidPrivsError, mysql_driver.Error) as e: + module.fail_json(msg=to_native(e)) + else: + if host_all: + module.fail_json(msg="host_all parameter cannot be used when adding a user") + try: + changed = user_add(cursor, user, host, host_all, password, encrypted, + plugin, plugin_hash_string, plugin_auth_string, + priv, tls_requires, module.check_mode) + if changed: + msg = "User added" + + except (SQLParseError, InvalidPrivsError, mysql_driver.Error) as e: + module.fail_json(msg=to_native(e)) + + if resource_limits: + changed = limit_resources(module, cursor, user, host, resource_limits, module.check_mode) or changed + + elif state == "absent": + if user_exists(cursor, user, host, host_all): + changed = user_delete(cursor, user, host, host_all, module.check_mode) + msg = "User deleted" + else: + changed = False + msg = "User doesn't exist" + module.exit_json(changed=changed, user=user, msg=msg) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/mysql/plugins/modules/mysql_variables.py b/collections-debian-merged/ansible_collections/community/mysql/plugins/modules/mysql_variables.py new file mode 100644 index 00000000..06beee3c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/plugins/modules/mysql_variables.py @@ -0,0 +1,268 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2013, Balazs Pocze <banyek@gawker.com> +# Certain parts are taken from Mark Theunissen's mysqldb module +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: mysql_variables + +short_description: Manage MySQL global variables +description: +- Query / Set MySQL variables. +author: +- Balazs Pocze (@banyek) +options: + variable: + description: + - Variable name to operate. + type: str + required: yes + value: + description: + - If set, then sets variable value to this. + type: str + mode: + description: + - C(global) assigns C(value) to a global system variable which will be changed at runtime + but won't persist across server restarts. + - C(persist) assigns C(value) to a global system variable and persists it to + the mysqld-auto.cnf option file in the data directory + (the variable will survive service restarts). + - C(persist_only) persists C(value) to the mysqld-auto.cnf option file in the data directory + but without setting the global variable runtime value + (the value will be changed after the next service restart). + - Supported by MySQL 8.0 or later. + - For more information see U(https://dev.mysql.com/doc/refman/8.0/en/set-variable.html). + type: str + choices: ['global', 'persist', 'persist_only'] + default: global + version_added: '0.1.0' + +notes: +- Does not support C(check_mode). + +seealso: +- module: community.mysql.mysql_info +- name: MySQL SET command reference + description: Complete reference of the MySQL SET command documentation. + link: https://dev.mysql.com/doc/refman/8.0/en/set-statement.html + +extends_documentation_fragment: +- community.mysql.mysql +''' + +EXAMPLES = r''' +- name: Check for sync_binlog setting + community.mysql.mysql_variables: + variable: sync_binlog + +- name: Set read_only variable to 1 persistently + community.mysql.mysql_variables: + variable: read_only + value: 1 + mode: persist +''' + +RETURN = r''' +queries: + description: List of executed queries which modified DB's state. + returned: if executed + type: list + sample: ["SET GLOBAL `read_only` = 1"] + version_added: '0.1.0' +''' + +import os +import warnings +from re import match + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.mysql.plugins.module_utils.database import SQLParseError, mysql_quote_identifier +from ansible_collections.community.mysql.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg, mysql_common_argument_spec +from ansible.module_utils._text import to_native + +executed_queries = [] + + +def check_mysqld_auto(module, cursor, mysqlvar): + """Check variable's value in mysqld-auto.cnf.""" + query = ("SELECT VARIABLE_VALUE " + "FROM performance_schema.persisted_variables " + "WHERE VARIABLE_NAME = %s") + try: + cursor.execute(query, (mysqlvar,)) + res = cursor.fetchone() + except Exception as e: + if "Table 'performance_schema.persisted_variables' doesn't exist" in str(e): + module.fail_json(msg='Server version must be 8.0 or greater.') + + if res: + return res[0] + else: + return None + + +def typedvalue(value): + """ + Convert value to number whenever possible, return same value + otherwise. + + >>> typedvalue('3') + 3 + >>> typedvalue('3.0') + 3.0 + >>> typedvalue('foobar') + 'foobar' + + """ + try: + return int(value) + except ValueError: + pass + + try: + return float(value) + except ValueError: + pass + + return value + + +def getvariable(cursor, mysqlvar): + cursor.execute("SHOW VARIABLES WHERE Variable_name = %s", (mysqlvar,)) + mysqlvar_val = cursor.fetchall() + if len(mysqlvar_val) == 1: + return mysqlvar_val[0][1] + else: + return None + + +def setvariable(cursor, mysqlvar, value, mode='global'): + """ Set a global mysql variable to a given value + + The DB driver will handle quoting of the given value based on its + type, thus numeric strings like '3.0' or '8' are illegal, they + should be passed as numeric literals. + + """ + if mode == 'persist': + query = "SET PERSIST %s = " % mysql_quote_identifier(mysqlvar, 'vars') + elif mode == 'global': + query = "SET GLOBAL %s = " % mysql_quote_identifier(mysqlvar, 'vars') + elif mode == 'persist_only': + query = "SET PERSIST_ONLY %s = " % mysql_quote_identifier(mysqlvar, 'vars') + + try: + cursor.execute(query + "%s", (value,)) + executed_queries.append(query + "%s" % value) + cursor.fetchall() + result = True + except Exception as e: + result = to_native(e) + + return result + + +def main(): + argument_spec = mysql_common_argument_spec() + argument_spec.update( + variable=dict(type='str'), + value=dict(type='str'), + mode=dict(type='str', choices=['global', 'persist', 'persist_only'], default='global'), + ) + + module = AnsibleModule( + argument_spec=argument_spec + ) + user = module.params["login_user"] + password = module.params["login_password"] + connect_timeout = module.params['connect_timeout'] + ssl_cert = module.params["client_cert"] + ssl_key = module.params["client_key"] + ssl_ca = module.params["ca_cert"] + check_hostname = module.params["check_hostname"] + config_file = module.params['config_file'] + db = 'mysql' + + mysqlvar = module.params["variable"] + value = module.params["value"] + mode = module.params["mode"] + + if mysqlvar is None: + module.fail_json(msg="Cannot run without variable to operate with") + if match('^[0-9a-z_.]+$', mysqlvar) is None: + module.fail_json(msg="invalid variable name \"%s\"" % mysqlvar) + if mysql_driver is None: + module.fail_json(msg=mysql_driver_fail_msg) + else: + warnings.filterwarnings('error', category=mysql_driver.Warning) + + try: + cursor, db_conn = mysql_connect(module, user, password, config_file, ssl_cert, ssl_key, ssl_ca, db, + connect_timeout=connect_timeout, check_hostname=check_hostname) + except Exception as e: + if os.path.exists(config_file): + module.fail_json(msg=("unable to connect to database, check login_user and " + "login_password are correct or %s has the credentials. " + "Exception message: %s" % (config_file, to_native(e)))) + else: + module.fail_json(msg="unable to find %s. Exception message: %s" % (config_file, to_native(e))) + + mysqlvar_val = None + var_in_mysqld_auto_cnf = None + + mysqlvar_val = getvariable(cursor, mysqlvar) + if mysqlvar_val is None: + module.fail_json(msg="Variable not available \"%s\"" % mysqlvar, changed=False) + + if value is None: + module.exit_json(msg=mysqlvar_val) + + if mode in ('persist', 'persist_only'): + var_in_mysqld_auto_cnf = check_mysqld_auto(module, cursor, mysqlvar) + + if mode == 'persist_only': + if var_in_mysqld_auto_cnf is None: + mysqlvar_val = False + else: + mysqlvar_val = var_in_mysqld_auto_cnf + + # Type values before using them + value_wanted = typedvalue(value) + value_actual = typedvalue(mysqlvar_val) + value_in_auto_cnf = None + if var_in_mysqld_auto_cnf is not None: + value_in_auto_cnf = typedvalue(var_in_mysqld_auto_cnf) + + if value_wanted == value_actual and mode in ('global', 'persist'): + if mode == 'persist' and value_wanted == value_in_auto_cnf: + module.exit_json(msg="Variable is already set to requested value globally" + "and stored into mysqld-auto.cnf file.", changed=False) + + elif mode == 'global': + module.exit_json(msg="Variable is already set to requested value.", changed=False) + + if mode == 'persist_only' and value_wanted == value_in_auto_cnf: + module.exit_json(msg="Variable is already stored into mysqld-auto.cnf " + "with requested value.", changed=False) + + try: + result = setvariable(cursor, mysqlvar, value_wanted, mode) + except SQLParseError as e: + result = to_native(e) + + if result is True: + module.exit_json(msg="Variable change succeeded prev_value=%s" % value_actual, + changed=True, queries=executed_queries) + else: + module.fail_json(msg=result, changed=False) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/old_mariadb_replication/aliases b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/old_mariadb_replication/aliases new file mode 100644 index 00000000..1065935f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/old_mariadb_replication/aliases @@ -0,0 +1,7 @@ +destructive +unsupported # these tests conflict with mysql_replication tests and do not run on changes to the mysql_replication module +skip/aix +skip/osx +skip/freebsd +skip/rhel +needs/root diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/old_mariadb_replication/defaults/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/old_mariadb_replication/defaults/main.yml new file mode 100644 index 00000000..3751f4ef --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/old_mariadb_replication/defaults/main.yml @@ -0,0 +1,7 @@ +master_port: 3306 +standby_port: 3307 +test_db: test_db +replication_user: replication_user +replication_pass: replication_pass +dump_path: /tmp/dump.sql +conn_name: master-1 diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/old_mariadb_replication/meta/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/old_mariadb_replication/meta/main.yml new file mode 100644 index 00000000..71986171 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/old_mariadb_replication/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: +- setup_mariadb diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/old_mariadb_replication/tasks/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/old_mariadb_replication/tasks/main.yml new file mode 100644 index 00000000..4ea76a9e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/old_mariadb_replication/tasks/main.yml @@ -0,0 +1,21 @@ +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Initial CI tests of mysql_replication module +- import_tasks: mariadb_replication_initial.yml + when: + - ansible_facts.distribution == 'CentOS' + - ansible_facts.distribution_major_version is version('7', '>=') + +# Tests of master_use_gtid parameter +# https://github.com/ansible/ansible/pull/62648 +- import_tasks: mariadb_master_use_gtid.yml + when: + - ansible_facts.distribution == 'CentOS' + - ansible_facts.distribution_major_version is version('7', '>=') + +# Tests of connection_name parameter +- import_tasks: mariadb_replication_connection_name.yml + when: + - ansible_facts.distribution == 'CentOS' + - ansible_facts.distribution_major_version is version('7', '>=') diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/old_mariadb_replication/tasks/mariadb_master_use_gtid.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/old_mariadb_replication/tasks/mariadb_master_use_gtid.yml new file mode 100644 index 00000000..e3e76058 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/old_mariadb_replication/tasks/mariadb_master_use_gtid.yml @@ -0,0 +1,173 @@ +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Tests for master_use_gtid parameter. +# https://github.com/ansible/ansible/pull/62648 + +############################# +# master_use_gtid: "disabled" +############################# + +# Auxiliary step: +- name: Get master status + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ primary_db.port }}" + mode: getmaster + register: primary_status + +# Set master_use_gtid disabled: +- name: Run replication + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ replica_db.port }}" + mode: changemaster + master_host: 127.0.0.1 + master_port: "{{ primary_db.port }}" + master_user: "{{ replication_user }}" + master_password: "{{ replication_pass }}" + master_log_file: mysql-bin.000001 + master_log_pos: '{{ primary_status.Position }}' + master_use_gtid: disabled + register: result + +- assert: + that: + - result is changed + +# Start standby for further tests: +- name: Start standby + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ primary_db.port }}" + mode: startslave + +- name: Get standby status + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ replica_db.port }}" + mode: getslave + register: slave_status + +- assert: + that: + - slave_status.Using_Gtid == 'No' + +# Stop standby for further tests: +- name: Stop standby + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ replica_db.port }}" + mode: stopslave + +################################ +# master_use_gtid: "current_pos" +################################ + +# Auxiliary step: +- name: Get master status + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ primary_db.port }}" + mode: getmaster + register: primary_status + +# Set master_use_gtid current_pos: +- name: Run replication + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ replica_db.port }}" + mode: changemaster + master_host: 127.0.0.1 + master_port: "{{ primary_db.port }}" + master_user: "{{ replication_user }}" + master_password: "{{ replication_pass }}" + master_log_file: mysql-bin.000001 + master_log_pos: '{{ primary_status.Position }}' + master_use_gtid: current_pos + register: result + +- assert: + that: + - result is changed + +# Start standby for further tests: +- name: Start standby + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ primary_db.port }}" + mode: startslave + +- name: Get standby status + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ replica_db.port }}" + mode: getslave + register: slave_status + +- assert: + that: + - slave_status.Using_Gtid == 'Current_Pos' + +# Stop standby for further tests: +- name: Stop standby + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ replica_db.port }}" + mode: stopslave + +############################## +# master_use_gtid: "slave_pos" +############################## + +# Auxiliary step: +- name: Get master status + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ primary_db.port }}" + mode: getmaster + register: primary_status + +# Set master_use_gtid slave_pos: +- name: Run replication + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ replica_db.port }}" + mode: changemaster + master_host: 127.0.0.1 + master_port: "{{ primary_db.port }}" + master_user: "{{ replication_user }}" + master_password: "{{ replication_pass }}" + master_log_file: mysql-bin.000001 + master_log_pos: '{{ primary_status.Position }}' + master_use_gtid: slave_pos + register: result + +- assert: + that: + - result is changed + +# Start standby for further tests: +- name: Start standby + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ primary_db.port }}" + mode: startslave + +- name: Get standby status + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ replica_db.port }}" + mode: getslave + register: slave_status + +- assert: + that: + - slave_status.Using_Gtid == 'Slave_Pos' + +# Stop standby for further tests: +- name: Stop standby + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ replica_db.port }}" + mode: stopslave diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/old_mariadb_replication/tasks/mariadb_replication_connection_name.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/old_mariadb_replication/tasks/mariadb_replication_connection_name.yml new file mode 100644 index 00000000..98fa5fe6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/old_mariadb_replication/tasks/mariadb_replication_connection_name.yml @@ -0,0 +1,118 @@ +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Needs for further tests: +- name: Stop slave + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ replica_db.port }}" + mode: stopslave + +- name: Reset slave all + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ replica_db.port }}" + mode: resetslaveall + +# Get master log pos: +- name: Get master status + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ primary_db.port }}" + mode: getmaster + register: primary_status + +# Test changemaster mode: +- name: Run replication with connection_name + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ replica_db.port }}" + mode: changemaster + master_host: 127.0.0.1 + master_port: "{{ primary_db.port }}" + master_user: "{{ replication_user }}" + master_password: "{{ replication_pass }}" + master_log_file: mysql-bin.000001 + master_log_pos: '{{ primary_status.Position }}' + connection_name: '{{ conn_name }}' + register: result + +- assert: + that: + - result is changed + - result.queries[0] is match("CHANGE MASTER ('\S+' )?TO MASTER_HOST='[0-9.]+',MASTER_USER='\w+',MASTER_PASSWORD='[*]{8}',MASTER_PORT=\d+,MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=\d+") + +# Test startslave mode: +- name: Start slave with connection_name + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ replica_db.port }}" + mode: startslave + connection_name: "{{ conn_name }}" + register: result + +- assert: + that: + - result is changed + - result.queries == ["START SLAVE \'{{ conn_name }}\'"] + +# Test getslave mode: +- name: Get standby statu with connection_name + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ replica_db.port }}" + mode: getslave + connection_name: "{{ conn_name }}" + register: slave_status + +- assert: + that: + - slave_status.Is_Slave == true + - slave_status.Master_Host == '127.0.0.1' + - slave_status.Exec_Master_Log_Pos == primary_status.Position + - slave_status.Master_Port == {{ primary_db.port }} + - slave_status.Last_IO_Errno == 0 + - slave_status.Last_IO_Error == '' + - slave_status is not changed + +# Test stopslave mode: +- name: Stop slave with connection_name + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ replica_db.port }}" + mode: stopslave + connection_name: "{{ conn_name }}" + register: result + +- assert: + that: + - result is changed + - result.queries == ["STOP SLAVE \'{{ conn_name }}\'"] + +# Test reset +- name: Reset slave with connection_name + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ replica_db.port }}" + mode: resetslave + connection_name: "{{ conn_name }}" + register: result + +- assert: + that: + - result is changed + - result.queries == ["RESET SLAVE \'{{ conn_name }}\'"] + +# Test reset all +- name: Reset slave all with connection_name + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ replica_db.port }}" + mode: resetslaveall + connection_name: "{{ conn_name }}" + register: result + +- assert: + that: + - result is changed + - result.queries == ["RESET SLAVE \'{{ conn_name }}\' ALL"] diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/old_mariadb_replication/tasks/mariadb_replication_initial.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/old_mariadb_replication/tasks/mariadb_replication_initial.yml new file mode 100644 index 00000000..86a67606 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/old_mariadb_replication/tasks/mariadb_replication_initial.yml @@ -0,0 +1,96 @@ +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Preparation: +- name: Create user for replication + shell: "echo \"GRANT REPLICATION SLAVE ON *.* TO '{{ replication_user }}'@'localhost' IDENTIFIED BY '{{ replication_pass }}'; FLUSH PRIVILEGES;\" | mysql -P {{ primary_db.port }} -h 127.0.0.1" + +- name: Create test database + mysql_db: + login_host: 127.0.0.1 + login_port: '{{ primary_db.port }}' + state: present + name: '{{ test_db }}' + +- name: Dump all databases from the master + shell: 'mysqldump -P {{ primary_db.port }} -h 127.0.01 --all-databases --master-data=2 > {{ dump_path }}' + +- name: Restore the dump to the replica + shell: 'mysql -P {{ replica_db.port }} -h 127.0.0.1 < {{ dump_path }}' + +# Test getmaster mode: +- name: Get master status + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ primary_db.port }}" + mode: getmaster + register: master_status + +- assert: + that: + - master_status.Is_Master == true + - master_status.Position != 0 + - master_status is not changed + +# Test changemaster mode: +- name: Run replication + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ replica_db.port }}" + mode: changemaster + master_host: 127.0.0.1 + master_port: "{{ primary_db.port }}" + master_user: "{{ replication_user }}" + master_password: "{{ replication_pass }}" + master_log_file: mysql-bin.000001 + master_log_pos: '{{ master_status.Position }}' + register: result + +- assert: + that: + - result is changed + - result.queries[0] is match("CHANGE MASTER ('\S+' )?TO MASTER_HOST='[0-9.]+',MASTER_USER='\w+',MASTER_PASSWORD='[*]{8}',MASTER_PORT=\d+,MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=\d+") + +# Test startslave mode: +- name: Start slave + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ replica_db.port }}" + mode: startslave + register: result + +- assert: + that: + - result is changed + - result.queries == ["START SLAVE"] + +# Test getslave mode: +- name: Get replica status + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ replica_db.port }}" + mode: getslave + register: slave_status + +- assert: + that: + - slave_status.Is_Slave == true + - slave_status.Master_Host == '127.0.0.1' + - slave_status.Exec_Master_Log_Pos == master_status.Position + - slave_status.Master_Port == {{ primary_db.port }} + - slave_status.Last_IO_Errno == 0 + - slave_status.Last_IO_Error == '' + - slave_status is not changed + +# Test stopslave mode: +- name: Stop slave + mysql_replication: + login_host: 127.0.0.1 + login_port: "{{ replica_db.port }}" + mode: stopslave + register: result + +- assert: + that: + - result is changed + - result.queries == ["STOP SLAVE"] diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/defaults/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/defaults/main.yml new file mode 100644 index 00000000..7bcb2d23 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/defaults/main.yml @@ -0,0 +1,13 @@ +dbdeployer_version: 1.52.0 +dbdeployer_home_dir: /opt/dbdeployer + +home_dir: /root + +percona_client_version: 5.7 + +mariadb_install: false + +mysql_version: 8.0.22 +mariadb_version: 10.5.4 + +mysql_base_port: 3306 diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/handlers/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/handlers/main.yml new file mode 100644 index 00000000..090a5e77 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: "{{ role_name }} | handler | create dbdeployer installed file" + template: + src: installed_file.j2 + dest: "{{ dbdeployer_installed_file }}" + listen: create zookeeper installed file diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/tasks/config.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/tasks/config.yml new file mode 100644 index 00000000..57be29aa --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/tasks/config.yml @@ -0,0 +1,15 @@ +--- +- name: "{{ role_name }} | config | download mysql tarball" + get_url: + url: "{{ install_src }}" + dest: "{{ dbdeployer_sandbox_download_dir }}/{{ install_tarball }}" + +- name: "{{ role_name }} | config | run unpack tarball" + shell: + cmd: "dbdeployer unpack {{ dbdeployer_sandbox_download_dir }}/{{ install_tarball }}" + creates: "{{ dbdeployer_sandbox_binary_dir }}/{{ install_version }}" + +- name: "{{ role_name }} | config | setup replication topology" + shell: + cmd: "dbdeployer deploy multiple {{ install_version }} --base-port {{ mysql_base_port }} --my-cnf-options=\"master_info_repository='TABLE'\" --my-cnf-options=\"relay_log_info_repository='TABLE'\"" + creates: "{{ dbdeployer_sandbox_home_dir }}/multi_msb_{{ install_version|replace('.','_') }}" diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/tasks/dir.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/tasks/dir.yml new file mode 100644 index 00000000..dc028799 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/tasks/dir.yml @@ -0,0 +1,11 @@ +--- +- name: "{{ role_name }} | dir | create dbdeployer directories" + file: + state: directory + path: "{{ item }}" + loop: + - "{{ dbdeployer_home_dir }}" + - "{{ dbdeployer_install_dir }}" + - "{{ dbdeployer_sandbox_download_dir }}" + - "{{ dbdeployer_sandbox_binary_dir }}" + - "{{ dbdeployer_sandbox_home_dir }}" diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/tasks/install.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/tasks/install.yml new file mode 100644 index 00000000..aacdddc3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/tasks/install.yml @@ -0,0 +1,59 @@ +--- +- name: "{{ role_name }} | install | add apt signing key for percona" + apt_key: + keyserver: keyserver.ubuntu.com + id: 4D1BB29D63D98E422B2113B19334A25F8507EFA5 + state: present + +- name: "{{ role_name }} | install | add percona repositories" + apt_repository: + repo: "{{ item }}" + state: present + loop: "{{ percona_mysql_repos }}" + +- name: "{{ role_name }} | install | install packages required by percona" + apt: + name: "{{ percona_mysql_packages }}" + state: present + environment: + DEBIAN_FRONTEND: noninteractive + +- name: "{{ role_name }} | install | install packages required by mysql connector" + apt: + name: "{{ install_python_prereqs }}" + state: present + environment: + DEBIAN_FRONTEND: noninteractive + +- name: "{{ role_name }} | install | install python packages" + pip: + name: "{{ python_packages }}" + +- name: "{{ role_name }} | install | install packages required by mysql" + apt: + name: "{{ install_prereqs }}" + state: present + environment: + DEBIAN_FRONTEND: noninteractive + +- name: "{{ role_name }} | install | download and unpack dbdeployer" + unarchive: + remote_src: true + src: "{{ dbdeployer_src }}" + dest: "{{ dbdeployer_install_dir }}" + creates: "{{ dbdeployer_installed_file }}" + register: dbdeployer_tarball_install + notify: + - create zookeeper installed file + until: dbdeployer_tarball_install is not failed + retries: 6 + delay: 5 + +- name: "{{ role_name }} | install | create symlink" + file: + src: "{{ dbdeployer_install_dir }}/dbdeployer-{{ dbdeployer_version }}.linux" + dest: /usr/local/bin/dbdeployer + follow: false + state: link + +- meta: flush_handlers diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/tasks/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/tasks/main.yml new file mode 100644 index 00000000..c6a83489 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/tasks/main.yml @@ -0,0 +1,11 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- import_tasks: setvars.yml +- import_tasks: dir.yml +- import_tasks: install.yml +- import_tasks: config.yml +- import_tasks: verify.yml diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/tasks/setvars.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/tasks/setvars.yml new file mode 100644 index 00000000..14a2c545 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/tasks/setvars.yml @@ -0,0 +1,28 @@ +--- +- name: "{{ role_name }} | setvars | split mysql version in parts" + set_fact: + mysql_version_parts: "{{ mysql_version.split('.') }}" + +- name: "{{ role_name }} | setvars | get mysql major version" + set_fact: + mysql_major_version: "{{ mysql_version_parts[0] + '.' + mysql_version_parts[1] }}" + +- name: "{{ role_name }} | setvars | set the appropriate extension dependent on the mysql version" + set_fact: + mysql_compression_extension: "{{ mysql_version is version('8.0.0', '<') | ternary('gz', 'xz') }}" + +- name: "{{ role_name }} | setvars | set the install type" + set_fact: + install_type: "{{ mariadb_install | ternary('mariadb', 'mysql') }}" + +- name: "{{ role_name }} | setvars | set install_version" + set_fact: + install_version: "{{ lookup('vars', install_type + '_version') }}" + +- name: "{{ role_name }} | setvars | set install_tarball" + set_fact: + install_tarball: "{{ lookup('vars', install_type + '_tarball') }}" + +- name: "{{ role_name }} | setvars | set install_src" + set_fact: + install_src: "{{ lookup('vars', install_type + '_src') }}" diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/tasks/verify.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/tasks/verify.yml new file mode 100644 index 00000000..ca383d98 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/tasks/verify.yml @@ -0,0 +1,27 @@ +--- +- name: "{{ role_name }} | verify | confirm primary is running and get the port" + shell: "{{ dbdeployer_sandbox_home_dir }}/multi_msb_{{ install_version|replace('.','_') }}/n1 -BNe'select @@port'" + register: primary_port + +- name: "{{ role_name }} | verify | confirm replica1 is running and get the port" + shell: "{{ dbdeployer_sandbox_home_dir }}/multi_msb_{{ install_version|replace('.','_') }}/n2 -BNe'select @@port'" + register: replica1_port + +- name: "{{ role_name }} | verify | confirm replica2 is running and get the port" + shell: "{{ dbdeployer_sandbox_home_dir }}/multi_msb_{{ install_version|replace('.','_') }}/n3 -BNe'select @@port'" + register: replica2_port + +- name: "{{ role_name }} | verify | confirm primary is running on expected port" + assert: + that: + - primary_port.stdout|int == 3307 + +- name: "{{ role_name }} | verify | confirm replica1 is running on expected port" + assert: + that: + - replica1_port.stdout|int == 3308 + +- name: "{{ role_name }} | verify | confirm replica2 is running on expected port" + assert: + that: + - replica2_port.stdout|int == 3309 diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/templates/installed_file.j2 b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/templates/installed_file.j2 new file mode 100644 index 00000000..862a357a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/templates/installed_file.j2 @@ -0,0 +1 @@ +{{ dbdeployer_version }} diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/vars/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/vars/main.yml new file mode 100644 index 00000000..e2673079 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_mysql/vars/main.yml @@ -0,0 +1,31 @@ +--- +dbdeployer_install_dir: "{{ dbdeployer_home_dir }}/dbdeployer_{{ dbdeployer_version }}" +dbdeployer_src: "https://github.com/datacharmer/dbdeployer/releases/download/v{{ dbdeployer_version }}/dbdeployer-{{ dbdeployer_version }}.linux.tar.gz" +dbdeployer_installed_file: "{{ dbdeployer_home_dir }}/dbdeployer_installed" + +dbdeployer_sandbox_download_dir: "{{ home_dir }}/downloads" +dbdeployer_sandbox_binary_dir: "{{ home_dir }}/opt/mysql" +dbdeployer_sandbox_home_dir: "{{ home_dir }}/sandboxes" + +percona_mysql_repos: + - deb http://repo.percona.com/apt {{ ansible_lsb.codename }} main + - deb-src http://repo.percona.com/apt {{ ansible_lsb.codename }} main + +percona_mysql_packages: + - percona-server-client-{{ percona_client_version }} + +python_packages: [pymysql == 0.9.3] + +install_prereqs: + - libaio1 + - libnuma1 + +install_python_prereqs: + - python3-dev + - default-libmysqlclient-dev + - build-essential + +mysql_tarball: "mysql-{{ mysql_version }}-linux-glibc2.12-x86_64.tar.{{ mysql_compression_extension }}" +mysql_src: "https://dev.mysql.com/get/Downloads/MySQL-{{ mysql_major_version }}/{{ mysql_tarball }}" +mariadb_tarball: "mariadb-{{ mariadb_version }}-linux-x86_64.tar.gz" +mariadb_src: "https://downloads.mariadb.com/MariaDB/mariadb-{{ mariadb_version }}/bintar-linux-x86_64/{{ mariadb_tarball }}" diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_remote_tmp_dir/handlers/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_remote_tmp_dir/handlers/main.yml new file mode 100644 index 00000000..229037c8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_remote_tmp_dir/handlers/main.yml @@ -0,0 +1,5 @@ +- name: delete temporary directory + include_tasks: default-cleanup.yml + +- name: delete temporary directory (windows) + include_tasks: windows-cleanup.yml diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_remote_tmp_dir/tasks/default-cleanup.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_remote_tmp_dir/tasks/default-cleanup.yml new file mode 100644 index 00000000..39872d74 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_remote_tmp_dir/tasks/default-cleanup.yml @@ -0,0 +1,5 @@ +- name: delete temporary directory + file: + path: "{{ remote_tmp_dir }}" + state: absent + no_log: yes diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_remote_tmp_dir/tasks/default.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_remote_tmp_dir/tasks/default.yml new file mode 100644 index 00000000..1e0f51b8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_remote_tmp_dir/tasks/default.yml @@ -0,0 +1,11 @@ +- name: create temporary directory + tempfile: + state: directory + suffix: .test + register: remote_tmp_dir + notify: + - delete temporary directory + +- name: record temporary directory + set_fact: + remote_tmp_dir: "{{ remote_tmp_dir.path }}" diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_remote_tmp_dir/tasks/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_remote_tmp_dir/tasks/main.yml new file mode 100644 index 00000000..93d786f0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/setup_remote_tmp_dir/tasks/main.yml @@ -0,0 +1,15 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: make sure we have the ansible_os_family and ansible_distribution_version facts + setup: + gather_subset: distribution + when: ansible_facts == {} + +- include_tasks: "{{ lookup('first_found', files)}}" + vars: + files: + - "{{ ansible_os_family | lower }}.yml" + - "default.yml" diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/defaults/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/defaults/main.yml new file mode 100644 index 00000000..b6ae7806 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/defaults/main.yml @@ -0,0 +1,17 @@ +--- +# defaults file for test_mysql_db +mysql_user: root +mysql_password: msandbox +mysql_primary_port: 3307 + +db_name: 'data' +db_name2: 'data2' +db_user1: 'datauser1' +db_user2: 'datauser2' + +tmp_dir: '/tmp' +db_latin1_name: 'db_latin1' +file4: 'latin1_file' + +user_name_1: 'db_user1' +user_password_1: 'gadfFDSdtTU^Sdfuj' diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/meta/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/meta/main.yml new file mode 100644 index 00000000..f1174ff2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_mysql diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/tasks/config_overrides_defaults.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/tasks/config_overrides_defaults.yml new file mode 100644 index 00000000..42d8fd70 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/tasks/config_overrides_defaults.yml @@ -0,0 +1,108 @@ +- set_fact: + db_to_create: testdb1 + config_file: "/root/.my1.cnf" + fake_port: 9999 + fake_host: "blahblah.local" + include_dir: "/root/mycnf.d" + +- name: Create custom config file + shell: 'echo "[client]" > {{ config_file }}' + +- name: Add fake port to config file + shell: 'echo "port = {{ fake_port }}" >> {{ config_file }}' + +- name: Get pymysql version + shell: pip show pymysql | awk '/Version/ {print $2}' + register: pymysql_version + +- name: Add blank line + shell: 'echo "" >> {{ config_file }}' + when: (pymysql_version.stdout | default('1000', true)) is version('0.9.3', '>=') + +- name: Create include_dir + file: + path: '{{ include_dir }}' + state: directory + mode: '0777' + when: (pymysql_version.stdout | default('1000', true)) is version('0.9.3', '>=') + +- name: Add include_dir + lineinfile: + path: '{{ config_file }}' + line: '!includedir {{ include_dir }}' + insertafter: EOF + when: (pymysql_version.stdout | default('1000', true)) is version('0.9.3', '>=') + +- name: Create database using fake port to connect to, must fail + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_to_create }}' + state: present + check_implicit_admin: yes + config_file: '{{ config_file }}' + config_overrides_defaults: yes + ignore_errors: yes + register: result + +- name: Must fail because login_port default has beed overriden by wrong value from config file + assert: + that: + - result is failed + - result.msg is search("unable to connect to database") + +- name: Create database using default port + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_to_create }}' + state: present + check_implicit_admin: yes + config_file: '{{ config_file }}' + config_overrides_defaults: no + register: result + +- name: Must not fail because of the default of login_port is correct + assert: + that: + - result is changed + +- name: Reinit custom config file + shell: 'echo "[client]" > {{ config_file }}' + +- name: Add fake host to config file + shell: 'echo "host = {{ fake_host }}" >> {{ config_file }}' + +- name: Remove database using fake login_host + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_to_create }}' + state: absent + config_file: '{{ config_file }}' + config_overrides_defaults: yes + register: result + ignore_errors: yes + +- name: Must fail because login_host default has beed overriden by wrong value from config file + assert: + that: + - result is failed + - result.msg is search("Can't connect to MySQL server on '{{ fake_host }}'") or result.msg is search("Unknown MySQL server host '{{ fake_host }}'") + +# Clean up +- name: Remove test db + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_to_create }}' + state: absent + check_implicit_admin: yes diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/tasks/encoding_dump_import.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/tasks/encoding_dump_import.yml new file mode 100644 index 00000000..173386ca --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/tasks/encoding_dump_import.yml @@ -0,0 +1,98 @@ +--- + +- set_fact: + latin1_file1: "{{tmp_dir}}/{{file}}" + +- name: Deleting Latin1 encoded Database + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_latin1_name }}' + state: absent + +- name: create Latin1 encoded database + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_latin1_name }}' + state: present + encoding: latin1 + +- name: create a table in Latin1 database + command: "{{ mysql_command }} {{ db_latin1_name }} -e \"create table testlatin1(id int, name varchar(100))\"" + + +# Inserting a string in latin1 into table, , this string be tested later, +# so report any change of content in the test too +- name: inserting data into Latin1 database + command: "{{ mysql_command }} {{ db_latin1_name }} -e \"insert into testlatin1 value(47,'Amédée Bôlüt')\"" + +- name: selecting table + command: "{{ mysql_command }} {{ db_latin1_name }} -e \"select * from testlatin1\"" + register: output + +- name: Dumping a table in Latin1 database + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: "{{ db_latin1_name }}" + encoding: latin1 + target: "{{ latin1_file1 }}" + state: dump + register: dump_result + +- assert: + that: + - result is changed + +- name: state dump - file name should exist + file: + name: '{{ latin1_file1 }}' + state: file + +- name: od the file and check of latin1 encoded string is present + shell: grep -a 47 {{ latin1_file1 }} | od -c |grep "A m 351 d 351 e B 364\|A m 303 251 d 303 251 e B 303" + +- name: Dropping {{ db_latin1_name }} database + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_latin1_name }}' + state: absent + +- name: Importing the latin1 mysql script + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + state: import + encoding: latin1 + name: '{{ db_latin1_name }}' + target: "{{ latin1_file1 }}" + +- assert: + that: + - result is changed + +- name: check encoding of table + shell: "{{ mysql_command }} {{ db_latin1_name }} -e \"SHOW FULL COLUMNS FROM testlatin1\"" + register: output + failed_when: '"latin1_swedish_ci" not in output.stdout' + +- name: remove database + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_latin1_name }}' + state: absent diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/tasks/issue-28.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/tasks/issue-28.yml new file mode 100644 index 00000000..871e92d0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/tasks/issue-28.yml @@ -0,0 +1,81 @@ +--- +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + + block: + + # ============================================================ + - shell: pip show pymysql | awk '/Version/ {print $2}' + register: pymysql_version + + - name: get server certificate + copy: + content: "{{ lookup('pipe', \"openssl s_client -starttls mysql -connect localhost:3307 -showcerts 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'\") }}" + dest: /tmp/cert.pem + delegate_to: localhost + + - name: Drop mysql user if exists + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + state: absent + ignore_errors: yes + + - name: create user with ssl requirement + mysql_user: + <<: *mysql_params + name: "{{ user_name_1 }}" + password: "{{ user_password_1 }}" + priv: '*.*:ALL,GRANT' + tls_requires: + SSL: + + - name: attempt connection with newly created user (expect failure) + mysql_db: + name: '{{ db_name }}' + state: absent + login_user: '{{ user_name_1 }}' + login_password: '{{ user_password_1 }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + ca_cert: /tmp/cert.pem + register: result + ignore_errors: yes + + - assert: + that: + - result is failed + when: pymysql_version.stdout != "" + + - assert: + that: + - result is succeeded + when: pymysql_version.stdout == "" + + - name: attempt connection with newly created user ignoring hostname + mysql_db: + name: '{{ db_name }}' + state: absent + login_user: '{{ user_name_1 }}' + login_password: '{{ user_password_1 }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + ca_cert: /tmp/cert.pem + check_hostname: no + register: result + ignore_errors: yes + + - assert: + that: + - result is succeeded or 'pymysql >= 0.7.11 is required' in result.msg + + - name: Drop mysql user + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + host: 127.0.0.1 + state: absent diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/tasks/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/tasks/main.yml new file mode 100644 index 00000000..139d5bbc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/tasks/main.yml @@ -0,0 +1,326 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# test code for the mysql_db module +# (c) 2014, Wayne Rosario <wrosario@ansible.com> + +# This file is part of Ansible +# +# Ansible 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 3 of the License, or +# (at your option) any later version. +# +# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>. + +# ============================================================ +- name: alias mysql command to include default options + set_fact: + mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} -P{{ mysql_primary_port }} --protocol=tcp" + +- name: remove database if it exists + command: > + "{{ mysql_command }} -sse 'drop database {{ db_name }}'" + ignore_errors: True + +- name: make sure the test database is not there + command: "{{ mysql_command }} {{ db_name }}" + register: mysql_db_check + failed_when: "'1049' not in mysql_db_check.stderr" + +- name: test state=present for a database name (expect changed=true) + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name }}' + state: present + register: result + +- name: assert output message that database exist + assert: + that: + - result is changed + - result.db == '{{ db_name }}' + - result.executed_commands == ["CREATE DATABASE `{{ db_name }}`"] + +- name: run command to test state=present for a database name (expect db_name in stdout) + command: "{{ mysql_command }} -e \"show databases like '{{ db_name }}'\"" + register: result + +- name: assert database exist + assert: + that: + - "'{{ db_name }}' in result.stdout" + +# ============================================================ +- name: test state=absent for a database name (expect changed=true) + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name }}' + state: absent + register: result + +- name: assert output message that database does not exist + assert: + that: + - result is changed + - result.db == '{{ db_name }}' + - result.executed_commands == ["DROP DATABASE `{{ db_name }}`"] + +- name: run command to test state=absent for a database name (expect db_name not in stdout) + command: "{{ mysql_command }} -e \"show databases like '{{ db_name }}'\"" + register: result + +- name: assert database does not exist + assert: + that: + - "'{{ db_name }}' not in result.stdout" + +# ============================================================ +- name: test mysql_db encoding param not valid - issue 8075 + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: datanotvalid + state: present + encoding: notvalid + register: result + ignore_errors: true + +- name: assert test mysql_db encoding param not valid - issue 8075 (failed=true) + assert: + that: + - "result.failed == true" + - "'Traceback' not in result.msg" + - "'Unknown character set' in result.msg" + +# ============================================================ +- name: test mysql_db using a valid encoding utf8 (expect changed=true) + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: 'en{{ db_name }}' + state: present + encoding: utf8 + register: result + +- name: assert output message created a database + assert: + that: + - result is changed + - result.executed_commands == ["CREATE DATABASE `en{{ db_name }}` CHARACTER SET 'utf8'"] + +- name: test database was created + command: "{{ mysql_command }} -e \"SHOW CREATE DATABASE en{{ db_name }}\"" + register: result + +- name: assert created database is of encoding utf8 + assert: + that: + - "'utf8' in result.stdout" + +- name: remove database + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: 'en{{ db_name }}' + state: absent + +# ============================================================ +- name: test mysql_db using valid encoding binary (expect changed=true) + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: 'en{{ db_name }}' + state: present + encoding: binary + register: result + +- name: assert output message that database was created + assert: + that: + - result is changed + - result.executed_commands == ["CREATE DATABASE `en{{ db_name }}` CHARACTER SET 'binary'"] + +- name: run command to test database was created + command: "{{ mysql_command }} -e \"SHOW CREATE DATABASE en{{ db_name }}\"" + register: result + +- name: assert created database is of encoding binary + assert: + that: + - "'binary' in result.stdout" + +- name: remove database + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: 'en{{ db_name }}' + state: absent + +# ============================================================ +- name: create user1 to access database dbuser1 + mysql_user: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: user1 + password: 'Hfd6fds^dfA8Ga' + priv: '*.*:ALL' + state: present + +- name: create database dbuser1 using user1 + mysql_db: + login_user: user1 + login_password: 'Hfd6fds^dfA8Ga' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_user1 }}' + state: present + register: result + +- name: assert output message that database was created + assert: + that: + - "result.changed == true" + +- name: run command to test database was created using user1 + command: "{{ mysql_command }} -e \"show databases like '{{ db_user1 }}'\"" + register: result + +- name: assert database exist + assert: + that: + - "'{{ db_user1 }}' in result.stdout" + +# ============================================================ +- name: create user2 to access database with privilege select only + mysql_user: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: user2 + password: 'kjsfd&F7safjad' + priv: '*.*:SELECT' + state: present + +- name: create database dbuser2 using user2 with no privilege to create (expect failed=true) + mysql_db: + login_user: user2 + login_password: 'kjsfd&F7safjad' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_user2 }}' + state: present + register: result + ignore_errors: true + +- name: assert output message that database was not created using dbuser2 + assert: + that: + - "result.failed == true" + - "'Access denied' in result.msg" + +- name: run command to test that database was not created + command: "{{ mysql_command }} -e \"show databases like '{{ db_user2 }}'\"" + register: result + +- name: assert database does not exist + assert: + that: + - "'{{ db_user2 }}' not in result.stdout" + +# ============================================================ +- name: delete database using user2 with no privilege to delete (expect failed=true) + mysql_db: + login_user: user2 + login_password: 'kjsfd&F7safjad' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_user1 }}' + state: absent + register: result + ignore_errors: true + +- name: assert output message that database was not deleted using dbuser2 + assert: + that: + - "result.failed == true" + - "'Access denied' in result.msg" + +- name: run command to test database was not deleted + command: "{{ mysql_command }} -e \"show databases like '{{ db_user1 }}'\"" + register: result + +- name: assert database still exist + assert: + that: + - "'{{ db_user1 }}' in result.stdout" + +# ============================================================ +- name: delete database using user1 with all privilege to delete a database (expect changed=true) + mysql_db: + login_user: user1 + login_password: 'Hfd6fds^dfA8Ga' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_user1 }}' + state: absent + register: result + ignore_errors: true + +- name: assert output message that database was deleted using user1 + assert: + that: + - result is changed + - result.executed_commands == ["DROP DATABASE `{{ db_user1 }}`"] + +- name: run command to test database was deleted using user1 + command: "{{ mysql_command }} -e \"show databases like '{{ db_name }}'\"" + register: result + +- name: assert database does not exist + assert: + that: + - "'{{ db_user1 }}' not in result.stdout" + +# ============================================================ +- include: state_dump_import.yml format_type=sql file=dbdata.sql format_msg_type=ASCII file2=dump2.sql file3=dump3.sql file4=dump4.sql + +- include: state_dump_import.yml format_type=gz file=dbdata.gz format_msg_type=gzip file2=dump2.gz file3=dump3.gz file4=dump4.gz + +- include: state_dump_import.yml format_type=bz2 file=dbdata.bz2 format_msg_type=bzip2 file2=dump2.bz2 file3=dump3.bz2 file4=dump4.bz2 + +- include: multi_db_create_delete.yml + +- include: encoding_dump_import.yml file=latin1.sql format_msg_type=ASCII + +- include: config_overrides_defaults.yml + when: ansible_python.version_info[0] >= 3 + +- include: issue-28.yml diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/tasks/multi_db_create_delete.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/tasks/multi_db_create_delete.yml new file mode 100644 index 00000000..6bada1c5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/tasks/multi_db_create_delete.yml @@ -0,0 +1,626 @@ +# Copyright (c) 2019, Pratik Gadiya <pratikgadiya1@gmail.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +- set_fact: + db1_name: "database1" + db2_name: "database2" + db3_name: "database3" + db4_name: "database4" + db5_name: "database5" + dump1_file: "/tmp/dump1_file.sql" + dump2_file: "/tmp/all.sql" + +# ============================== CREATE TEST =============================== +# +# ========================================================================== +# Initial check - To confirm that database does not exist before executing check mode tasks +- name: run command to list databases like specified database name + command: "{{ mysql_command }} \"-e show databases like 'database%'\"" + register: mysql_result + +- name: assert that databases does not exist + assert: + that: + - "'{{ db1_name }}' not in mysql_result.stdout" + - "'{{ db2_name }}' not in mysql_result.stdout" + - "'{{ db3_name }}' not in mysql_result.stdout" + +# ========================================================================== +# Create multiple databases that does not exists (check mode) +- name: Create multiple databases that does not exists (check mode) + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: + - '{{ db1_name }}' + - '{{ db2_name }}' + - '{{ db3_name }}' + state: present + register: check_mode_result + check_mode: yes + +- name: assert successful completion of create database using check_mode since databases does not exist prior + assert: + that: + - check_mode_result.changed == true + +- name: run command to list databases like specified database name + command: "{{ mysql_command }} \"-e show databases like 'database%'\"" + register: mysql_result + +- name: assert that databases does not exist (since created via check mode) + assert: + that: + - "'{{ db1_name }}' not in mysql_result.stdout" + - "'{{ db2_name }}' not in mysql_result.stdout" + - "'{{ db3_name }}' not in mysql_result.stdout" + +# ========================================================================== +# Create multiple databases +- name: Create multiple databases + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: + - '{{ db1_name }}' + - '{{ db2_name }}' + - '{{ db3_name }}' + state: present + register: result + +- name: assert successful completion of create database + assert: + that: + - result.changed == true + - result.db_list == ['{{ db1_name }}', '{{ db2_name }}', '{{ db3_name }}'] + +- name: run command to list databases like specified database name + command: "{{ mysql_command }} \"-e show databases like 'database%'\"" + register: mysql_result + +- name: assert that databases exist after creation + assert: + that: + - "'{{ db1_name }}' in mysql_result.stdout" + - "'{{ db2_name }}' in mysql_result.stdout" + - "'{{ db3_name }}' in mysql_result.stdout" + +# ========================================================================= +# Recreate already existing databases (check mode) +- name: Recreate already existing databases (check mode) + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: + - '{{ db1_name }}' + - '{{ db2_name }}' + - '{{ db3_name }}' + state: present + register: check_mode_result + check_mode: yes + +- name: assert that recreation of existing databases does not make change (since recreated using check mode) + assert: + that: + - check_mode_result.changed == false + +- name: run command to list databases like specified database name + command: "{{ mysql_command }} \"-e show databases like 'database%'\"" + register: mysql_result + +- name: assert that databases exist (since performed recreation of existing databases via check mode) + assert: + that: + - "'{{ db1_name }}' in mysql_result.stdout" + - "'{{ db2_name }}' in mysql_result.stdout" + - "'{{ db3_name }}' in mysql_result.stdout" + +# ========================================================================== +# Recreate same databases +- name: Recreate multiple databases + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: + - '{{ db1_name }}' + - '{{ db2_name }}' + - '{{ db3_name }}' + state: present + register: result + +- name: assert that recreation of existing databases does not make change + assert: + that: + - result.changed == false + +- name: run command to list databases like specified database name + command: "{{ mysql_command }} \"-e show databases like 'database%'\"" + register: mysql_result + +- name: assert that databases does priorly exist + assert: + that: + - "'{{ db1_name }}' in mysql_result.stdout" + - "'{{ db2_name }}' in mysql_result.stdout" + - "'{{ db3_name }}' in mysql_result.stdout" + +# ========================================================================== +# Delete one of the databases (db2 here) +- name: Delete db2 database + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: + - '{{ db2_name }}' + state: absent + register: result + +- name: assert successful completion of deleting database + assert: + that: + - result.changed == true + +- name: run command to list databases like specified database name + command: "{{ mysql_command }} \"-e show databases like 'database%'\"" + register: mysql_result + +- name: assert that only db2 database does not exist + assert: + that: + - "'{{ db1_name }}' in mysql_result.stdout" + - "'{{ db2_name }}' not in mysql_result.stdout" + - "'{{ db3_name }}' in mysql_result.stdout" + +# ========================================================================= +# Recreate multiple databases in which few databases does not exists (check mode) +- name: Recreate multiple databases in which few databases does not exists (check mode) + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: + - '{{ db1_name }}' + - '{{ db2_name }}' + - '{{ db3_name }}' + state: present + register: check_mode_result + check_mode: yes + +- name: assert successful completion of recreation of partially existing database using check mode + assert: + that: + - check_mode_result.changed == true + +- name: run command to list databases like specified database name + command: "{{ mysql_command }} \"-e show databases like 'database%'\"" + register: mysql_result + +- name: assert that recreated non existing databases does not exist (since created via check mode) + assert: + that: + - "'{{ db1_name }}' in mysql_result.stdout" + - "'{{ db2_name }}' not in mysql_result.stdout" + - "'{{ db3_name }}' in mysql_result.stdout" + +# ========================================================================== +# Create multiple databases +- name: Create multiple databases + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: + - '{{ db1_name }}' + - '{{ db2_name }}' + - '{{ db3_name }}' + state: present + register: result + +- name: assert successful completion of create database + assert: + that: + - result.changed == true + +- name: run command to list databases like specified database name + command: "{{ mysql_command }} \"-e show databases like 'database%'\"" + register: mysql_result + +- name: assert that databases exist + assert: + that: + - "'{{ db1_name }}' in mysql_result.stdout" + - "'{{ db2_name }}' in mysql_result.stdout" + - "'{{ db3_name }}' in mysql_result.stdout" + +# ============================== DUMP TEST ================================= +# +# ========================================================================== +# Check that dump file does not exist +- name: Dump file does not exist + file: + name: '{{ dump1_file }}' + state: absent + +# ========================================================================== +# Dump existing databases (check mode) +- name: Dump existing databases (check mode) + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: + - '{{ db1_name }}' + - '{{ db3_name }}' + state: dump + target: '{{ dump1_file }}' + register: check_mode_dump_result + check_mode: yes + +- name: assert successful completion of dump operation using check mode + assert: + that: + - check_mode_dump_result.changed == true + +- name: run command to list databases like specified database name + command: "{{ mysql_command }} \"-e show databases like 'database%'\"" + register: mysql_result + +- name: assert that databases exist (check mode) + assert: + that: + - "'{{ db1_name }}' in mysql_result.stdout" + - "'{{ db2_name }}' in mysql_result.stdout" + - "'{{ db3_name }}' in mysql_result.stdout" + +- name: state dump - file name should not exist (since dumped via check mode) + file: + name: '{{ dump1_file }}' + state: absent + +# ========================================================================== +# Dump existing and non-existing databases (check mode) +- name: Dump existing and non-existing databases (check mode) + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: + - "{{ db1_name }}" + - "{{ db4_name }}" + - "{{ db3_name }}" + state: dump + target: "{{ dump1_file }}" + register: check_mode_dump_result + ignore_errors: True + check_mode: yes + +- name: assert that dump operation of existing and non existing databases does not make change (using check mode) + assert: + that: + - "'Cannot dump database' in check_mode_dump_result['msg']" + +- name: run command to list databases like specified database name + command: "{{ mysql_command }} \"-e show databases like 'database%'\"" + register: mysql_result + +- name: assert that databases exist (since check mode) + assert: + that: + - "'{{ db1_name }}' in mysql_result.stdout" + - "'{{ db2_name }}' in mysql_result.stdout" + - "'{{ db3_name }}' in mysql_result.stdout" + - "'{{ db4_name }}' not in mysql_result.stdout" + +- name: state dump - file name should not exist (since prior dump operation performed via check mode) + file: + name: '{{ dump1_file }}' + state: absent + +# ========================================================================== +# Dump non-existing databases (check mode) +- name: Dump non-existing databases (check mode) + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: + - "{{ db4_name }}" + - "{{ db5_name }}" + state: dump + target: "{{ dump1_file }}" + register: check_mode_dump_result + ignore_errors: True + check_mode: yes + +- name: assert successful completion of dump operation using check mode + assert: + that: + - "'Cannot dump database' in check_mode_dump_result['msg']" + +- name: run command to list databases like specified database name + command: "{{ mysql_command }} \"-e show databases like 'database%'\"" + register: mysql_result + +- name: assert that databases exist (since delete via check mode) + assert: + that: + - "'{{ db1_name }}' in mysql_result.stdout" + - "'{{ db2_name }}' in mysql_result.stdout" + - "'{{ db3_name }}' in mysql_result.stdout" + - "'{{ db4_name }}' not in mysql_result.stdout" + - "'{{ db5_name }}' not in mysql_result.stdout" + +- name: state dump - file name should not exist (since prior dump operation performed via check mode) + file: + name: '{{ dump1_file }}' + state: absent + +# ========================================================================== +# Dump existing databases +- name: Dump existing databases + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: + - '{{ db1_name }}' + - '{{ db2_name }}' + - '{{ db3_name }}' + state: dump + target: '{{ dump1_file }}' + register: dump_result + +- name: assert successful completion of dump operation + assert: + that: + - dump_result.changed == true + - dump_result.db_list == ['{{ db1_name }}', '{{ db2_name }}', '{{ db3_name }}'] + +- name: run command to list databases like specified database name + command: "{{ mysql_command }} \"-e show databases like 'database%'\"" + register: mysql_result + +- name: assert that databases exist + assert: + that: + - "'{{ db1_name }}' in mysql_result.stdout" + - "'{{ db2_name }}' in mysql_result.stdout" + - "'{{ db3_name }}' in mysql_result.stdout" + +- name: state dump - file name should exist + file: + name: '{{ dump1_file }}' + state: file + +- name: Check if db1 database create command is present in the dumped file + shell: "grep -i 'CREATE DATABASE.*`{{ db1_name }}`' {{ dump1_file }}" + +- name: Check if db2 database create command is present in the dumped file + shell: "grep -i 'CREATE DATABASE.*`{{ db2_name }}`' {{ dump1_file }}" + +- name: Check if db3 database create command is present in the dumped file + shell: "grep -i 'CREATE DATABASE.*`{{ db3_name }}`' {{ dump1_file }}" + +# ========================================================================== +# Dump all databases + +- name: state dump - dump2 file name should not exist + file: + name: '{{ dump2_file }}' + state: absent + +- name: Dump existing databases + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: all + state: dump + target: '{{ dump2_file }}' + register: dump_result + +- name: assert successful completion of dump operation + assert: + that: + - dump_result.changed == true + +- name: run command to list databases like specified database name + command: "{{ mysql_command }} \"-e show databases like 'database%'\"" + register: mysql_result + +- name: assert that databases exist + assert: + that: + - "'{{ db1_name }}' in mysql_result.stdout" + - "'{{ db2_name }}' in mysql_result.stdout" + - "'{{ db3_name }}' in mysql_result.stdout" + - "'{{ db4_name }}' not in mysql_result.stdout" + - "'{{ db5_name }}' not in mysql_result.stdout" + +- name: state dump - file name should exist + file: + name: '{{ dump2_file }}' + state: file + +# ============================ DELETE TEST ================================= +# +# ========================================================================== +# Delete multiple databases which already exists (check mode) +- name: Delete multiple databases which already exists (check mode) + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: + - '{{ db2_name }}' + - '{{ db3_name }}' + state: absent + register: check_mode_result + check_mode: yes + +- name: assert successful completion of delete databases which already exists using check mode + assert: + that: + - check_mode_result.changed == true + +- name: run command to test state=absent for a database name + command: "{{ mysql_command }} \"-e show databases like 'database%'\"" + register: mysql_result + +- name: assert that databases exist even after deleting (since deleted via check mode) + assert: + that: + - "'{{ db2_name }}' in mysql_result.stdout" + - "'{{ db3_name }}' in mysql_result.stdout" + +# ========================================================================== +# Delete multiple databases +- name: Delete multiple databases + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: + - '{{ db2_name }}' + - '{{ db3_name }}' + state: absent + register: result + +- name: assert successful completion of deleting database + assert: + that: + - result.changed == true + - result.db_list == ['{{ db2_name }}', '{{ db3_name }}'] + +- name: run command to list databases like specified database name + command: "{{ mysql_command }} \"-e show databases like 'database%'\"" + register: mysql_result + +- name: assert that databases does not exist + assert: + that: + - "'{{ db2_name }}' not in mysql_result.stdout" + - "'{{ db3_name }}' not in mysql_result.stdout" + +# ========================================================================== +# Delete non existing databases (check mode) +- name: Delete non existing databases (check mode) + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: + - '{{ db2_name }}' + - '{{ db4_name }}' + state: absent + register: check_mode_result + check_mode: yes + +- name: assert that deletion of non existing databases does not make change (using check mode) + assert: + that: + - check_mode_result.changed == false + +- name: run command to test state=absent for a database name + command: "{{ mysql_command }} \"-e show databases like 'database%'\"" + register: mysql_result + +- name: assert that databases does not exist since were deleted priorly (check mode) + assert: + that: + - "'{{ db2_name }}' not in mysql_result.stdout" + - "'{{ db4_name }}' not in mysql_result.stdout" + +# ========================================================================== +# Delete already deleted databases +- name: Delete already deleted databases + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: + - '{{ db2_name }}' + - '{{ db4_name }}' + state: absent + register: result + +- name: assert that deletion of non existing databases does not make change + assert: + that: + - result.changed == false + +- name: run command to list databases like specified database name + command: "{{ mysql_command }} \"-e show databases like 'database%'\"" + register: mysql_result + +- name: assert that databases does not exists + assert: + that: + - "'{{ db2_name }}' not in mysql_result.stdout" + - "'{{ db4_name }}' not in mysql_result.stdout" + +# ========================================================================== +# Delete all databases +- name: Delete all databases + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: + - '{{ db1_name }}' + - '{{ db2_name }}' + - '{{ db3_name }}' + - '{{ db4_name }}' + - '{{ db5_name }}' + state: absent + register: result + +- name: assert successful completion of deleting database + assert: + that: + - result.changed == true + +- name: run command to list databases like specified database name + command: "{{ mysql_command }} \"-e show databases like 'database%'\"" + register: mysql_result + +- name: assert that specific databases does not exist + assert: + that: + - "'{{ db1_name }}' not in mysql_result.stdout" + - "'{{ db2_name }}' not in mysql_result.stdout" + - "'{{ db3_name }}' not in mysql_result.stdout" + - "'{{ db4_name }}' not in mysql_result.stdout" + - "'{{ db5_name }}' not in mysql_result.stdout" + +- name: state dump - dump 1 file name should be removed + file: + name: '{{ dump1_file }}' + state: absent + +- name: state dump - dump 2 file name should be removed + file: + name: '{{ dump2_file }}' + state: absent diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml new file mode 100644 index 00000000..1de74395 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_db/tasks/state_dump_import.yml @@ -0,0 +1,459 @@ +# test code for state dump and import for mysql_db module +# (c) 2014, Wayne Rosario <wrosario@ansible.com> + +# This file is part of Ansible +# +# Ansible 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 3 of the License, or +# (at your option) any later version. +# +# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>. + +# ============================================================ +- set_fact: + db_file_name: "{{ tmp_dir }}/{{ file }}" + wrong_sql_file: "{{ tmp_dir }}/wrong.sql" + dump_file1: "{{ tmp_dir }}/{{ file2 }}" + dump_file2: "{{ tmp_dir }}/{{ file3 }}" + db_user: "test" + db_user_unsafe_password: "pass!word" + config_file: "/root/.my.cnf" + +- name: create custom config file + shell: 'echo "[client]" > {{ config_file }}' + +- name: create user for test unsafe_login_password parameter + mysql_user: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_user }}' + password: '{{ db_user_unsafe_password }}' + priv: '*.*:ALL' + state: present + +- name: state dump/import - create database + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name }}' + state: present + check_implicit_admin: yes + +- name: create database + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name2 }}' + state: present + check_implicit_admin: no + +- name: state dump/import - create table department + command: "{{ mysql_command }} {{ db_name }} \"-e create table department(id int, name varchar(100))\"" + +- name: state dump/import - create table employee + command: "{{ mysql_command }} {{ db_name }} \"-e create table employee(id int, name varchar(100))\"" + +- name: state dump/import - insert data into table employee + command: "{{ mysql_command }} {{ db_name }} \"-e insert into employee value(47,'Joe Smith')\"" + +- name: state dump/import - insert data into table department + command: "{{ mysql_command }} {{ db_name }} \"-e insert into department value(2,'Engineering')\"" + +- name: state dump/import - file name should not exist + file: + name: '{{ db_file_name }}' + state: absent + +- name: database dump file1 should not exist + file: + name: '{{ dump_file1 }}' + state: absent + +- name: database dump file2 should not exist + file: + name: '{{ dump_file2 }}' + state: absent + +- name: state dump without department table. + mysql_db: + login_user: '{{ db_user }}' + login_password: '{{ db_user_unsafe_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + unsafe_login_password: yes + name: '{{ db_name }}' + state: dump + target: '{{ db_file_name }}' + ignore_tables: + - "{{ db_name }}.department" + force: yes + master_data: 1 + skip_lock_tables: yes + dump_extra_args: --skip-triggers + config_file: '{{ config_file }}' + restrict_config_file: yes + check_implicit_admin: no + register: result + +- name: assert successful completion of dump operation + assert: + that: + - result is changed + - result.executed_commands[0] is search("mysqldump --defaults-file={{ config_file }} --user={{ db_user }} --password=\*\*\*\*\*\*\*\* --force --host=127.0.0.1 --port={{ mysql_primary_port }} {{ db_name }} --skip-lock-tables --quick --ignore-table={{ db_name }}.department --master-data=1 --skip-triggers") + +- name: state dump/import - file name should exist + file: + name: '{{ db_file_name }}' + state: file + +- name: state dump with multiple databases in comma separated form. + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: "{{ db_name }},{{ db_name2 }}" + state: dump + target: '{{ dump_file1 }}' + check_implicit_admin: yes + register: dump_result1 + +- name: assert successful completion of dump operation (with multiple databases in comma separated form) + assert: + that: + - dump_result1 is changed + - dump_result1.executed_commands[0] is search(" --user=root --password=\*\*\*\*\*\*\*\*") + +- name: state dump - dump file1 should exist + file: + name: '{{ dump_file1 }}' + state: file + +- name: state dump with multiple databases in list form via check_mode + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: + - '{{ db_name }}' + - '{{ db_name2 }}' + state: dump + target: '{{ dump_file2 }}' + register: dump_result + check_mode: yes + +- name: assert successful completion of dump operation (with multiple databases in list form) via check mode + assert: + that: + - "dump_result.changed == true" + +- name: database dump file2 should not exist + stat: + path: '{{ dump_file2 }}' + register: stat_result + +- name: assert that check_mode does not create dump file for databases + assert: + that: + - stat_result.stat.exists is defined and not stat_result.stat.exists + +- name: state dump with multiple databases in list form. + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: + - '{{ db_name }}' + - '{{ db_name2 }}' + state: dump + target: '{{ dump_file2 }}' + register: dump_result2 + +- name: assert successful completion of dump operation (with multiple databases in list form) + assert: + that: + - "dump_result2.changed == true" + +- name: state dump - dump file2 should exist + file: + name: '{{ dump_file2 }}' + state: file + +- name: state dump/import - remove database + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name }}' + state: absent + +- name: remove database + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name2 }}' + state: absent + +- name: test state=import to restore the database of type {{ format_type }} (expect changed=true) + mysql_db: + login_user: '{{ db_user }}' + login_password: '{{ db_user_unsafe_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + unsafe_login_password: yes + name: '{{ db_name }}' + state: import + target: '{{ db_file_name }}' + use_shell: yes + register: result + +- name: show the tables + command: "{{ mysql_command }} {{ db_name }} \"-e show tables\"" + register: result + +- name: assert that the department table is absent. + assert: + that: + - "'department' not in result.stdout" + +- name: test state=import to restore a database from multiple database dumped file1 + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name2 }}' + state: import + target: '{{ dump_file1 }}' + use_shell: no + register: import_result + +- name: assert output message restored a database from dump file1 + assert: + that: + - "import_result.changed == true" + +- name: remove database + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name2 }}' + state: absent + +- name: run command to list databases + command: "{{ mysql_command }} \"-e show databases like 'data%'\"" + register: mysql_result + +- name: assert that db_name2 database does not exist + assert: + that: + - "'{{ db_name2 }}' not in mysql_result.stdout" + +- name: test state=import to restore a database from dumped file2 (check mode) + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name2 }}' + state: import + target: '{{ dump_file2 }}' + register: check_import_result + check_mode: yes + +- name: assert output message restored a database from dump file2 (check mode) + assert: + that: + - "check_import_result.changed == true" + +- name: run command to list databases + command: "{{ mysql_command }} \"-e show databases like 'data%'\"" + register: mysql_result + +- name: assert that db_name2 database does not exist (check mode) + assert: + that: + - "'{{ db_name2 }}' not in mysql_result.stdout" + +- name: test state=import to restore a database from multiple database dumped file2 + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name2 }}' + state: import + target: '{{ dump_file2 }}' + register: import_result2 + +- name: assert output message restored a database from dump file2 + assert: + that: + - import_result2.changed == true + - import_result2.db_list == ['{{ db_name2 }}'] + +- name: run command to list databases + command: "{{ mysql_command }} \"-e show databases like 'data%'\"" + register: mysql_result + +- name: assert that db_name2 database does exist after import + assert: + that: + - "'{{ db_name2 }}' in mysql_result.stdout" + +- name: test state=dump to backup the database of type {{ format_type }} (expect changed=true) + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name }}' + state: dump + target: '{{ db_file_name }}' + register: result + +- name: assert output message backup the database + assert: + that: + - "result.changed == true" + - "result.db =='{{ db_name }}'" + +# - name: assert database was backed up successfully +# command: "file {{ db_file_name }}" +# register: result +# +# - name: assert file format type +# assert: +# that: +# - "'{{ format_msg_type }}' in result.stdout" + +- name: update database table employee + command: "{{ mysql_command }} {{ db_name }} \"-e update employee set name='John Doe' where id=47\"" + +- name: test state=import to restore the database of type {{ format_type }} (expect changed=true) + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name }}' + state: import + target: '{{ db_file_name }}' + register: result + +- name: assert output message restore the database + assert: + that: + - "result.changed == true" + +- name: select data from table employee + command: "{{ mysql_command }} {{ db_name }} \"-e select * from employee\"" + register: result + +- name: assert data in database is from the restore database + assert: + that: + - "'47' in result.stdout" + - "'Joe Smith' in result.stdout" + +########################## +# Test ``force`` parameter +########################## + +- name: create wrong sql file + shell: echo 'CREATE TABLE hello (id int); CREATE ELBAT ehlo (int id);' >> '{{ wrong_sql_file }}' + +- name: try to import without force parameter, must fail + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name }}' + state: import + target: '{{ wrong_sql_file }}' + force: no + register: result + ignore_errors: yes + +- assert: + that: + - result.failed == true + +- name: try to import with force parameter + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name }}' + state: import + target: '{{ wrong_sql_file }}' + force: yes + register: result + +- assert: + that: + - result is changed + +########## +# Clean up +########## + +- name: remove database name + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name }}' + state: absent + +- name: remove database + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name2 }}' + state: absent + +- name: remove file name + file: + name: '{{ db_file_name }}' + state: absent + +- name: remove file name + file: + name: '{{ wrong_sql_file }}' + state: absent + +- name: remove dump file1 + file: + name: '{{ dump_file1 }}' + state: absent + +- name: remove dump file2 + file: + name: '{{ dump_file2 }}' + state: absent diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_info/defaults/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_info/defaults/main.yml new file mode 100644 index 00000000..e1b932cc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_info/defaults/main.yml @@ -0,0 +1,11 @@ +--- +# defaults file for test_mysql_info +mysql_user: root +mysql_password: msandbox +mysql_host: 127.0.0.1 +mysql_primary_port: 3307 + +db_name: data + +user_name_1: 'db_user1' +user_password_1: 'gadfFDSdtTU^Sdfuj' diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_info/meta/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_info/meta/main.yml new file mode 100644 index 00000000..a7ace5d1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_info/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - setup_mysql + - setup_remote_tmp_dir diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_info/tasks/issue-28.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_info/tasks/issue-28.yml new file mode 100644 index 00000000..955683d4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_info/tasks/issue-28.yml @@ -0,0 +1,78 @@ +--- +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + + block: + + # ============================================================ + - shell: pip show pymysql | awk '/Version/ {print $2}' + register: pymysql_version + + - name: get server certificate + copy: + content: "{{ lookup('pipe', \"openssl s_client -starttls mysql -connect localhost:3307 -showcerts 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'\") }}" + dest: /tmp/cert.pem + delegate_to: localhost + + - name: Drop mysql user if exists + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + state: absent + ignore_errors: yes + + - name: create user with ssl requirement + mysql_user: + <<: *mysql_params + name: "{{ user_name_1 }}" + password: "{{ user_password_1 }}" + tls_requires: + SSL: + + - name: attempt connection with newly created user (expect failure) + mysql_info: + filter: version + login_user: '{{ user_name_1 }}' + login_password: '{{ user_password_1 }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + ca_cert: /tmp/cert.pem + register: result + ignore_errors: yes + + - assert: + that: + - result is failed + when: pymysql_version.stdout != "" + + - assert: + that: + - result is succeeded + when: pymysql_version.stdout == "" + + - name: attempt connection with newly created user ignoring hostname + mysql_info: + filter: version + login_user: '{{ user_name_1 }}' + login_password: '{{ user_password_1 }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + ca_cert: /tmp/cert.pem + check_hostname: no + register: result + ignore_errors: yes + + - assert: + that: + - result is succeeded or 'pymysql >= 0.7.11 is required' in result.msg + + - name: Drop mysql user + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + host: 127.0.0.1 + state: absent diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_info/tasks/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_info/tasks/main.yml new file mode 100644 index 00000000..13ddbcb2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_info/tasks/main.yml @@ -0,0 +1,193 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Test code for mysql_info module +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +################### +# Prepare for tests +# + +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + + block: + + # Create default MySQL config file with credentials + - name: mysql_info - create default config file + template: + src: my.cnf.j2 + dest: /root/.my.cnf + mode: '0400' + + # Create non-default MySQL config file with credentials + - name: mysql_info - create non-default config file + template: + src: my.cnf.j2 + dest: /root/non-default_my.cnf + mode: '0400' + + ############### + # Do tests + + # Access by default cred file + - name: mysql_info - collect default cred file + mysql_info: + login_user: '{{ mysql_user }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + register: result + + - assert: + that: + - result.changed == false + - result.version != {} + - result.settings != {} + - result.global_status != {} + - result.databases != {} + - result.engines != {} + - result.users != {} + + # Access by non-default cred file + - name: mysql_info - check non-default cred file + mysql_info: + login_user: '{{ mysql_user }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + config_file: /root/non-default_my.cnf + register: result + + - assert: + that: + - result.changed == false + - result.version != {} + + # Remove cred files + - name: mysql_info - remove cred files + file: + path: '{{ item }}' + state: absent + with_items: + - /root/.my.cnf + - /root/non-default_my.cnf + + # Access with password + - name: mysql_info - check access with password + mysql_info: + <<: *mysql_params + register: result + + - assert: + that: + - result.changed == false + - result.version != {} + + # Test excluding + - name: Collect all info except settings and users + mysql_info: + <<: *mysql_params + filter: '!settings,!users' + register: result + + - assert: + that: + - result.changed == false + - result.version != {} + - result.global_status != {} + - result.databases != {} + - result.engines != {} + - result.settings is not defined + - result.users is not defined + + # Test including + - name: Collect info only about version and databases + mysql_info: + <<: *mysql_params + filter: + - version + - databases + register: result + + - assert: + that: + - result.changed == false + - result.version != {} + - result.databases != {} + - result.engines is not defined + - result.settings is not defined + - result.global_status is not defined + - result.users is not defined + + # Test exclude_fields: db_size + # 'unsupported' element is passed to check that an unsupported value + # won't break anything (will be ignored regarding to the module's documentation). + - name: Collect info about databases excluding their sizes + mysql_info: + <<: *mysql_params + filter: + - databases + exclude_fields: + - db_size + - unsupported + register: result + + - assert: + that: + - result.changed == false + - result.databases != {} + - result.databases.mysql == {} + + ######################################################## + # Issue #65727, empty databases must be in returned dict + # + - name: Create empty database acme + mysql_db: + <<: *mysql_params + name: acme + + - name: Collect info about databases + mysql_info: + <<: *mysql_params + filter: + - databases + return_empty_dbs: true + register: result + + # Check acme is in returned dict + - assert: + that: + - result.changed == false + - result.databases.acme.size == 0 + - result.databases.mysql != {} + + - name: Collect info about databases excluding their sizes + mysql_info: + <<: *mysql_params + filter: + - databases + exclude_fields: + - db_size + return_empty_dbs: true + register: result + + # Check acme is in returned dict + - assert: + that: + - result.changed == false + - result.databases.acme == {} + - result.databases.mysql == {} + + - name: Remove acme database + mysql_db: + <<: *mysql_params + name: acme + state: absent + + - include: issue-28.yml diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_info/templates/my.cnf.j2 b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_info/templates/my.cnf.j2 new file mode 100644 index 00000000..7d159a21 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_info/templates/my.cnf.j2 @@ -0,0 +1,5 @@ +[client] +user={{ mysql_user }} +password={{ mysql_password }} +host={{ mysql_host }} +port={{ mysql_primary_port }} diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_query/defaults/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_query/defaults/main.yml new file mode 100644 index 00000000..51a3bd7f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_query/defaults/main.yml @@ -0,0 +1,13 @@ +mysql_user: root +mysql_password: msandbox +mysql_primary_port: 3307 + +db_name: data +test_db: testdb +test_table1: test1 +test_table2: test2 +test_table3: test3 +test_script_path: /tmp/test.sql + +user_name_1: 'db_user1' +user_password_1: 'gadfFDSdtTU^Sdfuj' diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_query/meta/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_query/meta/main.yml new file mode 100644 index 00000000..ce08dc4a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_query/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: +- setup_mysql diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_query/tasks/issue-28.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_query/tasks/issue-28.yml new file mode 100644 index 00000000..e2b51a6b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_query/tasks/issue-28.yml @@ -0,0 +1,78 @@ +--- +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + + block: + + # ============================================================ + - shell: pip show pymysql | awk '/Version/ {print $2}' + register: pymysql_version + + - name: get server certificate + copy: + content: "{{ lookup('pipe', \"openssl s_client -starttls mysql -connect localhost:3307 -showcerts 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'\") }}" + dest: /tmp/cert.pem + delegate_to: localhost + + - name: Drop mysql user if exists + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + state: absent + ignore_errors: yes + + - name: create user with ssl requirement + mysql_user: + <<: *mysql_params + name: "{{ user_name_1 }}" + password: "{{ user_password_1 }}" + tls_requires: + SSL: + + - name: attempt connection with newly created user (expect failure) + mysql_query: + query: 'SHOW DATABASES' + login_user: '{{ user_name_1 }}' + login_password: '{{ user_password_1 }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + ca_cert: /tmp/cert.pem + register: result + ignore_errors: yes + + - assert: + that: + - result is failed + when: pymysql_version.stdout != "" + + - assert: + that: + - result is succeeded + when: pymysql_version.stdout == "" + + - name: attempt connection with newly created user ignoring hostname + mysql_query: + query: 'SHOW DATABASES' + login_user: '{{ user_name_1 }}' + login_password: '{{ user_password_1 }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + ca_cert: /tmp/cert.pem + check_hostname: no + register: result + ignore_errors: yes + + - assert: + that: + - result is succeeded or 'pymysql >= 0.7.11 is required' in result.msg + + - name: Drop mysql user + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + host: 127.0.0.1 + state: absent diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_query/tasks/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_query/tasks/main.yml new file mode 100644 index 00000000..6d17308f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_query/tasks/main.yml @@ -0,0 +1,9 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# mysql_query module initial CI tests +- import_tasks: mysql_query_initial.yml + +- include: issue-28.yml diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml new file mode 100644 index 00000000..b01de55b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_query/tasks/mysql_query_initial.yml @@ -0,0 +1,301 @@ +# Test code for mysql_query module +# Copyright: (c) 2020, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + + block: + + - name: Create db {{ test_db }} + mysql_query: + <<: *mysql_params + query: 'CREATE DATABASE {{ test_db }}' + register: result + + - assert: + that: + - result is changed + - result.executed_queries == ['CREATE DATABASE {{ test_db }}'] + + - name: Create {{ test_table1 }} + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: 'CREATE TABLE {{ test_table1 }} (id int)' + register: result + + - assert: + that: + - result is changed + - result.executed_queries == ['CREATE TABLE {{ test_table1 }} (id int)'] + + - name: Insert test data + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: + - 'INSERT INTO {{ test_table1 }} VALUES (1), (2)' + - 'INSERT INTO {{ test_table1 }} VALUES (3)' + single_transaction: yes + register: result + + - assert: + that: + - result is changed + - result.rowcount == [2, 1] + - result.executed_queries == ['INSERT INTO {{ test_table1 }} VALUES (1), (2)', 'INSERT INTO {{ test_table1 }} VALUES (3)'] + + - name: Check data in {{ test_table1 }} + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: 'SELECT * FROM {{ test_table1 }}' + register: result + + - assert: + that: + - result is not changed + - result.executed_queries == ['SELECT * FROM {{ test_table1 }}'] + - result.rowcount == [3] + - result.query_result[0][0].id == 1 + - result.query_result[0][1].id == 2 + - result.query_result[0][2].id == 3 + + - name: Check data in {{ test_table1 }} using positional args + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: 'SELECT * FROM {{ test_table1 }} WHERE id = %s' + positional_args: + - 1 + register: result + + - assert: + that: + - result is not changed + - result.executed_queries == ["SELECT * FROM {{ test_table1 }} WHERE id = 1"] + - result.rowcount == [1] + - result.query_result[0][0].id == 1 + + - name: Check data in {{ test_table1 }} using named args + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: 'SELECT * FROM {{ test_table1 }} WHERE id = %(some_id)s' + named_args: + some_id: 1 + register: result + + - assert: + that: + - result is not changed + - result.executed_queries == ["SELECT * FROM {{ test_table1 }} WHERE id = 1"] + - result.rowcount == [1] + - result.query_result[0][0].id == 1 + + - name: Update data in {{ test_table1 }} + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: 'UPDATE {{ test_table1 }} SET id = %(new_id)s WHERE id = %(current_id)s' + named_args: + current_id: 1 + new_id: 0 + register: result + + - assert: + that: + - result is changed + - result.executed_queries == ['UPDATE {{ test_table1 }} SET id = 0 WHERE id = 1'] + - result.rowcount == [1] + + - name: Check the prev update - row with value 1 does not exist anymore + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: 'SELECT * FROM {{ test_table1 }} WHERE id = %(some_id)s' + named_args: + some_id: 1 + register: result + + - assert: + that: + - result is not changed + - result.executed_queries == ['SELECT * FROM {{ test_table1 }} WHERE id = 1'] + - result.rowcount == [0] + + - name: Check the prev update - row with value - exist + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: 'SELECT * FROM {{ test_table1 }} WHERE id = %(some_id)s' + named_args: + some_id: 0 + register: result + + - assert: + that: + - result is not changed + - result.executed_queries == ['SELECT * FROM {{ test_table1 }} WHERE id = 0'] + - result.rowcount == [1] + + - name: Update data in {{ test_table1 }} again + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: 'UPDATE {{ test_table1 }} SET id = %(new_id)s WHERE id = %(current_id)s' + named_args: + current_id: 1 + new_id: 0 + register: result + + - assert: + that: + - result is not changed + - result.executed_queries == ['UPDATE {{ test_table1 }} SET id = 0 WHERE id = 1'] + - result.rowcount == [0] + + - name: Delete data from {{ test_table1 }} + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: + - 'DELETE FROM {{ test_table1 }} WHERE id = 0' + - 'SELECT * FROM {{ test_table1 }} WHERE id = 0' + register: result + + - assert: + that: + - result is changed + - result.executed_queries == ['DELETE FROM {{ test_table1 }} WHERE id = 0', 'SELECT * FROM {{ test_table1 }} WHERE id = 0'] + - result.rowcount == [1, 0] + + - name: Delete data from {{ test_table1 }} again + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: 'DELETE FROM {{ test_table1 }} WHERE id = 0' + register: result + + - assert: + that: + - result is not changed + - result.executed_queries == ['DELETE FROM {{ test_table1 }} WHERE id = 0'] + - result.rowcount == [0] + + - name: Truncate {{ test_table1 }} + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: + - 'TRUNCATE {{ test_table1 }}' + - 'SELECT * FROM {{ test_table1 }}' + register: result + + - assert: + that: + - result is changed + - result.executed_queries == ['TRUNCATE {{ test_table1 }}', 'SELECT * FROM {{ test_table1 }}'] + - result.rowcount == [0, 0] + + - name: Rename {{ test_table1 }} + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: 'RENAME TABLE {{ test_table1 }} TO {{ test_table2 }}' + register: result + + - assert: + that: + - result is changed + - result.executed_queries == ['RENAME TABLE {{ test_table1 }} TO {{ test_table2 }}'] + - result.rowcount == [0] + + - name: Check the prev rename + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: 'SELECT * FROM {{ test_table1 }}' + register: result + ignore_errors: yes + + - assert: + that: + - result.failed == true + + - name: Check the prev rename + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: 'SELECT * FROM {{ test_table2 }}' + register: result + + - assert: + that: + - result.rowcount == [0] + + - name: Create {{ test_table3 }} + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: 'CREATE TABLE {{ test_table3 }} (id int, story text)' + + - name: Add data to {{ test_table3 }} + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: "INSERT INTO {{ test_table3 }} (id, story) VALUES (1, 'first'), (2, 'second')" + + - name: Select from {{ test_table3 }} + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: 'SELECT id, story FROM {{ test_table3 }}' + register: result + + - assert: + that: + - result.rowcount == [2] + + - name: Pass wrong query type + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: {'this type is': 'wrong'} + register: result + ignore_errors: yes + + - assert: + that: + - result is failed + - result.msg is search('the query option value must be a string or list') + + - name: Pass wrong query element + mysql_query: + <<: *mysql_params + login_db: '{{ test_db }}' + query: + - 'SELECT now()' + - {'this type is': 'wrong'} + register: result + ignore_errors: yes + + - assert: + that: + - result is failed + - result.msg is search('the elements in query list must be strings') + + - name: Drop db {{ test_db }} + mysql_query: + <<: *mysql_params + query: 'DROP DATABASE {{ test_db }}' + register: result + + - assert: + that: + - result is changed + - result.executed_queries == ['DROP DATABASE {{ test_db }}'] diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/defaults/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/defaults/main.yml new file mode 100644 index 00000000..fefcf293 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/defaults/main.yml @@ -0,0 +1,17 @@ +mysql_user: root +mysql_password: msandbox +mysql_host: 127.0.0.1 +mysql_primary_port: 3307 +mysql_replica1_port: 3308 +mysql_replica2_port: 3309 + +test_db: test_db +test_table: test_table +test_master_delay: 60 +replication_user: replication_user +replication_pass: replication_pass +dump_path: /tmp/dump.sql +test_channel: test_channel-1 + +user_name_1: 'db_user1' +user_password_1: 'gadfFDSdtTU^Sdfuj' diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/meta/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/meta/main.yml new file mode 100644 index 00000000..36e111c3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: +- setup_mysql diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml new file mode 100644 index 00000000..c6668201 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/tasks/issue-28.yml @@ -0,0 +1,79 @@ +--- +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + + block: + + # ============================================================ + - shell: pip show pymysql | awk '/Version/ {print $2}' + register: pymysql_version + + - name: get server certificate + copy: + content: "{{ lookup('pipe', \"openssl s_client -starttls mysql -connect localhost:3307 -showcerts 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'\") }}" + dest: /tmp/cert.pem + delegate_to: localhost + + - name: Drop mysql user if exists + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + state: absent + ignore_errors: yes + + - name: create user with ssl requirement + mysql_user: + <<: *mysql_params + name: "{{ user_name_1 }}" + password: "{{ user_password_1 }}" + priv: '*.*:ALL,GRANT' + tls_requires: + SSL: + + - name: attempt connection with newly created user (expect failure) + mysql_replication: + mode: getmaster + login_user: '{{ user_name_1 }}' + login_password: '{{ user_password_1 }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + ca_cert: /tmp/cert.pem + register: result + ignore_errors: yes + + - assert: + that: + - result is failed + when: pymysql_version.stdout != "" + + - assert: + that: + - result is succeeded + when: pymysql_version.stdout == "" + + - name: attempt connection with newly created user ignoring hostname + mysql_replication: + mode: getmaster + login_user: '{{ user_name_1 }}' + login_password: '{{ user_password_1 }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + ca_cert: /tmp/cert.pem + check_hostname: no + register: result + ignore_errors: yes + + - assert: + that: + - result is succeeded or 'pymysql >= 0.7.11 is required' in result.msg + + - name: Drop mysql user + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + host: 127.0.0.1 + state: absent diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/tasks/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/tasks/main.yml new file mode 100644 index 00000000..239598a6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/tasks/main.yml @@ -0,0 +1,21 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Initial CI tests of mysql_replication module: +- import_tasks: mysql_replication_initial.yml + +# Tests of master_delay parameter: +- import_tasks: mysql_replication_master_delay.yml + +# Tests of channel parameter: +- import_tasks: mysql_replication_channel.yml + +# Tests of resetmaster mode: +- import_tasks: mysql_replication_resetmaster_mode.yml + +- include: issue-28.yml diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml new file mode 100644 index 00000000..cb6d23a9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml @@ -0,0 +1,127 @@ +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +- vars: + mysql_params: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + + block: + # Get master log file and log pos: + - name: Get master status + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_primary_port }}' + mode: getmaster + register: mysql_primary_status + + # Test changemaster mode: + - name: Run replication with channel + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica2_port }}' + mode: changemaster + master_host: '{{ mysql_host }}' + master_port: '{{ mysql_primary_port }}' + master_user: '{{ replication_user }}' + master_password: '{{ replication_pass }}' + master_log_file: '{{ mysql_primary_status.File }}' + master_log_pos: '{{ mysql_primary_status.Position }}' + channel: '{{ test_channel }}' + register: result + + - assert: + that: + - result is changed + - result.queries == ["CHANGE MASTER TO MASTER_HOST='{{ mysql_host }}',MASTER_USER='{{ replication_user }}',MASTER_PASSWORD='********',MASTER_PORT={{ mysql_primary_port }},MASTER_LOG_FILE='{{ mysql_primary_status.File }}',MASTER_LOG_POS={{ mysql_primary_status.Position }} FOR CHANNEL '{{ test_channel }}'"] + + # Test startslave mode: + - name: Start slave with channel + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica2_port }}' + mode: startslave + channel: '{{ test_channel }}' + register: result + + - assert: + that: + - result is changed + - result.queries == ["START SLAVE FOR CHANNEL '{{ test_channel }}'"] or result.queries == ["START REPLICA FOR CHANNEL '{{ test_channel }}'"] + + # Test getslave mode: + - name: Get standby status with channel + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica2_port }}' + mode: getslave + channel: '{{ test_channel }}' + register: slave_status + + - assert: + that: + - slave_status.Is_Slave == true + - slave_status.Master_Host == '{{ mysql_host }}' + - slave_status.Exec_Master_Log_Pos == mysql_primary_status.Position + - slave_status.Master_Port == {{ mysql_primary_port }} + - slave_status.Last_IO_Errno == 0 + - slave_status.Last_IO_Error == '' + - slave_status.Channel_Name == '{{ test_channel }}' + - slave_status is not changed + when: mysql8022_and_higher == false + + - assert: + that: + - slave_status.Is_Slave == true + - slave_status.Source_Host == '{{ mysql_host }}' + - slave_status.Exec_Source_Log_Pos == mysql_primary_status.Position + - slave_status.Source_Port == {{ mysql_primary_port }} + - slave_status.Last_IO_Errno == 0 + - slave_status.Last_IO_Error == '' + - slave_status.Channel_Name == '{{ test_channel }}' + - slave_status is not changed + when: mysql8022_and_higher == true + + + # Test stopslave mode: + - name: Stop slave with channel + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica2_port }}' + mode: stopslave + channel: '{{ test_channel }}' + register: result + + - assert: + that: + - result is changed + - result.queries == ["STOP SLAVE FOR CHANNEL '{{ test_channel }}'"] or result.queries == ["STOP REPLICA FOR CHANNEL '{{ test_channel }}'"] + + # Test reset + - name: Reset slave with channel + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica2_port }}' + mode: resetslave + channel: '{{ test_channel }}' + register: result + + - assert: + that: + - result is changed + - result.queries == ["RESET SLAVE FOR CHANNEL '{{ test_channel }}'"] or result.queries == ["RESET REPLICA FOR CHANNEL '{{ test_channel }}'"] + + # Test reset all + - name: Reset slave all with channel + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica2_port }}' + mode: resetslaveall + channel: '{{ test_channel }}' + register: result + + - assert: + that: + - result is changed + - result.queries == ["RESET SLAVE ALL FOR CHANNEL '{{ test_channel }}'"] or result.queries == ["RESET REPLICA ALL FOR CHANNEL '{{ test_channel }}'"] diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_initial.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_initial.yml new file mode 100644 index 00000000..e26dcd23 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_initial.yml @@ -0,0 +1,243 @@ +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +- vars: + mysql_params: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + + block: + - name: find out the database version + mysql_info: + <<: *mysql_params + login_port: '{{ mysql_primary_port }}' + filter: version + register: db + + - name: Set mysql8022_and_higher + set_fact: + mysql8022_and_higher: false + + - name: Set mysql8022_and_higher + set_fact: + mysql8022_and_higher: true + when: + - db.version.major > 8 or (db.version.major == 8 and db.version.minor > 0) or (db.version.major == 8 and db.version.minor == 0 and db.version.release >= 22) + + - name: alias mysql command to include default options + set_fact: + mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} --protocol=tcp" + + # Preparation: + - name: Create user for replication + shell: "echo \"CREATE USER '{{ replication_user }}'@'localhost' IDENTIFIED WITH mysql_native_password BY '{{ replication_pass }}'; GRANT REPLICATION SLAVE ON *.* TO '{{ replication_user }}'@'localhost';\" | {{ mysql_command }} -P{{ mysql_primary_port }}" + + - name: Create test database + mysql_db: + <<: *mysql_params + login_port: '{{ mysql_primary_port }}' + state: present + name: '{{ test_db }}' + + - name: Dump all databases from the primary + shell: 'mysqldump -u{{ mysql_user }} -p{{ mysql_password }} -h{{ mysql_host }} --protocol=tcp -P{{ mysql_primary_port }} --all-databases --ignore-table=mysql.innodb_index_stats --ignore-table=mysql.innodb_table_stats --master-data=2 > {{ dump_path }}' + + - name: Restore the dump to replica1 + shell: '{{ mysql_command }} -P{{ mysql_replica1_port }} < {{ dump_path }}' + + - name: Restore the dump to replica2 + shell: '{{ mysql_command }} -P{{ mysql_replica2_port }} < {{ dump_path }}' + + # Test getmaster mode: + - name: Get master status + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_primary_port }}' + mode: getmaster + register: mysql_primary_status + + - assert: + that: + - mysql_primary_status.Is_Master == true + - mysql_primary_status.Position != 0 + - mysql_primary_status is not changed + + # Test startslave fails without changemaster first. This needs fail_on_error + - name: Start slave and fail because master is not specified; failing on error as requested + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: startslave + fail_on_error: yes + register: result + ignore_errors: yes + + - assert: + that: + - result is failed + + # Test startslave doesn't fail if fail_on_error: no + - name: Start slave and fail without propagating it to ansible as we were asked not to + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: startslave + fail_on_error: no + register: result + + - assert: + that: + - result is not failed + + # Test startslave doesn't fail if there is no fail_on_error. + # This is suboptimal because nothing happens, but it's the old behavior. + - name: Start slave and fail without propagating it to ansible as previous versions did not fail on error + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: startslave + register: result + + - assert: + that: + - result is not failed + + # Test changemaster mode: + # master_ssl_ca will be set as '' to check the module's behaviour for #23976, + # must be converted to an empty string + - name: Run replication + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: changemaster + master_host: '{{ mysql_host }}' + master_port: '{{ mysql_primary_port }}' + master_user: '{{ replication_user }}' + master_password: '{{ replication_pass }}' + master_log_file: '{{ mysql_primary_status.File }}' + master_log_pos: '{{ mysql_primary_status.Position }}' + master_ssl_ca: '' + register: result + + - assert: + that: + - result is changed + - result.queries == ["CHANGE MASTER TO MASTER_HOST='{{ mysql_host }}',MASTER_USER='{{ replication_user }}',MASTER_PASSWORD='********',MASTER_PORT={{ mysql_primary_port }},MASTER_LOG_FILE='{{ mysql_primary_status.File }}',MASTER_LOG_POS={{ mysql_primary_status.Position }},MASTER_SSL_CA=''"] + + # Test startslave mode: + - name: Start slave + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: startslave + register: result + + - assert: + that: + - result is changed + - result.queries == ["START SLAVE"] or result.queries == ["START REPLICA"] + + # Test getslave mode: + - name: Get standby status + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: getslave + register: slave_status + + - assert: + that: + - slave_status.Is_Slave == true + - slave_status.Master_Host == '{{ mysql_host }}' + - slave_status.Exec_Master_Log_Pos == mysql_primary_status.Position + - slave_status.Master_Port == {{ mysql_primary_port }} + - slave_status.Last_IO_Errno == 0 + - slave_status.Last_IO_Error == '' + - slave_status is not changed + when: mysql8022_and_higher == false + + - assert: + that: + - slave_status.Is_Slave == true + - slave_status.Source_Host == '{{ mysql_host }}' + - slave_status.Exec_Source_Log_Pos == mysql_primary_status.Position + - slave_status.Source_Port == {{ mysql_primary_port }} + - slave_status.Last_IO_Errno == 0 + - slave_status.Last_IO_Error == '' + - slave_status is not changed + when: mysql8022_and_higher == true + + # Create test table and add data to it: + - name: Create test table + shell: "echo \"CREATE TABLE {{ test_table }} (id int);\" | {{ mysql_command }} -P{{ mysql_primary_port }} {{ test_db }}" + + - name: Insert data + shell: "echo \"INSERT INTO {{ test_table }} (id) VALUES (1), (2), (3); FLUSH LOGS;\" | {{ mysql_command }} -P{{ mysql_primary_port }} {{ test_db }}" + + - name: Small pause to be sure the bin log, which was flushed previously, reached the slave + pause: + seconds: 2 + + # Test master log pos has been changed: + - name: Get standby status + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: getslave + register: slave_status + + # mysql_primary_status.Position is not actual and it has been changed by the prev step, + # so slave_status.Exec_Master_Log_Pos must be different: + - assert: + that: + - slave_status.Exec_Master_Log_Pos != mysql_primary_status.Position + when: mysql8022_and_higher == false + + - assert: + that: + - slave_status.Exec_Source_Log_Pos != mysql_primary_status.Position + when: mysql8022_and_higher == true + + - shell: pip show pymysql | awk '/Version/ {print $2}' + register: pymysql_version + + - name: Start slave that is already running + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: startslave + fail_on_error: true + register: result + + - assert: + that: + - result is not changed + when: (pymysql_version.stdout | default('1000', true)) is version('0.9.3', '<=') + + # Test stopslave mode: + - name: Stop slave + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: stopslave + register: result + + - assert: + that: + - result is changed + - result.queries == ["STOP SLAVE"] or result.queries == ["STOP REPLICA"] + + # Test stopslave mode: + - name: Stop slave that is no longer running + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: stopslave + fail_on_error: true + register: result + + - assert: + that: + - result is not changed + when: (pymysql_version.stdout | default('1000', true)) is version('0.9.3', '<=') diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_master_delay.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_master_delay.yml new file mode 100644 index 00000000..dcf977d1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_master_delay.yml @@ -0,0 +1,45 @@ +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +- vars: + mysql_params: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + + block: + + # Test master_delay mode: + - name: Run replication + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: changemaster + master_delay: '{{ test_master_delay }}' + register: result + + - assert: + that: + - result is changed + - result.queries == ["CHANGE MASTER TO MASTER_DELAY=60"] + + # Auxiliary step: + - name: Start slave + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: startslave + register: result + + # Check master_delay: + - name: Get standby status + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: getslave + register: slave_status + + - assert: + that: + - slave_status.SQL_Delay == {{ test_master_delay }} + - slave_status is not changed diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetmaster_mode.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetmaster_mode.yml new file mode 100644 index 00000000..36ed47d5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_resetmaster_mode.yml @@ -0,0 +1,56 @@ +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +- vars: + mysql_params: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + + block: + + # Needs for further tests: + - name: Stop slave + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: stopslave + + - name: Reset slave all + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_replica1_port }}' + mode: resetslaveall + + # Get master initial status: + - name: Get master status + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_primary_port }}' + mode: getmaster + register: mysql_primary_initial_status + + # Test resetmaster mode: + - name: Reset master + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_primary_port }}' + mode: resetmaster + register: result + + - assert: + that: + - result is changed + - result.queries == ["RESET MASTER"] + + # Get master final status: + - name: Get master status + mysql_replication: + <<: *mysql_params + login_port: '{{ mysql_primary_port }}' + mode: getmaster + register: mysql_primary_final_status + + - assert: + that: + - mysql_primary_initial_status.File != mysql_primary_final_status.File diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/defaults/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/defaults/main.yml new file mode 100644 index 00000000..5cf9074b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/defaults/main.yml @@ -0,0 +1,26 @@ +--- +# defaults file for test_mysql_user +mysql_user: root +mysql_password: msandbox +mysql_host: 127.0.0.1 +mysql_primary_port: 3307 + +db_name: 'data' +user_name_1: 'db_user1' +user_name_2: 'db_user2' +user_name_3: 'db_user3' +user_name_4: 'db_user4' + +user_password_1: 'gadfFDSdtTU^Sdfuj' +user_password_2: 'jkFKUdfhdso78yi&td' +user_password_3: 'jkFKUdfhdso78yi&tk' +user_password_4: 's2R#7pLV31!ZJrXPa3' + +root_password: 'zevuR6oPh7' + +db_names: + - clientdb + - employeedb + - providerdb + +tmp_dir: '/tmp' diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/files/create-function.sql b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/files/create-function.sql new file mode 100644 index 00000000..d16118cd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/files/create-function.sql @@ -0,0 +1,8 @@ +USE foo; +DELIMITER ;; +CREATE FUNCTION `function` () RETURNS tinyint(4) DETERMINISTIC +BEGIN + DECLARE NAME_FOUND tinyint DEFAULT 0; + RETURN NAME_FOUND; +END;; +DELIMITER ; diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/files/create-procedure.sql b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/files/create-procedure.sql new file mode 100644 index 00000000..d0d45aa4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/files/create-procedure.sql @@ -0,0 +1,5 @@ +USE bar; +DELIMITER ;; +CREATE PROCEDURE `procedure` () +SELECT * FROM bar;; +DELIMITER ; diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/meta/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/meta/main.yml new file mode 100644 index 00000000..a7ace5d1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - setup_mysql + - setup_remote_tmp_dir diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/assert_no_user.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/assert_no_user.yml new file mode 100644 index 00000000..98610842 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/assert_no_user.yml @@ -0,0 +1,25 @@ +# test code to assert no mysql user +# (c) 2014, Wayne Rosario <wrosario@ansible.com> + +# This file is part of Ansible +# +# Ansible 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 3 of the License, or +# (at your option) any later version. +# +# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>. + +# ============================================================ +- name: run command to query for mysql user + command: "{{ mysql_command }} -e \"SELECT User FROM mysql.user where user='{{ user_name }}'\"" + register: result + +- name: assert mysql user is not present + assert: { that: "'{{ user_name }}' not in result.stdout" } diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/assert_user.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/assert_user.yml new file mode 100644 index 00000000..d95d9d21 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/assert_user.yml @@ -0,0 +1,38 @@ +# test code to assert mysql user +# (c) 2014, Wayne Rosario <wrosario@ansible.com> + +# This file is part of Ansible +# +# Ansible 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 3 of the License, or +# (at your option) any later version. +# +# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>. + +# ============================================================ +- name: run command to query for mysql user + command: "{{ mysql_command }} -e \"SELECT User FROM mysql.user where user='{{ user_name }}'\"" + register: result + +- name: assert mysql user is present + assert: + that: + - "'{{ user_name }}' in result.stdout" + +- name: run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name }}'@'localhost'\"" + register: result + when: priv is defined + +- name: assert user has giving privileges + assert: + that: + - "'GRANT {{priv}} ON *.*' in result.stdout" + when: priv is defined diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/create_user.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/create_user.yml new file mode 100644 index 00000000..790d9bb2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/create_user.yml @@ -0,0 +1,40 @@ +# test code to create mysql user +# (c) 2014, Wayne Rosario <wrosario@ansible.com> + +# This file is part of Ansible +# +# Ansible 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 3 of the License, or +# (at your option) any later version. +# +# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>. + +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + + block: + + # ============================================================ + - name: create mysql user {{user_name}} + mysql_user: + <<: *mysql_params + name: '{{ user_name }}' + password: '{{ user_password }}' + state: present + register: result + + - name: assert output message mysql user was created + assert: + that: + - "result.changed == true" diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/issue-28.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/issue-28.yml new file mode 100644 index 00000000..a5b3d2a7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/issue-28.yml @@ -0,0 +1,86 @@ +--- +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + + block: + + # ============================================================ + - shell: pip show pymysql | awk '/Version/ {print $2}' + register: pymysql_version + + - name: get server certificate + copy: + content: "{{ lookup('pipe', \"openssl s_client -starttls mysql -connect localhost:3307 -showcerts 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'\") }}" + dest: /tmp/cert.pem + delegate_to: localhost + + - name: Drop mysql user if exists + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + state: absent + ignore_errors: yes + + - name: create user with ssl requirement + mysql_user: + <<: *mysql_params + name: "{{ user_name_1 }}" + password: "{{ user_password_1 }}" + priv: '*.*:ALL,GRANT' + tls_requires: + SSL: + + - name: attempt connection with newly created user (expect failure) + mysql_user: + name: "{{ user_name_2 }}" + password: "{{ user_password_2 }}" + host: 127.0.0.1 + login_user: '{{ user_name_1 }}' + login_password: '{{ user_password_1 }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + ca_cert: /tmp/cert.pem + register: result + ignore_errors: yes + + - assert: + that: + - result is failed + when: pymysql_version.stdout != "" + + - assert: + that: + - result is succeeded + when: pymysql_version.stdout == "" + + - name: attempt connection with newly created user ignoring hostname + mysql_user: + name: "{{ user_name_2 }}" + password: "{{ user_password_2 }}" + host: 127.0.0.1 + login_user: '{{ user_name_1 }}' + login_password: '{{ user_password_1 }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + ca_cert: /tmp/cert.pem + check_hostname: no + register: result + ignore_errors: yes + + - assert: + that: + - result is succeeded or 'pymysql >= 0.7.11 is required' in result.msg + + - name: Drop mysql user + mysql_user: + <<: *mysql_params + name: '{{ item }}' + host: 127.0.0.1 + state: absent + with_items: + - "{{ user_name_1 }}" + - "{{ user_name_2 }}" diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/issue-29511.yaml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/issue-29511.yaml new file mode 100644 index 00000000..31e6edfe --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/issue-29511.yaml @@ -0,0 +1,86 @@ +--- +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + + block: + + - name: Issue test setup - drop database + mysql_db: + <<: *mysql_params + name: "{{ item }}" + state: absent + loop: + - foo + - bar + + - name: Issue test setup - create database + mysql_db: + <<: *mysql_params + name: "{{ item }}" + state: present + loop: + - foo + - bar + + - name: Copy SQL scripts to remote + copy: + src: "{{ item }}" + dest: "{{ remote_tmp_dir }}/{{ item | basename }}" + with_items: + - create-function.sql + - create-procedure.sql + + - name: Create function for test + shell: "{{ mysql_command }} < {{ remote_tmp_dir }}/create-function.sql" + + - name: Create procedure for test + shell: "{{ mysql_command }} < {{ remote_tmp_dir }}/create-procedure.sql" + + - name: Create user with FUNCTION and PROCEDURE privileges + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + password: '{{ user_password_2 }}' + state: present + priv: 'FUNCTION foo.function:EXECUTE/foo.*:SELECT/PROCEDURE bar.procedure:EXECUTE' + register: result + + - name: Assert Create user with FUNCTION and PROCEDURE privileges + assert: + that: + - result is success + - result is changed + + - name: Create user with FUNCTION and PROCEDURE privileges - Idempotent check + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + password: '{{ user_password_2 }}' + state: present + priv: 'FUNCTION foo.function:EXECUTE/foo.*:SELECT/PROCEDURE bar.procedure:EXECUTE' + register: result + + - name: Assert Create user with FUNCTION and PROCEDURE privileges + assert: + that: + - result is success + - result is not changed + + - name: Remove user + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + state: absent + + - name: Issue test teardown - cleanup databases + mysql_db: + <<: *mysql_params + name: "{{ item }}" + state: absent + loop: + - foo + - bar diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/issue-64560.yaml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/issue-64560.yaml new file mode 100644 index 00000000..46078b2f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/issue-64560.yaml @@ -0,0 +1,45 @@ +--- +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + + block: + + - name: Set root password + mysql_user: + <<: *mysql_params + name: root + password: '{{ root_password }}' + check_implicit_admin: yes + register: result + + - name: assert root password is changed + assert: { that: "result.changed == true" } + + - name: Set root password again + mysql_user: + login_user: '{{ mysql_user }}' + login_password: '{{ root_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: root + password: '{{ root_password }}' + check_implicit_admin: yes + register: result + + - name: Assert root password is not changed + assert: { that: "result.changed == false" } + + - name: Set root password again + mysql_user: + login_user: '{{ mysql_user }}' + login_password: '{{ root_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + name: root + password: '{{ mysql_password }}' + check_implicit_admin: yes + register: result diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/main.yml new file mode 100644 index 00000000..a744050a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -0,0 +1,282 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# test code for the mysql_user module +# (c) 2014, Wayne Rosario <wrosario@ansible.com> + +# This file is part of Ansible +# +# Ansible 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 3 dof the License, or +# (at your option) any later version. +# +# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>. + +# ============================================================ +# create mysql user and verify user is added to mysql database +# +- name: alias mysql command to include default options + set_fact: + mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} -P{{ mysql_primary_port }} --protocol=tcp" + +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + + block: + + - include: issue-28.yml + + - include: create_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }} + + - include: resource_limits.yml + + - include: assert_user.yml user_name={{user_name_1}} + + - include: remove_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }} + + - include: assert_no_user.yml user_name={{user_name_1}} + + # ============================================================ + # Create mysql user that already exist on mysql database + # + - include: create_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }} + + - name: create mysql user that already exist (expect changed=false) + mysql_user: + <<: *mysql_params + name: '{{user_name_1}}' + password: '{{user_password_1}}' + state: present + register: result + + - name: assert output message mysql user was not created + assert: { that: "result.changed == false" } + + # ============================================================ + # remove mysql user and verify user is removed from mysql database + # + - name: remove mysql user state=absent (expect changed=true) + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + password: '{{ user_password_1 }}' + state: absent + register: result + + - name: assert output message mysql user was removed + assert: + that: + - "result.changed == true" + + - include: assert_no_user.yml user_name={{user_name_1}} + + # ============================================================ + # remove mysql user that does not exist on mysql database + # + - name: remove mysql user that does not exist state=absent (expect changed=false) + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + password: '{{ user_password_1 }}' + state: absent + register: result + + - name: assert output message mysql user that does not exist + assert: + that: + - "result.changed == false" + + - include: assert_no_user.yml user_name={{user_name_1}} + + # ============================================================ + # Create user with no privileges and verify default privileges are assign + # + - name: create user with select privilege state=present (expect changed=true) + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + password: '{{ user_password_1 }}' + state: present + register: result + + - include: assert_user.yml user_name={{user_name_1}} priv=USAGE + + - include: remove_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }} + + - include: assert_no_user.yml user_name={{user_name_1}} + + # ============================================================ + # Create user with select privileges and verify select privileges are assign + # + - name: create user with select privilege state=present (expect changed=true) + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + password: '{{ user_password_2 }}' + state: present + priv: '*.*:SELECT' + register: result + + - include: assert_user.yml user_name={{user_name_2}} priv=SELECT + + - include: remove_user.yml user_name={{user_name_2}} user_password={{ user_password_2 }} + + - include: assert_no_user.yml user_name={{user_name_2}} + + # ============================================================ + # Assert user has access to multiple databases + # + - name: give users access to multiple databases + mysql_user: + <<: *mysql_params + name: '{{ item[0] }}' + priv: '{{ item[1] }}.*:ALL' + append_privs: yes + password: '{{ user_password_1 }}' + with_nested: + - [ '{{ user_name_1 }}', '{{ user_name_2 }}'] + - "{{db_names}}" + + - name: show grants access for user1 on multiple database + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_1 }}'@'localhost'\"" + register: result + + - name: assert grant access for user1 on multiple database + assert: + that: + - "'{{ item }}' in result.stdout" + with_items: "{{db_names}}" + + - name: show grants access for user2 on multiple database + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_2 }}'@'localhost'\"" + register: result + + - name: assert grant access for user2 on multiple database + assert: + that: + - "'{{ item }}' in result.stdout" + with_items: "{{db_names}}" + + - include: remove_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }} + + - include: remove_user.yml user_name={{user_name_2}} user_password={{ user_password_1 }} + + - name: give user access to database via wildcard + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + priv: '%db.*:SELECT' + append_privs: yes + password: '{{ user_password_1 }}' + + - name: show grants access for user1 on multiple database + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_1 }}'@'localhost'\"" + register: result + + - name: assert grant access for user1 on multiple database + assert: + that: + - "'%db' in result.stdout" + - "'SELECT' in result.stdout" + + - name: test priv type check, must fail + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + priv: + - unsuitable + - type + append_privs: yes + host_all: yes + password: '{{ user_password_1 }}' + register: result + ignore_errors: yes + + - name: check fail message + assert: + that: + - result is failed + - result.msg is search('priv parameter must be str or dict') + + - name: change user access to database via wildcard + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + priv: '%db.*:INSERT' + append_privs: yes + host_all: yes + password: '{{ user_password_1 }}' + + - name: show grants access for user1 on multiple database + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_1 }}'@'localhost'\"" + register: result + + - name: assert grant access for user1 on multiple database + assert: + that: + - "'%db' in result.stdout" + - "'INSERT' in result.stdout" + + - include: remove_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }} + + # ============================================================ + # Test plaintext and encrypted password scenarios. + # + - include: test_user_password.yml + + # ============================================================ + # Test plugin authentication scenarios. + # + - include: test_user_plugin_auth.yml + + # ============================================================ + # Assert create user with SELECT privileges, attempt to create database and update privileges to create database + # + - include: test_privs.yml current_privilege=SELECT current_append_privs=no + + # ============================================================ + # Assert creating user with SELECT privileges, attempt to create database and append privileges to create database + # + - include: test_privs.yml current_privilege=DROP current_append_privs=yes + + # ============================================================ + # Assert create user with SELECT privileges, attempt to create database and update privileges to create database + # + - include: test_privs.yml current_privilege='UPDATE,ALTER' current_append_privs=no + + # ============================================================ + # Assert creating user with SELECT privileges, attempt to create database and append privileges to create database + # + - include: test_privs.yml current_privilege='INSERT,DELETE' current_append_privs=yes + + # Tests for the priv parameter with dict value (https://github.com/ansible/ansible/issues/57533) + - include: test_priv_dict.yml + + # Test that append_privs will not attempt to make a change where current privileges are a superset of new privileges + # (https://github.com/ansible-collections/community.mysql/issues/69) + - include: test_priv_append.yml enable_check_mode=no + - include: test_priv_append.yml enable_check_mode=yes + + # Tests for the TLS requires dictionary + - include: tls_requirements.yml + + - import_tasks: issue-29511.yaml + tags: + - issue-29511 + + - import_tasks: issue-64560.yaml + tags: + - issue-64560 diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/remove_user.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/remove_user.yml new file mode 100644 index 00000000..45a0ad4c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/remove_user.yml @@ -0,0 +1,74 @@ +# test code to remove mysql user +# (c) 2014, Wayne Rosario <wrosario@ansible.com> + +# This file is part of Ansible +# +# Ansible 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 3 of the License, or +# (at your option) any later version. +# +# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>. + +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + + block: + + # ============================================================ + - name: remove mysql user {{user_name}} + mysql_user: + <<: *mysql_params + name: '{{user_name}}' + password: '{{user_password}}' + state: absent + register: result + + - name: assert output message mysql user was removed + assert: + that: + - "result.changed == true" + + # ============================================================ + - name: create blank mysql user to be removed later + mysql_user: + <<: *mysql_params + name: "" + state: present + password: 'KJFDY&D*Sfuydsgf' + + - name: remove blank mysql user with hosts=all (expect changed) + mysql_user: + <<: *mysql_params + user: "" + host_all: true + state: absent + register: result + + - name: assert changed is true for removing all blank users + assert: + that: + - "result.changed == true" + + - name: remove blank mysql user with hosts=all (expect ok) + mysql_user: + <<: *mysql_params + user: "" + host_all: true + state: absent + register: result + + - name: assert changed is true for removing all blank users + assert: + that: + - "result.changed == false" diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/resource_limits.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/resource_limits.yml new file mode 100644 index 00000000..736adb33 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/resource_limits.yml @@ -0,0 +1,118 @@ +# test code for resource_limits parameter +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + + block: + + - name: Drop mysql user {{ user_name_1 }} if exists + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + state: absent + + - name: Create mysql user {{ user_name_1 }} with resource limits in check_mode + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + password: '{{ user_password_1 }}' + state: present + resource_limits: + MAX_QUERIES_PER_HOUR: 10 + MAX_CONNECTIONS_PER_HOUR: 5 + check_mode: yes + register: result + + - assert: + that: + - result is changed + + - name: Create mysql user {{ user_name_1 }} with resource limits in actual mode + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + password: '{{ user_password_1 }}' + state: present + resource_limits: + MAX_QUERIES_PER_HOUR: 10 + MAX_CONNECTIONS_PER_HOUR: 5 + register: result + + - assert: + that: + - result is changed + + - name: Check + mysql_query: + <<: *mysql_params + query: > + SELECT User FROM mysql.user WHERE User = '{{ user_name_1 }}' AND Host = 'localhost' + AND max_questions = 10 AND max_connections = 5 + register: result + + - assert: + that: + - result.rowcount[0] == 1 + + - name: Try to set the same limits again in check mode + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + password: '{{ user_password_1 }}' + state: present + resource_limits: + MAX_QUERIES_PER_HOUR: 10 + MAX_CONNECTIONS_PER_HOUR: 5 + check_mode: yes + register: result + + - assert: + that: + - result is not changed + + - name: Try to set the same limits again in actual mode + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + password: '{{ user_password_1 }}' + state: present + resource_limits: + MAX_QUERIES_PER_HOUR: 10 + MAX_CONNECTIONS_PER_HOUR: 5 + register: result + + - assert: + that: + - result is not changed + + - name: Change limits + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + password: '{{ user_password_1 }}' + state: present + resource_limits: + MAX_QUERIES_PER_HOUR: 5 + MAX_CONNECTIONS_PER_HOUR: 5 + register: result + + - assert: + that: + - result is changed + + - name: Check + mysql_query: + <<: *mysql_params + query: > + SELECT User FROM mysql.user WHERE User = '{{ user_name_1 }}' AND Host = 'localhost' + AND max_questions = 5 AND max_connections = 5 + register: result + + - assert: + that: + - result.rowcount[0] == 1 + + when: (ansible_distribution == 'Ubuntu' and ansible_distribution_major_version >= '18') or (ansible_distribution == 'CentOS' and ansible_distribution_major_version >= '8') diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/test_priv_append.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/test_priv_append.yml new file mode 100644 index 00000000..7dc15ca0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/test_priv_append.yml @@ -0,0 +1,114 @@ +# Test code to ensure that appending privileges will not result in unnecessary changes when the current privileges +# are a superset of the new privileges that have been defined. +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + + block: + + - name: Create test databases + mysql_db: + <<: *mysql_params + name: '{{ item }}' + state: present + loop: + - data1 + - data2 + + - name: Create a user with an initial set of privileges + mysql_user: + <<: *mysql_params + name: '{{ user_name_4 }}' + password: '{{ user_password_4 }}' + priv: 'data1.*:SELECT,INSERT/data2.*:SELECT,DELETE' + state: present + + - name: Run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'localhost'\"" + register: result + + - name: Assert that the initial set of privileges matches what is expected + assert: + that: + - "'GRANT SELECT, INSERT ON `data1`.*' in result.stdout" + - "'GRANT SELECT, DELETE ON `data2`.*' in result.stdout" + + - name: Append privileges that are a subset of the current privileges, which should be a no-op + mysql_user: + <<: *mysql_params + name: '{{ user_name_4 }}' + password: '{{ user_password_4 }}' + priv: 'data1.*:SELECT/data2.*:SELECT' + append_privs: yes + state: present + check_mode: '{{ enable_check_mode }}' + register: result + + - name: Assert that there wasn't a change in permissions + assert: + that: + - "result.changed == false" + + - name: Run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'localhost'\"" + register: result + + - name: Assert that the permissions still match what was originally granted + assert: + that: + - "'GRANT SELECT, INSERT ON `data1`.*' in result.stdout" + - "'GRANT SELECT, DELETE ON `data2`.*' in result.stdout" + + - name: Append privileges that are not included in the current set of privileges to test that privileges are updated + mysql_user: + <<: *mysql_params + name: '{{ user_name_4 }}' + password: '{{ user_password_4 }}' + priv: 'data1.*:DELETE/data2.*:SELECT' + append_privs: yes + state: present + check_mode: '{{ enable_check_mode }}' + register: result + + - name: Assert that there was a change because permissions were added to data1.* + assert: + that: + - "result.changed == true" + + - name: Run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_4 }}'@'localhost'\"" + register: result + + - name: Assert that the permissions were changed as expected if check_mode is set to 'no' + assert: + that: + - "'GRANT SELECT, INSERT, DELETE ON `data1`.*' in result.stdout" + - "'GRANT SELECT, DELETE ON `data2`.*' in result.stdout" + when: enable_check_mode == 'no' + + - name: Assert that the permissions were not actually changed if check_mode is set to 'yes' + assert: + that: + - "'GRANT SELECT, INSERT ON `data1`.*' in result.stdout" + - "'GRANT SELECT, DELETE ON `data2`.*' in result.stdout" + when: enable_check_mode == 'yes' + + ########## + # Clean up + - name: Drop test databases + mysql_db: + <<: *mysql_params + name: '{{ item }}' + state: present + loop: + - data1 + - data2 + + - name: Drop test user + mysql_user: + <<: *mysql_params + name: '{{ user_name_4 }}' + state: absent diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/test_priv_dict.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/test_priv_dict.yml new file mode 100644 index 00000000..ec7b05ee --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/test_priv_dict.yml @@ -0,0 +1,55 @@ +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + + block: + + # Tests for priv parameter value passed as a dict + - name: Create test databases + mysql_db: + <<: *mysql_params + name: '{{ item }}' + state: present + loop: + - data1 + - data2 + + - name: Create user with privileges + mysql_user: + <<: *mysql_params + name: '{{ user_name_3 }}' + password: '{{ user_password_3 }}' + priv: + "data1.*": "SELECT" + "data2.*": "SELECT" + state: present + + - name: Run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_3 }}'@'localhost'\"" + register: result + + - name: Assert user has giving privileges + assert: + that: + - "'GRANT SELECT ON `data1`.*' in result.stdout" + - "'GRANT SELECT ON `data2`.*' in result.stdout" + + ########## + # Clean up + - name: Drop test databases + mysql_db: + <<: *mysql_params + name: '{{ item }}' + state: present + loop: + - data1 + - data2 + + - name: Drop test user + mysql_user: + <<: *mysql_params + name: '{{ user_name_3 }}' + state: absent diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/test_privs.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/test_privs.yml new file mode 100644 index 00000000..4ed75d17 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/test_privs.yml @@ -0,0 +1,186 @@ +# test code for privileges for mysql_user module +# (c) 2014, Wayne Rosario <wrosario@ansible.com> + +# This file is part of Ansible +# +# Ansible 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 3 of the License, or +# (at your option) any later version. +# +# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>. + +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + + block: + + # ============================================================ + - name: create user with basic select privileges + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + password: '{{ user_password_2 }}' + priv: '*.*:SELECT' + state: present + when: current_append_privs == "yes" + + - include: assert_user.yml user_name={{user_name_2}} priv='SELECT' + when: current_append_privs == "yes" + + - name: create user with current privileges (expect changed=true) + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + password: '{{ user_password_2 }}' + priv: '*.*:{{current_privilege}}' + append_privs: '{{current_append_privs}}' + state: present + register: result + + - name: assert output message for current privileges + assert: + that: + - "result.changed == true" + + - name: run command to show privileges for user (expect privileges in stdout) + command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{user_name_2}}'@'localhost'\"" + register: result + + - name: assert user has correct privileges + assert: + that: + - "'GRANT {{current_privilege | replace(',', ', ')}} ON *.*' in result.stdout" + when: current_append_privs == "no" + + - name: assert user has correct privileges + assert: + that: + - "'GRANT SELECT, {{current_privilege | replace(',', ', ')}} ON *.*' in result.stdout" + when: current_append_privs == "yes" + + - name: create database using user current privileges + mysql_db: + login_user: '{{ user_name_2 }}' + login_password: '{{ user_password_2 }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name }}' + state: present + ignore_errors: true + + - name: run command to test that database was not created + command: "{{ mysql_command }} -e \"show databases like '{{ db_name }}'\"" + register: result + + - name: assert database was not created + assert: + that: + - "'{{ db_name }}' not in result.stdout" + + # ============================================================ + - name: Add privs to a specific table (expect changed) + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + password: '{{ user_password_2 }}' + priv: 'jmainguy.jmainguy:ALL' + state: present + register: result + + - name: Assert that priv changed + assert: + that: + - "result.changed == true" + + - name: Add privs to a specific table (expect ok) + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + password: '{{ user_password_2 }}' + priv: 'jmainguy.jmainguy:ALL' + state: present + register: result + + - name: Assert that priv did not change + assert: + that: + - "result.changed == false" + + # ============================================================ + - name: update user with all privileges + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + password: '{{ user_password_2 }}' + priv: '*.*:ALL' + state: present + + # - include: assert_user.yml user_name={{user_name_2}} priv='ALL PRIVILEGES' + + - name: create database using user + mysql_db: + login_user: '{{ user_name_2 }}' + login_password: '{{ user_password_2 }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name }}' + state: present + + - name: run command to test database was created using user new privileges + command: "{{ mysql_command }} -e \"SHOW CREATE DATABASE {{ db_name }}\"" + + - name: drop database using user + mysql_db: + login_user: '{{ user_name_2 }}' + login_password: '{{ user_password_2 }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + name: '{{ db_name }}' + state: absent + + # ============================================================ + - name: update user with a long privileges list (mysql has a special multiline grant output) + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + password: '{{ user_password_2 }}' + priv: '*.*:CREATE USER,FILE,PROCESS,RELOAD,REPLICATION CLIENT,REPLICATION SLAVE,SHOW DATABASES,SHUTDOWN,SUPER,CREATE,DROP,EVENT,LOCK TABLES,INSERT,UPDATE,DELETE,SELECT,SHOW VIEW,GRANT' + state: present + register: result + + - name: Assert that priv changed + assert: + that: + - "result.changed == true" + + - name: Test idempotency (expect ok) + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + password: '{{ user_password_2 }}' + priv: '*.*:CREATE USER,FILE,PROCESS,RELOAD,REPLICATION CLIENT,REPLICATION SLAVE,SHOW DATABASES,SHUTDOWN,SUPER,CREATE,DROP,EVENT,LOCK TABLES,INSERT,UPDATE,DELETE,SELECT,SHOW VIEW,GRANT' + state: present + register: result + + - name: Assert that priv did not change + assert: + that: + - "result.changed == false" + + - name: remove username + mysql_user: + <<: *mysql_params + name: '{{ user_name_2 }}' + password: '{{ user_password_2 }}' + state: absent diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/test_user_password.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/test_user_password.yml new file mode 100644 index 00000000..f3b0e06a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/test_user_password.yml @@ -0,0 +1,269 @@ +# Tests scenarios for both plaintext and encrypted user passwords. + +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + test_user_name: 'test_user_password' + initial_password: 'a5C8SN*DBa0%a75sGz' + initial_password_encrypted: '*0A12D4DF68C2A50716111674E565CA3D7D68B048' + new_password: 'NkN&qECv33vuQzf3bJg' + new_password_encrypted: '*B6559186FAD0953589F54383AD8EE9E9172296DA' + test_default_priv_type: 'SELECT' + test_default_priv: '*.*:{{ test_default_priv_type }}' + + block: + + # ============================================================ + # Test setting plaintext password and changing it. + # + + - name: Create user with initial password + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + password: '{{ initial_password }}' + priv: '{{ test_default_priv }}' + state: present + register: result + + - name: Assert that a change occurred because the user was added + assert: + that: + - "result.changed == true" + + - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + + - name: Get the MySQL version using the newly created used creds + mysql_info: + login_user: '{{ test_user_name }}' + login_password: '{{ initial_password }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + filter: version + register: result + ignore_errors: true + + - name: Assert that mysql_info was successful + assert: + that: + - "result.failed == false" + + - name: Run mysql_user again without any changes + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + password: '{{ initial_password }}' + priv: '{{ test_default_priv }}' + state: present + register: result + + - name: Assert that there weren't any changes because username/password didn't change + assert: + that: + - "result.changed == false" + + - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + + - name: Update the user password + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + password: '{{ new_password }}' + state: present + register: result + + - name: Assert that a change occurred because the password was updated + assert: + that: + - "result.changed == true" + + - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + + - name: Get the MySQL version data using the original password (should fail) + mysql_info: + login_user: '{{ test_user_name }}' + login_password: '{{ initial_password }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + filter: version + register: result + ignore_errors: true + + - name: Assert that the mysql_info module failed because we used the old password + assert: + that: + - "result.failed == true" + + - name: Get the MySQL version data using the new password (should work) + mysql_info: + login_user: '{{ test_user_name }}' + login_password: '{{ new_password }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + filter: version + register: result + ignore_errors: true + + - name: Assert that the mysql_info module succeeded because we used the new password + assert: + that: + - "result.failed == false" + + # Cleanup + - include: remove_user.yml user_name={{ test_user_name }} user_password={{ new_password }} + + # ============================================================ + # Test setting a plaintext password and then the same password encrypted to ensure there isn't a change detected. + # + + - name: Create user with initial password + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + password: '{{ initial_password }}' + priv: '{{ test_default_priv }}' + state: present + register: result + + - name: Assert that a change occurred because the user was added + assert: + that: + - "result.changed == true" + + - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + + - name: Pass in the same password as before, but in the encrypted form (no change expected) + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + password: '{{ initial_password_encrypted }}' + encrypted: yes + priv: '{{ test_default_priv }}' + state: present + register: result + + - name: Assert that there weren't any changes because username/password didn't change + assert: + that: + - "result.changed == false" + + # Cleanup + - include: remove_user.yml user_name={{ test_user_name }} user_password={{ new_password }} + + # ============================================================ + # Test setting an encrypted password and then the same password in plaintext to ensure there isn't a change. + # + + - name: Create user with initial password + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + password: '{{ initial_password_encrypted }}' + encrypted: yes + priv: '{{ test_default_priv }}' + state: present + register: result + + - name: Assert that a change occurred because the user was added + assert: + that: + - "result.changed == true" + + - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + + - name: Get the MySQL version data using the new creds + mysql_info: + login_user: '{{ test_user_name }}' + login_password: '{{ initial_password }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + filter: version + register: result + ignore_errors: true + + - name: Assert that the mysql_info module succeeded because we used the new password + assert: + that: + - "result.failed == false" + + - name: Pass in the same password as before, but in the encrypted form (no change expected) + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + password: '{{ initial_password }}' + state: present + register: result + + - name: Assert that there weren't any changes because username/password didn't change + assert: + that: + - "result.changed == false" + + # Cleanup + - include: remove_user.yml user_name={{ test_user_name }} user_password={{ new_password }} + + # ============================================================ + # Test setting an empty password. + # + + - name: Create user with empty password + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + priv: '{{ test_default_priv }}' + state: present + register: result + + - name: Assert that a change occurred because the user was added + assert: + that: + - "result.changed == true" + + - name: Get the MySQL version using an empty password for the newly created user + mysql_info: + login_user: '{{ test_user_name }}' + login_password: '' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + filter: version + register: result + ignore_errors: true + + - name: Assert that mysql_info was successful + assert: + that: + - "result.failed == false" + + - name: Get the MySQL version using an non-empty password (should fail) + mysql_info: + login_user: '{{ test_user_name }}' + login_password: 'some_password' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + filter: version + register: result + ignore_errors: true + + - name: Assert that mysql_info failed + assert: + that: + - "result.failed == true" + + - name: Update the user without changing the password + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + priv: '{{ test_default_priv }}' + state: present + register: result + + - name: Assert that the user wasn't changed because the password is still empty + assert: + that: + - "result.changed == false" + + # Cleanup + - include: remove_user.yml user_name={{ test_user_name }} user_password='' diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml new file mode 100644 index 00000000..3ce9f1b8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/test_user_plugin_auth.yml @@ -0,0 +1,386 @@ +# Test user plugin auth scenarios. + +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + test_user_name: 'test_user_plugin_auth' + test_plugin_type: 'mysql_native_password' + test_plugin_hash: '*0CB5B86F23FDC24DB19A29B8854EB860CBC47793' + test_plugin_auth_string: 'Fdt8fd^34ds' + test_plugin_new_hash: '*E74368AC90460FA669F6D41BFB7F2A877DB73745' + test_plugin_new_auth_string: 'c$K01LsmK7nJnIR4!h' + test_default_priv_type: 'SELECT' + test_default_priv: '*.*:{{ test_default_priv_type }}' + + block: + + # ============================================================ + # Test plugin auth initially setting a hash and then changing to a different hash. + # + + - name: Create user with plugin auth (with hash string) + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + plugin: '{{ test_plugin_type }}' + plugin_hash_string: '{{ test_plugin_hash }}' + priv: '{{ test_default_priv }}' + register: result + + - name: Get user information + command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" + register: show_create_user + + - name: Check that the module made a change and that the expected plugin type is set + assert: + that: + - "result.changed == true" + - "'{{ test_plugin_type }}' in show_create_user.stdout" + + - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + + - name: Get the MySQL version using the newly created creds + mysql_info: + login_user: '{{ test_user_name }}' + login_password: '{{ test_plugin_auth_string }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + filter: version + register: result + + - name: Assert that mysql_info was successful + assert: + that: + - "result.failed == false" + + - name: Update the user with a different hash + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + plugin: '{{ test_plugin_type }}' + plugin_hash_string: '{{ test_plugin_new_hash }}' + register: result + + - name: Check that the module makes the change because the hash changed + assert: + that: + - "result.changed == true" + + - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + + - name: Getting the MySQL info with the new password should work + mysql_info: + login_user: '{{ test_user_name }}' + login_password: '{{ test_plugin_new_auth_string }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + filter: version + register: result + + - name: Assert that mysql_info was successful + assert: + that: + - "result.failed == false" + + # Cleanup + - include: remove_user.yml user_name={{ test_user_name }} user_password={{ test_plugin_new_auth_string }} + + # ============================================================ + # Test plugin auth initially setting a hash and then switching to a plaintext auth string. + # + + - name: Create user with plugin auth (with hash string) + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + plugin: '{{ test_plugin_type }}' + plugin_hash_string: '{{ test_plugin_hash }}' + priv: '{{ test_default_priv }}' + register: result + + - name: Get user information + command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" + register: show_create_user + + - name: Check that the module made a change and that the expected plugin type is set + assert: + that: + - "result.changed == true" + - "'{{ test_plugin_type }}' in show_create_user.stdout" + + - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + + - name: Get the MySQL version using the newly created creds + mysql_info: + login_user: '{{ test_user_name }}' + login_password: '{{ test_plugin_auth_string }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + filter: version + register: result + + - name: Assert that mysql_info was successful + assert: + that: + - "result.failed == false" + + - name: Update the user with the same hash (no change expected) + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + plugin: '{{ test_plugin_type }}' + plugin_hash_string: '{{ test_plugin_hash }}' + register: result + + - name: Check that the module doesn't make a change when the same hash is passed in + assert: + that: + - "result.changed == false" + + - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + + - name: Change the user using the same plugin, but switch to the same auth string in plaintext form + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + plugin: '{{ test_plugin_type }}' + plugin_auth_string: '{{ test_plugin_auth_string }}' + register: result + + # Expecting a change is currently by design (see comment in source). + - name: Check that the module did not change the password + assert: + that: + - "result.changed == true" + + - name: Getting the MySQL info should still work + mysql_info: + login_user: '{{ test_user_name }}' + login_password: '{{ test_plugin_auth_string }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + filter: version + register: result + + - name: Assert that mysql_info was successful + assert: + that: + - "result.failed == false" + + # Cleanup + - include: remove_user.yml user_name={{ test_user_name }} user_password={{ test_plugin_auth_string }} + + # ============================================================ + # Test plugin auth initially setting a plaintext auth string and then switching to a hash. + # + + - name: Create user with plugin auth (with auth string) + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + plugin: '{{ test_plugin_type }}' + plugin_auth_string: '{{ test_plugin_auth_string }}' + priv: '{{ test_default_priv }}' + register: result + + - name: Get user information + command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" + register: show_create_user + + - name: Check that the module made a change and that the expected plugin type is set + assert: + that: + - "result.changed == true" + - "'{{ test_plugin_type }}' in show_create_user.stdout" + + - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + + - name: Get the MySQL version using the newly created creds + mysql_info: + login_user: '{{ test_user_name }}' + login_password: '{{ test_plugin_auth_string }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + filter: version + register: result + + - name: Assert that mysql_info was successful + assert: + that: + - "result.failed == false" + + - name: Update the user with the same auth string + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + plugin: '{{ test_plugin_type }}' + plugin_auth_string: '{{ test_plugin_auth_string }}' + register: result + + # This is the current expected behavior because there isn't a reliable way to hash the password in the mysql_user + # module in order to be able to compare this password with the stored hash. See the source for more info. + - name: The module should detect a change even though the password is the same + assert: + that: + - "result.changed == true" + + - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + + - name: Change the user using the same plugin, but switch to the same auth string in hash form + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + plugin: '{{ test_plugin_type }}' + plugin_hash_string: '{{ test_plugin_hash }}' + register: result + + - name: Check that the module did not change the password + assert: + that: + - "result.changed == false" + + - name: Get the MySQL version using the newly created creds + mysql_info: + login_user: '{{ test_user_name }}' + login_password: '{{ test_plugin_auth_string }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + filter: version + register: result + + - name: Assert that mysql_info was successful + assert: + that: + - "result.failed == false" + + # Cleanup + - include: remove_user.yml user_name={{ test_user_name }} user_password={{ test_plugin_auth_string }} + + # ============================================================ + # Test plugin auth with an empty auth string. + # + + - name: Create user with plugin auth (empty auth string) + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + plugin: '{{ test_plugin_type }}' + priv: '{{ test_default_priv }}' + register: result + + - name: Get user information + command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" + register: show_create_user + + - name: Check that the module made a change and that the expected plugin type is set + assert: + that: + - "result.changed == true" + - "'{{ test_plugin_type }}' in show_create_user.stdout" + + - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + + - name: Get the MySQL version using an empty password for the newly created user + mysql_info: + login_user: '{{ test_user_name }}' + login_password: '' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + filter: version + register: result + ignore_errors: true + + - name: Assert that mysql_info was successful + assert: + that: + - "result.failed == false" + + - name: Get the MySQL version using an non-empty password (should fail) + mysql_info: + login_user: '{{ test_user_name }}' + login_password: 'some_password' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + filter: version + register: result + ignore_errors: true + + - name: Assert that mysql_info failed + assert: + that: + - "result.failed == true" + + - name: Update the user without changing the auth mechanism + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + plugin: '{{ test_plugin_type }}' + state: present + register: result + + - name: Assert that the user wasn't changed because the auth string is still empty + assert: + that: + - "result.changed == false" + + # Cleanup + - include: remove_user.yml user_name={{ test_user_name }} user_password={{ test_plugin_auth_string }} + + # ============================================================ + # Test plugin auth switching from one type of plugin to another without an auth string or hash. The only other + # plugins that are loaded by default are sha2*, but these aren't compatible with pymysql < 0.9, so skip these tests + # for those versions. + # + - name: Get pymysql version + shell: pip show pymysql | awk '/Version/ {print $2}' + register: pymysql_version + + - name: Test plugin auth switching which doesn't work on pymysql < 0.9 + when: pymysql_version.stdout == "" or (pymysql_version.stdout != "" and pymysql_version.stdout is version('0.9', '>=')) + block: + + - name: Create user with plugin auth (empty auth string) + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + plugin: '{{ test_plugin_type }}' + priv: '{{ test_default_priv }}' + register: result + + - name: Get user information + command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" + register: show_create_user + + - name: Check that the module made a change and that the expected plugin type is set + assert: + that: + - "result.changed == true" + - "'{{ test_plugin_type }}' in show_create_user.stdout" + + - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + + - name: Switch user to sha256_password auth plugin + mysql_user: + <<: *mysql_params + name: '{{ test_user_name }}' + plugin: sha256_password + priv: '{{ test_default_priv }}' + register: result + + - name: Get user information + command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ test_user_name }}'@'localhost'\"" + register: show_create_user + + - name: Check that the module made a change and that the expected plugin type is set + assert: + that: + - "result.changed == true" + - "'sha256_password' in show_create_user.stdout" + + - include: assert_user.yml user_name={{ test_user_name }} priv={{ test_default_priv_type }} + + # Cleanup + - include: remove_user.yml user_name={{ test_user_name }} user_password={{ test_plugin_auth_string }} diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/tls_requirements.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/tls_requirements.yml new file mode 100644 index 00000000..8de9401e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_user/tasks/tls_requirements.yml @@ -0,0 +1,195 @@ +--- +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + + block: + + # ============================================================ + - name: find out the database version + mysql_info: + <<: *mysql_params + filter: version + register: db_version + + - name: Drop mysql user {{ item }} if exists + mysql_user: + <<: *mysql_params + name: '{{ item }}' + state: absent + with_items: ['{{ user_name_1 }}', '{{ user_name_2 }}', '{{ user_name_3 }}'] + + - name: create user with TLS requirements in check mode (expect changed=true) + mysql_user: + <<: *mysql_params + name: "{{ user_name_1 }}" + password: "{{ user_password_1 }}" + tls_requires: + SSL: + check_mode: yes + register: result + + - name: Assert check mode user create reports changed state + assert: + that: + - result is changed + + - include: assert_no_user.yml user_name={{user_name_1}} + + - name: create user with TLS requirements state=present (expect changed=true) + mysql_user: + <<: *mysql_params + name: '{{ item[0] }}' + password: '{{ user_password_1 }}' + tls_requires: '{{ item[1] }}' + with_together: + - [ '{{ user_name_1 }}', '{{ user_name_2 }}', '{{ user_name_3 }}'] + - + - SSL: + - X509: + - subject: '/CN=alice/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' + cipher: 'ECDHE-ECDSA-AES256-SHA384' + issuer: '/CN=org/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' + + - block: + - name: retrieve TLS requiremets for users in old database version + command: "{{ mysql_command }} -L -N -s -e \"SHOW GRANTS for '{{ item }}'@'localhost'\"" + register: old_result + with_items: ['{{ user_name_1 }}', '{{ user_name_2 }}', '{{ user_name_3 }}'] + + - name: set old database separator + set_fact: + separator: '\n' + # Semantically: when mysql version <= 5.6 or MariaDB version <= 10.1 + when: db_version.version.major <= 5 and db_version.version.minor <= 6 or db_version.version.major == 10 and db_version.version.minor < 2 + + - block: + - name: retrieve TLS requiremets for users in new database version + command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ item }}'@'localhost'\"" + register: new_result + with_items: ['{{ user_name_1 }}', '{{ user_name_2 }}', '{{ user_name_3 }}'] + + - name: set new database separator + set_fact: + separator: 'PASSWORD' + # Semantically: when mysql version >= 5.7 or MariaDB version >= 10.2 + when: db_version.version.major == 5 and db_version.version.minor >= 7 or db_version.version.major > 5 and db_version.version.major < 10 or db_version.version.major == 10 and db_version.version.minor >= 2 + + - block: + - name: assert user1 TLS requirements + assert: + that: + - "'SSL' in reqs" + vars: + - reqs: "{{((old_result.results[0] is skipped | ternary(new_result, old_result)).results | selectattr('item', 'contains', user_name_1) | first).stdout.split('REQUIRE')[1].split(separator)[0].strip()}}" + + - name: assert user2 TLS requirements + assert: + that: + - "'X509' in reqs" + vars: + - reqs: "{{((old_result.results[0] is skipped | ternary(new_result, old_result)).results | selectattr('item', 'contains', user_name_2) | first).stdout.split('REQUIRE')[1].split(separator)[0].strip()}}" + + - name: assert user3 TLS requirements + assert: + that: + - "'/CN=alice/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' in (reqs | select('contains', 'SUBJECT') | first)" + - "'/CN=org/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' in (reqs | select('contains', 'ISSUER') | first)" + - "'ECDHE-ECDSA-AES256-SHA384' in (reqs | select('contains', 'CIPHER') | first)" + vars: + - reqs: "{{((old_result.results[0] is skipped | ternary(new_result, old_result)).results | selectattr('item', 'contains', user_name_3) | first).stdout.split('REQUIRE')[1].split(separator)[0].replace(\"' \", \"':\").split(\":\")}}" + # CentOS 6 uses an older version of jinja that does not provide the selectattr filter. + when: ansible_distribution != 'CentOS' or ansible_distribution_major_version != '6' + + - name: modify user with TLS requirements state=present in check mode (expect changed=true) + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + password: '{{ user_password_1 }}' + tls_requires: + X509: + check_mode: yes + register: result + + - name: Assert check mode user update reports changed state + assert: + that: + - result is changed + + - name: retrieve TLS requiremets for users in old database version + command: "{{ mysql_command }} -L -N -s -e \"SHOW GRANTS for '{{ user_name_1 }}'@'localhost'\"" + register: old_result + when: db_version.version.major <= 5 and db_version.version.minor <= 6 or db_version.version.major == 10 and db_version.version.minor < 2 + + - name: retrieve TLS requiremets for users in new database version + command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ user_name_1 }}'@'localhost'\"" + register: new_result + when: db_version.version.major == 5 and db_version.version.minor >= 7 or db_version.version.major > 5 and db_version.version.major < 10 or db_version.version.major == 10 and db_version.version.minor >= 2 + + - name: assert user1 TLS requirements was not changed + assert: + that: "'SSL' in reqs" + vars: + - reqs: "{{(old_result is skipped | ternary(new_result, old_result)).stdout.split('REQUIRE')[1].split(separator)[0].strip()}}" + + - name: modify user with TLS requirements state=present (expect changed=true) + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + password: '{{ user_password_1 }}' + tls_requires: + X509: + + - name: retrieve TLS requiremets for users in old database version + command: "{{ mysql_command }} -L -N -s -e \"SHOW GRANTS for '{{ user_name_1 }}'@'localhost'\"" + register: old_result + when: db_version.version.major <= 5 and db_version.version.minor <= 6 or db_version.version.major == 10 and db_version.version.minor < 2 + + - name: retrieve TLS requiremets for users in new database version + command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ user_name_1 }}'@'localhost'\"" + register: new_result + when: db_version.version.major == 5 and db_version.version.minor >= 7 or db_version.version.major > 5 and db_version.version.major < 10 or db_version.version.major == 10 and db_version.version.minor >= 2 + + - name: assert user1 TLS requirements + assert: + that: "'X509' in reqs" + vars: + - reqs: "{{(old_result is skipped | ternary(new_result, old_result)).stdout.split('REQUIRE')[1].split(separator)[0].strip()}}" + + - name: remove TLS requiremets from user (expect changed=true) + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + password: '{{ user_password_1 }}' + tls_requires: + + - name: retrieve TLS requiremets for users in old database version + command: "{{ mysql_command }} -L -N -s -e \"SHOW GRANTS for '{{ user_name_1 }}'@'localhost'\"" + register: old_result + when: db_version.version.major <= 5 and db_version.version.minor <= 6 or db_version.version.major == 10 and db_version.version.minor < 2 + + - name: retrieve TLS requiremets for users in new database version + command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ user_name_1 }}'@'localhost'\"" + register: new_result + when: db_version.version.major == 5 and db_version.version.minor >= 7 or db_version.version.major > 5 and db_version.version.major < 10 or db_version.version.major == 10 and db_version.version.minor >= 2 + + - name: assert user1 TLS requirements + assert: + that: "'NONE' in reqs" + vars: + - reqs: "{{(old_result is skipped | ternary(new_result, old_result)).stdout.split('REQUIRE')[1].split(separator)[0].strip() | default('NONE') }}" + + - include: remove_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }} + + - include: remove_user.yml user_name={{user_name_2}} user_password={{ user_password_1 }} + + - include: remove_user.yml user_name={{user_name_3}} user_password={{ user_password_1 }} + + - include: assert_no_user.yml user_name={{user_name_1}} + + - include: assert_no_user.yml user_name={{user_name_2}} + + - include: assert_no_user.yml user_name={{user_name_3}} diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/defaults/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/defaults/main.yml new file mode 100644 index 00000000..6d0e2ec8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/defaults/main.yml @@ -0,0 +1,8 @@ +--- +# defaults file for test_mysql_variables +mysql_user: root +mysql_password: msandbox +mysql_primary_port: 3307 + +user_name_1: 'db_user1' +user_password_1: 'gadfFDSdtTU^Sdfuj' diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/meta/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/meta/main.yml new file mode 100644 index 00000000..f1174ff2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_mysql diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/tasks/assert_fail_msg.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/tasks/assert_fail_msg.yml new file mode 100644 index 00000000..4a840b9d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/tasks/assert_fail_msg.yml @@ -0,0 +1,25 @@ +# test code to assert message in mysql_variables module +# (c) 2014, Wayne Rosario <wrosario@ansible.com> + +# This file is part of Ansible +# +# Ansible 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 3 of the License, or +# (at your option) any later version. +# +# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>. + +# ============================================================ +# Assert message failure and confirm failed=true +# +- name: assert message failure (expect failed=true) + assert: + that: + - "output.failed == true" diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/tasks/assert_var.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/tasks/assert_var.yml new file mode 100644 index 00000000..5419f34b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/tasks/assert_var.yml @@ -0,0 +1,36 @@ +# test code to assert variables in mysql_variables module +# (c) 2014, Wayne Rosario <wrosario@ansible.com> + +# This file is part of Ansible +# +# Ansible 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 3 of the License, or +# (at your option) any later version. +# +# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>. + +# ============================================================ +# Assert mysql variable name and value from mysql database +# +- name: assert output message changed value + assert: + that: + - "output.changed == {{ changed }}" + +- name: run mysql command to show variable + command: "{{ mysql_command }} \"-e show variables like '{{ var_name }}'\"" + register: result + +- name: assert output mysql variable name and value + assert: + that: + - "result.changed == true" + - "'{{ var_name }}' in result.stdout" + - "'{{ var_value }}' in result.stdout" diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/tasks/assert_var_output.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/tasks/assert_var_output.yml new file mode 100644 index 00000000..f84a468b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/tasks/assert_var_output.yml @@ -0,0 +1,40 @@ +# test code to assert variables in mysql_variables module +# (c) 2014, Wayne Rosario <wrosario@ansible.com> + +# This file is part of Ansible +# +# Ansible 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 3 of the License, or +# (at your option) any later version. +# +# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>. + +# ============================================================ +# Assert output variable name/value match mysql variable name/value +# +- name: assert output message changed value + assert: + that: + - "output.changed == {{ changed }}" + +- set_fact: + key_name: "{{ var_name }}" + key_value: "{{ output.msg[0][0] }}" + +- name: run mysql command to show variable + command: "{{ mysql_command }} \"-e show variables like '{{var_name}}'\"" + register: result + +- name: assert output variable info match mysql variable info + assert: + that: + - "result.changed == true" + - "key_name in result.stdout" + - "key_value in result.stdout" diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml new file mode 100644 index 00000000..056771e2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/tasks/issue-28.yml @@ -0,0 +1,79 @@ +--- +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + + block: + + # ============================================================ + - shell: pip show pymysql | awk '/Version/ {print $2}' + register: pymysql_version + + - name: get server certificate + copy: + content: "{{ lookup('pipe', \"openssl s_client -starttls mysql -connect localhost:3307 -showcerts 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'\") }}" + dest: /tmp/cert.pem + delegate_to: localhost + + - name: Drop mysql user if exists + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + state: absent + ignore_errors: yes + + - name: create user with ssl requirement + mysql_user: + <<: *mysql_params + name: "{{ user_name_1 }}" + password: "{{ user_password_1 }}" + priv: '*.*:ALL,GRANT' + tls_requires: + SSL: + + - name: attempt connection with newly created user (expect failure) + mysql_variables: + variable: '{{ set_name }}' + login_user: '{{ user_name_1 }}' + login_password: '{{ user_password_1 }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + ca_cert: /tmp/cert.pem + register: result + ignore_errors: yes + + - assert: + that: + - result is failed + when: pymysql_version.stdout != "" + + - assert: + that: + - result is succeeded + when: pymysql_version.stdout == "" + + - name: attempt connection with newly created user ignoring hostname + mysql_variables: + variable: '{{ set_name }}' + login_user: '{{ user_name_1 }}' + login_password: '{{ user_password_1 }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + ca_cert: /tmp/cert.pem + check_hostname: no + register: result + ignore_errors: yes + + - assert: + that: + - result is succeeded or 'pymysql >= 0.7.11 is required' in result.msg + + - name: Drop mysql user + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + host: 127.0.0.1 + state: absent diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/tasks/main.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/tasks/main.yml new file mode 100644 index 00000000..9c4cd7d5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/tasks/main.yml @@ -0,0 +1,8 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- import_tasks: mysql_variables.yml + +- include: issue-28.yml diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml new file mode 100644 index 00000000..8a25849e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/integration/targets/test_mysql_variables/tasks/mysql_variables.yml @@ -0,0 +1,379 @@ +# test code for the mysql_variables module +# (c) 2014, Wayne Rosario <wrosario@ansible.com> + +# This file is part of Ansible +# +# Ansible 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 3 of the License, or +# (at your option) any later version. +# +# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>. + +# ============================================================ +# Verify mysql_variable successfully queries a variable +# +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + + block: + + - name: alias mysql command to include default options + set_fact: + mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} -P{{ mysql_primary_port }} --protocol=tcp" + + - set_fact: + set_name: 'version' + + - name: read mysql variables (expect changed=false) + mysql_variables: + <<: *mysql_params + variable: '{{ set_name }}' + register: result + + - include: assert_var_output.yml changed=false output={{ result }} var_name={{ set_name }} + + # ============================================================ + # Verify mysql_variable successfully updates a variable (issue:4568) + # + - set_fact: + set_name: 'delay_key_write' + set_value: 'ON' + + - name: set mysql variable + mysql_variables: + <<: *mysql_params + variable: '{{ set_name }}' + value: '{{ set_value }}' + + - name: update mysql variable to same value (expect changed=false) + mysql_variables: + <<: *mysql_params + variable: '{{ set_name }}' + value: '{{ set_value }}' + register: result + + - include: assert_var.yml changed=false output={{ result }} var_name={{ set_name }} var_value={{ set_value }} + + # ============================================================ + # Verify mysql_variable successfully updates a variable using single quotes + # + - set_fact: + set_name: 'wait_timeout' + set_value: '300' + + - name: set mysql variable to a temp value + mysql_variables: + <<: *mysql_params + variable: '{{ set_name }}' + value: '200' + + - name: update mysql variable value (expect changed=true) + mysql_variables: + <<: *mysql_params + variable: '{{ set_name }}' + value: '{{ set_value }}' + register: result + + - assert: + that: + - result.queries == ["SET GLOBAL `{{ set_name }}` = {{ set_value }}"] + + - include: assert_var.yml changed=true output={{ result }} var_name={{ set_name }} var_value='{{ set_value }}' + + # ============================================================ + # Verify mysql_variable successfully updates a variable using double quotes + # + - set_fact: + set_name: "wait_timeout" + set_value: "400" + + - name: set mysql variable to a temp value + mysql_variables: + <<: *mysql_params + variable: '{{ set_name }}' + value: "200" + + - name: update mysql variable value (expect changed=true) + mysql_variables: + <<: *mysql_params + variable: '{{ set_name }}' + value: '{{ set_value }}' + register: result + + - include: assert_var.yml changed=true output={{ result }} var_name={{ set_name }} var_value='{{ set_value }}' + + # ============================================================ + # Verify mysql_variable successfully updates a variable using no quotes + # + - set_fact: + set_name: wait_timeout + set_value: 500 + + - name: set mysql variable to a temp value + mysql_variables: + <<: *mysql_params + variable: '{{ set_name }}' + value: 200 + + - name: update mysql variable value (expect changed=true) + mysql_variables: + <<: *mysql_params + variable: '{{ set_name }}' + value: '{{ set_value }}' + register: result + + - include: assert_var.yml changed=true output={{ result }} var_name={{ set_name }} var_value='{{ set_value }}' + + # ============================================================ + # Verify mysql_variable successfully updates a variable using an expression (e.g. 1024*4) + # + - name: set mysql variable value to an expression + mysql_variables: + <<: *mysql_params + variable: max_connect_errors + value: "1024*4" + register: result + ignore_errors: true + + - include: assert_fail_msg.yml output={{ result }} msg='Incorrect argument type to variable' + + # ============================================================ + # Verify mysql_variable fails when setting an incorrect value (out of range) + # + - shell: pip show pymysql | awk '/Version/ {print $2}' + register: pymysql_version + + - name: set mysql variable value to a number out of range + mysql_variables: + <<: *mysql_params + variable: max_connect_errors + value: -1 + register: oor_result + ignore_errors: true + + - include: assert_var.yml changed=true output={{ oor_result }} var_name=max_connect_errors var_value=1 + when: pymysql_version.stdout == "" + + - include: assert_fail_msg.yml output={{ oor_result }} msg='Truncated incorrect' + when: pymysql_version.stdout != "" + + # ============================================================ + # Verify mysql_variable fails when setting an incorrect value (incorrect type) + # + - name: set mysql variable value to a non-valid value number + mysql_variables: + <<: *mysql_params + variable: max_connect_errors + value: TEST + register: nvv_result + ignore_errors: true + + - include: assert_fail_msg.yml output={{ nvv_result }} msg='Incorrect argument type to variable' + + # ============================================================ + # Verify mysql_variable fails when setting an unknown variable + # + - name: set a non mysql variable + mysql_variables: + <<: *mysql_params + variable: my_sql_variable + value: ON + register: result + ignore_errors: true + + - include: assert_fail_msg.yml output={{ result }} msg='Variable not available' + + # ============================================================ + # Verify mysql_variable fails when setting a read-only variable + # + - name: set value of a read only mysql variable + mysql_variables: + <<: *mysql_params + variable: character_set_system + value: utf16 + register: result + ignore_errors: true + + - include: assert_fail_msg.yml output={{ result }} msg='read only variable' + + #============================================================= + # Verify mysql_variable works with the login_user and login_password parameters + # + - set_fact: + set_name: wait_timeout + set_value: 77 + + - name: query mysql_variable using login_user and password_password + mysql_variables: + <<: *mysql_params + variable: '{{ set_name }}' + register: result + + - include: assert_var_output.yml changed=false output={{ result }} var_name={{ set_name }} + + - name: set mysql variable to temp value using user login and password (expect changed=true) + mysql_variables: + <<: *mysql_params + variable: '{{ set_name }}' + value: 20 + register: result + + - name: update mysql variable value using user login and password (expect changed=true) + mysql_variables: + <<: *mysql_params + variable: '{{set_name}}' + value: '{{set_value}}' + register: result + + - include: assert_var.yml changed=true output={{result}} var_name={{set_name}} var_value='{{set_value}}' + + #============================================================ + # Verify mysql_variable fails with an incorrect login_password parameter + # + - set_fact: + set_name: connect_timeout + set_value: 10 + + - name: query mysql_variable using incorrect login_password + mysql_variables: + login_user: '{{ mysql_user }}' + login_password: 'wrongpassword' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + variable: '{{ set_name }}' + register: result + ignore_errors: true + + - include: assert_fail_msg.yml output={{ result }} msg='unable to connect to database' + + - name: update mysql variable value using incorrect login_password (expect failed=true) + mysql_variables: + login_user: '{{ mysql_user }}' + login_password: 'wrongpassword' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + variable: '{{ set_name }}' + value: '{{ set_value }}' + register: result + ignore_errors: true + + - include: assert_fail_msg.yml output={{ result }} msg='unable to connect to database' + + #============================================================ + # Verify mysql_variable fails with an incorrect login_host parameter + # + - name: query mysql_variable using incorrect login_host + mysql_variables: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: '12.0.0.9' + login_port: '{{ mysql_primary_port }}' + variable: wait_timeout + connect_timeout: 5 + register: result + ignore_errors: true + + - include: assert_fail_msg.yml output={{ result }} msg='unable to connect to database' + + - block: + + #========================================= + # Check mode 'persist' and 'persist_only': + # + - name: update mysql variable value (expect changed=true) in persist mode + mysql_variables: + <<: *mysql_params + variable: '{{ set_name }}' + value: '{{ set_value }}' + mode: persist + register: result + + - assert: + that: + - result.queries == ["SET PERSIST `{{ set_name }}` = {{ set_value }}"] + + - include: assert_var.yml changed=true output={{ result }} var_name={{ set_name }} var_value='{{ set_value }}' + + - name: try to update mysql variable value (expect changed=false) in persist mode again + mysql_variables: + <<: *mysql_params + variable: '{{ set_name }}' + value: '{{ set_value }}' + mode: persist + register: result + + - include: assert_var.yml changed=false output={{ result }} var_name={{ set_name }} var_value='{{ set_value }}' + + - name: set mysql variable to a temp value + mysql_variables: + <<: *mysql_params + variable: '{{ set_name }}' + value: '200' + mode: persist + + - name: update mysql variable value (expect changed=true) in persist_only mode + mysql_variables: + <<: *mysql_params + variable: '{{ set_name }}' + value: '{{ set_value }}' + mode: persist_only + register: result + + - assert: + that: + - result is changed + - result.queries == ["SET PERSIST_ONLY `{{ set_name }}` = {{ set_value }}"] + + - name: try to update mysql variable value (expect changed=false) in persist_only mode again + mysql_variables: + <<: *mysql_params + variable: '{{ set_name }}' + value: '{{ set_value }}' + mode: persist_only + register: result + + - assert: + that: + - result is not changed + + - set_fact: + set_name: max_connections + set_value: 105 + def_val: 151 + + - name: update mysql variable value (expect changed=true) in persist_only mode + mysql_variables: + <<: *mysql_params + variable: '{{ set_name }}' + value: '{{ set_value }}' + mode: persist_only + register: result + + - include: assert_var.yml changed=true output={{ result }} var_name={{ set_name }} var_value='{{ def_val }}' + + when: mysql_version is not version('8.0', '<') + + # Bugfix of https://github.com/ansible/ansible/issues/54239 + # - name: set variable containing dot + # mysql_variables: + # <<: *mysql_params + # variable: validate_password.policy + # value: LOW + # mode: persist_only + # register: result + # + # - assert: + # that: + # - result is changed + # - result.queries == ["SET PERSIST_ONLY `validate_password`.`policy` = LOW"] diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/sanity/ignore-2.10.txt b/collections-debian-merged/ansible_collections/community/mysql/tests/sanity/ignore-2.10.txt new file mode 100644 index 00000000..c0323aff --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/sanity/ignore-2.10.txt @@ -0,0 +1,8 @@ +plugins/modules/mysql_db.py validate-modules:doc-elements-mismatch +plugins/modules/mysql_db.py validate-modules:parameter-list-no-elements +plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen +plugins/modules/mysql_info.py validate-modules:doc-elements-mismatch +plugins/modules/mysql_info.py validate-modules:parameter-list-no-elements +plugins/modules/mysql_query.py validate-modules:parameter-list-no-elements +plugins/modules/mysql_user.py validate-modules:undocumented-parameter +plugins/modules/mysql_variables.py validate-modules:doc-required-mismatch diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/sanity/ignore-2.11.txt b/collections-debian-merged/ansible_collections/community/mysql/tests/sanity/ignore-2.11.txt new file mode 100644 index 00000000..c0323aff --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/sanity/ignore-2.11.txt @@ -0,0 +1,8 @@ +plugins/modules/mysql_db.py validate-modules:doc-elements-mismatch +plugins/modules/mysql_db.py validate-modules:parameter-list-no-elements +plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen +plugins/modules/mysql_info.py validate-modules:doc-elements-mismatch +plugins/modules/mysql_info.py validate-modules:parameter-list-no-elements +plugins/modules/mysql_query.py validate-modules:parameter-list-no-elements +plugins/modules/mysql_user.py validate-modules:undocumented-parameter +plugins/modules/mysql_variables.py validate-modules:doc-required-mismatch diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/sanity/ignore-2.9.txt b/collections-debian-merged/ansible_collections/community/mysql/tests/sanity/ignore-2.9.txt new file mode 100644 index 00000000..dabd55d3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/sanity/ignore-2.9.txt @@ -0,0 +1,3 @@ +plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen +plugins/modules/mysql_user.py validate-modules:parameter-type-not-in-doc +plugins/modules/mysql_user.py validate-modules:undocumented-parameter diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/unit/plugins/module_utils/__init__.py b/collections-debian-merged/ansible_collections/community/mysql/tests/unit/plugins/module_utils/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/unit/plugins/module_utils/__init__.py diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/unit/plugins/module_utils/test_mysql.py b/collections-debian-merged/ansible_collections/community/mysql/tests/unit/plugins/module_utils/test_mysql.py new file mode 100644 index 00000000..ac4de24f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/unit/plugins/module_utils/test_mysql.py @@ -0,0 +1,24 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from ansible_collections.community.mysql.plugins.module_utils.mysql import get_server_version +from ..utils import dummy_cursor_class + + +@pytest.mark.parametrize( + 'cursor_return_version,cursor_return_type', + [ + ('5.7.0-mysql', 'dict'), + ('8.0.0-mysql', 'list'), + ('10.5.0-mariadb', 'dict'), + ('10.5.1-mariadb', 'list'), + ] +) +def test_get_server_version(cursor_return_version, cursor_return_type): + """ + Test that server versions are handled properly by get_server_version() whether they're returned as a list or dict. + """ + cursor = dummy_cursor_class(cursor_return_version, cursor_return_type) + assert get_server_version(cursor) == cursor_return_version diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/unit/plugins/modules/__init__.py b/collections-debian-merged/ansible_collections/community/mysql/tests/unit/plugins/modules/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/unit/plugins/modules/__init__.py diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/unit/plugins/modules/test_mysql_replication.py b/collections-debian-merged/ansible_collections/community/mysql/tests/unit/plugins/modules/test_mysql_replication.py new file mode 100644 index 00000000..27473b74 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/unit/plugins/modules/test_mysql_replication.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru> + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from ansible_collections.community.mysql.plugins.modules.mysql_replication import uses_replica_terminology +from ..utils import dummy_cursor_class + + +@pytest.mark.parametrize( + 'f_output,c_output,c_ret_type', + [ + (False, '5.5.1-mysql', 'list'), + (False, '5.7.0-mysql', 'dict'), + (False, '8.0.0-mysql', 'list'), + (False, '8.0.11-mysql', 'dict'), + (False, '8.0.21-mysql', 'list'), + (False, '10.5.0-mariadb', 'dict'), + (True, '8.0.22-mysql', 'list'), + (True, '8.1.2-mysql', 'dict'), + (True, '9.0.0-mysql', 'list'), + (True, '10.5.1-mariadb', 'dict'), + (True, '10.6.0-mariadb', 'dict'), + (True, '11.5.1-mariadb', 'dict'), + ] +) +def test_uses_replica_terminology(f_output, c_output, c_ret_type): + cursor = dummy_cursor_class(c_output, c_ret_type) + assert uses_replica_terminology(cursor) == f_output diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/unit/plugins/modules/test_mysql_user.py b/collections-debian-merged/ansible_collections/community/mysql/tests/unit/plugins/modules/test_mysql_user.py new file mode 100644 index 00000000..2dae9c59 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/unit/plugins/modules/test_mysql_user.py @@ -0,0 +1,33 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from ansible_collections.community.mysql.plugins.modules.mysql_user import supports_identified_by_password +from ..utils import dummy_cursor_class + + +@pytest.mark.parametrize( + 'function_return,cursor_output,cursor_ret_type', + [ + (True, '5.5.1-mysql', 'list'), + (True, '5.7.0-mysql', 'dict'), + (True, '10.5.0-mariadb', 'dict'), + (True, '10.5.1-mariadb', 'dict'), + (True, '10.6.0-mariadb', 'dict'), + (True, '11.5.1-mariadb', 'dict'), + (False, '8.0.22-mysql', 'list'), + (False, '8.1.2-mysql', 'dict'), + (False, '9.0.0-mysql', 'list'), + (False, '8.0.0-mysql', 'list'), + (False, '8.0.11-mysql', 'dict'), + (False, '8.0.21-mysql', 'list'), + ] +) +def test_supports_identified_by_password(function_return, cursor_output, cursor_ret_type): + """ + Tests whether 'CREATE USER %s@%s IDENTIFIED BY PASSWORD %s' is supported, which is currently supported by everything + besides MySQL >= 8.0. + """ + cursor = dummy_cursor_class(cursor_output, cursor_ret_type) + assert supports_identified_by_password(cursor) == function_return diff --git a/collections-debian-merged/ansible_collections/community/mysql/tests/unit/plugins/utils.py b/collections-debian-merged/ansible_collections/community/mysql/tests/unit/plugins/utils.py new file mode 100644 index 00000000..7712d1cd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/mysql/tests/unit/plugins/utils.py @@ -0,0 +1,19 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +class dummy_cursor_class(): + """Dummy class for returning an answer for SELECT VERSION().""" + def __init__(self, output, ret_val_type='dict'): + self.output = output + self.ret_val_type = ret_val_type + + def execute(self, query): + pass + + def fetchone(self): + if self.ret_val_type == 'dict': + return {'version': self.output} + + elif self.ret_val_type == 'list': + return [self.output] |