diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:03:42 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:03:42 +0000 |
commit | 66cec45960ce1d9c794e9399de15c138acb18aed (patch) | |
tree | 59cd19d69e9d56b7989b080da7c20ef1a3fe2a5a /ansible_collections/sensu | |
parent | Initial commit. (diff) | |
download | ansible-66cec45960ce1d9c794e9399de15c138acb18aed.tar.xz ansible-66cec45960ce1d9c794e9399de15c138acb18aed.zip |
Adding upstream version 7.3.0+dfsg.upstream/7.3.0+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/sensu')
379 files changed, 33834 insertions, 0 deletions
diff --git a/ansible_collections/sensu/sensu_go/.circleci/config.yml b/ansible_collections/sensu/sensu_go/.circleci/config.yml new file mode 100644 index 00000000..96612ff4 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/.circleci/config.yml @@ -0,0 +1,206 @@ +version: "2.1" + +workflows: + version: 2 + main_workflow: + jobs: + - sanity_test: + matrix: &matrix + parameters: + # ansible is branch name in ansible/ansible git repo + ansible: + - stable-2.9 + - stable-2.10 + - stable-2.11 + - stable-2.12 + - stable-2.13 + - stable-2.14 + + + - unit_test: + matrix: *matrix + + - integration_test_git: + requires: + - sanity_test + - unit_test + matrix: *matrix + + - integration_test_galaxy: + filters: { branches: { only: [ stable ] } } + requires: + - integration_test_git + matrix: *matrix + + cron_master: + triggers: + - schedule: + cron: "12 5 * * 0,3" + filters: { branches: { only: [ master ] } } + jobs: + - integration_test_git: + matrix: *matrix + + cron_released: + triggers: + - schedule: + cron: "12 5 * * 1,4" + filters: { branches: { only: [ stable ] } } + jobs: + - integration_test_galaxy: + matrix: *matrix + + cron_ansible_devel: + triggers: + - schedule: + cron: "12 5 * * 2,5" + filters: { branches: { only: [ master ] } } + jobs: + - sanity_test: + matrix: &devel-matrix + parameters: + ansible: [ devel ] + + - unit_test: + matrix: *devel-matrix + + - integration_test_git: + requires: + - sanity_test + - unit_test + matrix: *devel-matrix + + windows_version_check: + triggers: + - schedule: + cron: "12 3 * * 0,2,4" + filters: { branches: { only: [ master ] } } + jobs: + - windows_version_check + +jobs: + sanity_test: + parameters: + ansible: + description: Ansible version to use + type: string + machine: &ci-machine + image: ubuntu-2004:202101-01 + working_directory: ~/ansible_collections/sensu/sensu_go + steps: + - wrapper: + ansible: << parameters.ansible >> + kind: sanity + test_commands: + - run: make sanity + + unit_test: + parameters: + ansible: + description: Ansible version to use + type: string + machine: *ci-machine + working_directory: ~/ansible_collections/sensu/sensu_go + steps: + - wrapper: + ansible: << parameters.ansible >> + kind: sanity + test_commands: + - run: make units + - store_artifacts: + path: tests/output/reports/coverage + destination: coverage-report + - store_test_results: + path: tests/output/junit + + integration_test_git: + parallelism: 6 + parameters: + ansible: + description: Ansible version to use + type: string + machine: *ci-machine + working_directory: ~/sensu_go + steps: + - wrapper: + ansible: << parameters.ansible >> + kind: integration + test_commands: + - run: ansible-galaxy collection build + - run: ansible-galaxy collection install sensu-sensu_go-*.tar.gz + - run_integration_tests + + integration_test_galaxy: + parallelism: 6 + parameters: + ansible: + description: Ansible version to use + type: string + machine: *ci-machine + working_directory: ~/sensu_go + steps: + - wrapper: + ansible: << parameters.ansible >> + kind: integration + test_commands: + - run: | + ansible-galaxy collection install \ + sensu.sensu_go:$(grep version: galaxy.yml | cut -d" " -f2) + - run_integration_tests + + windows_version_check: + docker: + - image: cimg/python:3.10.6 + steps: + - checkout + - run: pip3 install pyyaml + - run: make check_windows_versions + +commands: + run_integration_tests: + description: Run integration tests + steps: + - run: ansible-galaxy collection install community.docker + - run: + name: Display scheduled scenarios + command: | + circleci tests glob "tests/integration/molecule/*/molecule.yml" \ + | circleci tests split --split-by=timings + - run: make integration_ci + - store_test_results: + path: test_results + - store_artifacts: + path: test_results + + wrapper: + description: Wrapper command that takes care of venv caching + parameters: + ansible: + description: Ansible version to install + type: string + kind: + description: Test kind (used to construct cache name) + type: string + test_commands: + description: Test commands to execute + type: steps + steps: + - checkout: { path: . } + - run: + name: Generate cache id file + command: | + rm -f cache-id.txt + echo "week $(date +%V)" >> cache-id.txt + echo "ansible << parameters.ansible >>" >> cache-id.txt + echo "kind << parameters.kind >>" >> cache-id.txt + echo "cache busting string 2" >> cache-id.txt + - restore_cache: + key: '{{ checksum "cache-id.txt" }}' + - run: + name: Install Ansible + command: pip3 install -U https://github.com/ansible/ansible/archive/<< parameters.ansible >>.tar.gz --disable-pip-version-check + - steps: << parameters.test_commands >> + - save_cache: + key: '{{ checksum "cache-id.txt" }}' + paths: + - "~/venv" diff --git a/ansible_collections/sensu/sensu_go/.flake8 b/ansible_collections/sensu/sensu_go/.flake8 new file mode 100644 index 00000000..c54d5453 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/.flake8 @@ -0,0 +1,22 @@ +# This configuration mirrors the configuration that is used when linting +# collections on import into Ansible Galaxy. + +# TODO(@tadeboro): Some of our lines are almost twice the optimal reading +# length of 60-80 chars. It would be great to get line lengths down to 79, but +# this is not feasible at the moment since we have cca. 300 lines that are +# longer. What we need is a way of preventing new long lines of getting into +# the codebase, which means running flake8 with stricter rules on changed +# lines only. + +[flake8] +max-line-length = 144 +max-doc-length = 92 + +exclude = + # ansible-test creates all sorts of temporary stuff that we do not care + # about. + tests/output + +per-file-ignores = + # Modules have their imports listed after the metadata. + plugins/modules/*.py:E402 diff --git a/ansible_collections/sensu/sensu_go/.gitattributes b/ansible_collections/sensu/sensu_go/.gitattributes new file mode 100644 index 00000000..a19ade07 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/.gitattributes @@ -0,0 +1 @@ +CHANGELOG.md merge=union diff --git a/ansible_collections/sensu/sensu_go/.github/ISSUE_TEMPLATE/bug_report.md b/ansible_collections/sensu/sensu_go/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..5fb34016 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Bug description** +A clear and concise description of what the issue is. + +**How to reproduce the bug** +1. Run the command `ansible-playbook -i inv ...`. + +**If applicable, content of the files used in commands from the previous step** +For example, `inv` file from previous sample could contain: +``` +[mygroup] +10.5.126.31 +``` + +**Expected result** +A clear and concise description of what you expected to happen. + +**Actual result** +A clear and concise description of what actually happened. Make sure you +include any error messages, since they usually offer vital clues to the +bug-fixing process. + + +**Component versions** + - Ansible Version [e.g. 2.9.0] + - Sensu Go Collection Version [e.g. 0.7.5] + - Sensu Go Backend Version [e.g. 5.14.1] + +**Additional context** +Add any other context about the problem here. diff --git a/ansible_collections/sensu/sensu_go/.github/ISSUE_TEMPLATE/feature_request.md b/ansible_collections/sensu/sensu_go/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..4fe86d5e --- /dev/null +++ b/ansible_collections/sensu/sensu_go/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: feature +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/ansible_collections/sensu/sensu_go/.gitignore b/ansible_collections/sensu/sensu_go/.gitignore new file mode 100644 index 00000000..8e935789 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/.gitignore @@ -0,0 +1,9 @@ +tests/output +tests/runner +docs/build +docs/source/modules +test_results +.pytest_cache +*.pyc +*.swp +.vagrant diff --git a/ansible_collections/sensu/sensu_go/CODE_OF_CONDUCT.md b/ansible_collections/sensu/sensu_go/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..1c1f75cb --- /dev/null +++ b/ansible_collections/sensu/sensu_go/CODE_OF_CONDUCT.md @@ -0,0 +1,207 @@ +# Overview + +As a member of the Sensu community, we ask that you keep the following in mind: + + * Be kind and welcoming to others. + * Respect personal space and ask before you enter it. Remember not + everyone likes to be touched or hugged or joked with. + * Be mindful of language. Do not disparage others. Listen more than you talk. + * Remember that harassment and sexist, racist comments or behavior are not + appropriate in our Community. + +Sensu in-person and online events are intended for networking and +collaboration in the monitoring community. Attendees violating these rules may +be asked to leave any event we host without a refund at the sole discretion of +the event organizers. + +The same rules apply when participating in other Sensu-related forums and +mailing lists, contributing to code and documentation, and in private +correspondence in Sensu-related space. + +Thank you for helping make this a welcoming place for all. + + +## Important numbers + +Conference staff is happy to help participants contact venue security or local +law enforcement, provide escorts, or otherwise assist those experiencing +harassment to feel safe for the duration of the event. We value your +attendance. + +If your physical safety or the physical safety of others is at risk, please +dial or text emergency number (911 in Americas, 112 in Europe, see [wiki][] +page for more numbers). For other needs, contact the relevant authorities +below: + + [wiki]: + https://en.wikipedia.org/wiki/List_of_emergency_telephone_numbers + (List of emergency numbers) + +**Code of Conduct team** +The Sensu Community team +community@sensu.io +[+1 855-997-3678 ext 510](tel:+18559973678,,510) + + +# Full Code of Conduct + +In order to foster an inclusive, kind, harassment-free, and cooperative +community, Sensu enforces this code of conduct on our software projects and +events. To make clear what is expected, all delegates/attendees, speakers, +exhibitors, organizers, and volunteers at any Sensu activity are required to +read and follow this Code of Conduct. Same rules apply to online activities +such as opening pull requests, submitting issues, and posting content on Sensu +forums. + + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we, as +contributors and maintainers, pledge to making participation in our project +and our community a harassment-free experience for everyone, regardless of +age, body size, disability, ethnicity, gender identity and expression, level +of experience, nationality, personal appearance, race, religion, or sexual +identity and orientation. + + +## Enforcement + +Participants asked to stop any harassing behavior are expected to comply +immediately. + +At events, exhibitors in the expo hall, sponsor or vendor booths, or similar +activities are also subject to the anti-harassment policy. In particular, +exhibitors should never use sexualized images, activities, or other material. +Booth staff (including volunteers) should not use sexualized +clothing/uniforms/costumes, or otherwise create a sexualized environment. + +If a participant engages in behavior that violates this code of conduct, the +event organizers, project maintainers, or forum administrators will take any +action they deem appropriate, including warning the offender, expulsion from +the event with no refund, or blocking access to repository. + + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + + * Using welcoming and inclusive language + * Being respectful of differing viewpoints and experiences + * Gracefully accepting constructive criticism + * Focusing on what is best for the community + * Showing empathy towards other community members + +Examples of behavior that contributes to creating a negative environment +include: + + * The use of sexualized language or imagery and unwelcome sexual attention + or advances + * Trolling, insulting/derogatory comments, and personal or political attacks + * Public or private harassment + * Publishing others' private information, such as a physical or electronic + address, without explicit permission + +Other conduct which could reasonably be considered inappropriate in a +professional setting All communication should be appropriate for a +professional audience, including people of many different backgrounds. Sexual +language and imagery is not appropriate at any time in our community, +including presentations. + + +## Our Responsibilities + +Project contributors (including maintainers) are responsible for clarifying +the standards of acceptable behavior and are expected to take appropriate and +fair corrective action in response to any instances of unacceptable behavior. + +Project contributors have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an +appointed representative at an online or offline event. Representation of a +project may be further defined and clarified by project maintainers. + + +## Recommendations + +The team leading the Sensu Community and enforcing its Code of Conduct +recognize that most participants mean well, but may not be familiar with +behaviors that can harm others unintentionally. The following includes +recommendations to be effective and thoughtful participants. + + +### Be kind to others + +Kindness is about providing people what they need. It's best to start by +listening to others more than you speak. From there, it is all about providing +people with a sense that they are welcome. Here are some ideas of how to do +that: invite someone to your lunch table; smile at an attendee; introduce +yourself to someone new; and sit with folks you've never met; answer forum +questions politely and link to relevant documentation; help first time +contributors follow developer guides; say thank you when code contribution is +accepted. Encourage others to share. + + +### Respect personal space + +Respect others' boundaries. Respect personal space and ask before you enter +it. Remember not everyone likes to be touched or hugged. If you are a hugger, +it is respectful to directly ask ("I'm a hugger. Would you like a hug?"). + + +### Beware of gendered double standards + +Be conscious of your behavior reinforcing one set of introductions for women +and a different set for men. If you shake people's hands when you meet them, +offer to do so consistently regardless of gender. If you hug people when you +meet them, offer to do so consistently regardless of gender. + + +### Be respectful + +Not all of us will agree all the time, but disagreement is no excuse for poor +behavior and poor manners. This is doubly important in online communication +where it is easy to forget that we are interaction with another human being. +We do not tolerate harassment of event participants in any form. + +Harassment includes: offensive verbal comments related to gender, gender +identity, sexual orientation, disability, physical appearance, body size, +race, religion; sexual images in public spaces; deliberate intimidation, +stalking, or following; harassing photography or recording; sustained +disruption of talks or other events; unwanted physical contact; and unwelcome +sexual attention. + + +### Choose your words + +Be thoughtful with the words you choose. Remember that sexist, racist, and +other exclusionary jokes can be offensive to those around you. Excessive +swearing can be off-putting to those around you. When you want to bring up +topics that may relate to past trauma, mention the appropriate trigger/content +warnings (if necessary, read more about trigger warnings). + + +### Photography + +It's always nice to ask before taking a photo. If someone does not want to be +photographed, video or audio recorded, please respect their wishes. + + +## License + +This Code of Conduct began as an adaptation of Contributor Covenant, version +1.4, available at +https://www.contributor-covenant.org/version/1/4/code-of-conduct.html. It was +influenced by Geek Feminism wiki, created by the Ada Initiative under a +Creative Commons Zero license. We also used language about events from the +Conference Code of Conduct by 2017.djangocon.us/coc/. diff --git a/ansible_collections/sensu/sensu_go/COPYING b/ansible_collections/sensu/sensu_go/COPYING new file mode 100644 index 00000000..10926e87 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/COPYING @@ -0,0 +1,675 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://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 <http://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 +<http://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 +<http://www.gnu.org/philosophy/why-not-lgpl.html>. + diff --git a/ansible_collections/sensu/sensu_go/FILES.json b/ansible_collections/sensu/sensu_go/FILES.json new file mode 100644 index 00000000..953d4bec --- /dev/null +++ b/ansible_collections/sensu/sensu_go/FILES.json @@ -0,0 +1,3393 @@ +{ + "files": [ + { + "name": ".", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".circleci", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".circleci/config.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5374567ce315af2036683dfb61c105ae52e9242145eeef5a8e7fce24f5866c93", + "format": 1 + }, + { + "name": ".flake8", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1d14da49e3dc70a10ecb9132d3bc0c1a54e5dac2f28b9e712924e6b33033cfb4", + "format": 1 + }, + { + "name": ".gitattributes", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eda83702f1ee1fc2e4f7252ac946830797b83eb36aeb4ccff6133ab2b60c3148", + "format": 1 + }, + { + "name": ".github", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".github/ISSUE_TEMPLATE", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".github/ISSUE_TEMPLATE/bug_report.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1cbd84cd477255ed9c742dfff3b3e6d224c105e1a291aa5942f1670597afb011", + "format": 1 + }, + { + "name": ".github/ISSUE_TEMPLATE/feature_request.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "aa20f06cc398fde7b9ce6be01f4d39f7c5b6e8efbaf40b229983f994d5f5e49e", + "format": 1 + }, + { + "name": ".gitignore", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f7a82b393b6c5cb3f95294240659749465a5b004323671a41d367f3471b617ae", + "format": 1 + }, + { + "name": "CODE_OF_CONDUCT.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5625beeb80d7cca3f26f389bd847dc23acd6a29877d2f35cb5c33037721835e7", + "format": 1 + }, + { + "name": "COPYING", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0ae0485a5bd37a63e63603596417e4eb0e653334fa6c7f932ca3a0e85d4af227", + "format": 1 + }, + { + "name": "Makefile", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "aee376b38b859d8d33a6e63e1bc99297d0f6e2772a62381ba961143ec9718cb7", + "format": 1 + }, + { + "name": "README.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "25185d310c91a95e9ef28fbf2a9936fcb00b6f4f1851e1ee4cec84b5bc8e43fd", + "format": 1 + }, + { + "name": "changelogs", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "changelogs/changelog.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "49a5c005a5bf7c0cbf04e11acb92c00291d73d78ad419b6613082ff5658afad2", + "format": 1 + }, + { + "name": "collection.requirements", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "06447fa81cc36d2bb270ccc7584d6fcd7adf61e700cd456642ce86d82ba4a8d2", + "format": 1 + }, + { + "name": "docker", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "docker/alma-8.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "77faf407d99d19572bee379185ecc6e1db6ca09b24a8931c5ba2187efdfe6c7b", + "format": 1 + }, + { + "name": "docker/amazon-1.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e6423e2dc42361bdd99cf66638ef484b52052fb90c4e06425079f42a36a8f4e0", + "format": 1 + }, + { + "name": "docker/amazon-2.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0539425ff1396178a71e179b79281693c0a76e55f9b77baaef5313f75ed9a5b4", + "format": 1 + }, + { + "name": "docker/build-all.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d07218d70fff8b732f66a1fba749dbdcdd44e6cacb5f82bb3c7df4145cbd3266", + "format": 1 + }, + { + "name": "docker/build.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f5e11180ff7a8271e6e2312bffeb8d0f9530fde88056cc7101e14dff3e7ab770", + "format": 1 + }, + { + "name": "docker/centos-6.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f17b5627098349ba0b837f0325bae74cf1a2026c889fa1a2b8451bf278cfc110", + "format": 1 + }, + { + "name": "docker/centos-7.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "68c31a313c8d77cce02ca852fecba959afce224d76fa3e146a945c79b62eac0f", + "format": 1 + }, + { + "name": "docker/centos-8.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0b90bd28a61d8ac05908c717c28793626cee7071d30a7a29425f81d0afcb614e", + "format": 1 + }, + { + "name": "docker/debian-10.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a6189f24a19571821e78a20a356e77f8d2f3741b028e9c16aa449425e3088f05", + "format": 1 + }, + { + "name": "docker/debian-9.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f80ce3d252420117d1621e76c50896cc8dd1a50ca7eb05c9690cc414ce19c6fc", + "format": 1 + }, + { + "name": "docker/oracle-8.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0e2fe59fd5ecb49f0c28ce2d9549f2864713797abd8240a64f4477991bbef19e", + "format": 1 + }, + { + "name": "docker/redhat-7.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4f9601854dbad9e17e81ab915e09c8fa9c2dc2c1d255c7d9bfa47b387b6d09af", + "format": 1 + }, + { + "name": "docker/rocky-8.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4aedcd2b720be1d1af27970a083a3109764c4aca9a831cbbfc3b1550937bdb1a", + "format": 1 + }, + { + "name": "docker/sensu-5.14.2.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fb1fdf845b77606d01c4ac3b39fb57158bf8a36fb0be68c39e225923b7f10ff5", + "format": 1 + }, + { + "name": "docker/sensu-5.21.3.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5e9ccfbd7ed743d543502484133da23bc7be30a0fc7cce67a7c5b16717b30a28", + "format": 1 + }, + { + "name": "docker/sensu-6.2.5.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7b422c63badc1b329b408d124e36f08061502d084e52e1dd2f473f4dec417800", + "format": 1 + }, + { + "name": "docker/sensu-6.3.0.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2e4910df1e60432b26474e0fc5c03331a9f0180bdba76955f9565d8f40164736", + "format": 1 + }, + { + "name": "docker/sensu-6.4.3.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9ae90603dcdb1bee5212c5fe2f605ee7d5238b38ef50e543920d0d0fcff74e11", + "format": 1 + }, + { + "name": "docker/sensu-6.5.5.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4c1e1a675e932a2400cfdcf08c90ce0e36ca1324de25b13f03e1aa2ca6156ce9", + "format": 1 + }, + { + "name": "docker/sensu-6.6.2.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5eb25649c23ada4244ec7e0d9bc007946290f4531d73680930643195449e9a48", + "format": 1 + }, + { + "name": "docker/sensu-6.7.5.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "03ed4b823631f289922f92528544c21442923cbe6ac3bacc504b8aac398e8ef1", + "format": 1 + }, + { + "name": "docker/sensu-6.8.2.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5d6459d0af5f7ea823c9a9017184db7064a29792e43dee086af6d5582fc58e59", + "format": 1 + }, + { + "name": "docker/sensu-6.9.0.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b06b8318cad1be3eeba445f8976590662f8014e66b61ff6413d566c9df52ad9a", + "format": 1 + }, + { + "name": "docker/ubuntu-14.04.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d14b43d0e006b82b3242dbf025a43bbb63ff63eedd6b212417670b75fea75de7", + "format": 1 + }, + { + "name": "docker/ubuntu-16.04.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7b974549f85535a35f5e774969072d924762b970ff1e795b7d8ec70826ec9a08", + "format": 1 + }, + { + "name": "docker/ubuntu-18.04.docker", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "600a5eb78c7ad219f0fe28ce5681c0d1b03e917781b3353b1c2efc19b71ae4c5", + "format": 1 + }, + { + "name": "docs.requirements", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8dcdf757a8f9dcdaf8a0a95ad6add12a3d8eeae94691bbe85f9a9a5b8f8056f3", + "format": 1 + }, + { + "name": "docs", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "docs/Makefile", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e33bf74d59130029ba24168a813112dd923d7044d780ce942db6645d93f56acf", + "format": 1 + }, + { + "name": "docs/Makefile.custom", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2149597a9d9e33ffeeea20bec72388fa42107d543503548d89cab7e3b90c55d9", + "format": 1 + }, + { + "name": "docs/examples", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "docs/examples/installation", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "docs/examples/installation/ansible.cfg", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "563b8ca2cec5bf239ff7e1dd0189adcd0fa6a97aceee6de8bae886403dfe1a34", + "format": 1 + }, + { + "name": "docs/examples/quickstart", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "docs/examples/quickstart/inventory.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a2c708f68e93d0f52b1ded72f78a14ee1ae0f6862eccdeb9438cceac6a52b678", + "format": 1 + }, + { + "name": "docs/examples/quickstart/playbook-5.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e346260feff21bec7c93ee76576dee29914eaea1b09902210f3ebafaa0bcdd59", + "format": 1 + }, + { + "name": "docs/examples/quickstart/playbook-6.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8ab7bf41d5dde10479580d705b8ff2badeebcfcdf76064e900ff7952ad696aae", + "format": 1 + }, + { + "name": "docs/examples/roles", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "docs/examples/roles/agent.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "36e0c406bf4f31fb0dd39ae4b215d7d9d161dc316b1b28cb86da097f9dc62173", + "format": 1 + }, + { + "name": "docs/examples/roles/backend.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b68d0fe0dcb27f5f03fecfd9dd7b8a066be988d6a86e36eaa252aef821cba5db", + "format": 1 + }, + { + "name": "docs/examples/roles/install.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "baa2309b7f3a01e2b7bdbe47d792e552c55ccffe13cc0e88aa58a96b5d44744f", + "format": 1 + }, + { + "name": "docs/source", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "docs/source/conf.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "02cb76c3f0f659fae1de303aebfb88db6c0328c39cccbcbcb6a0cdaf0ed5e414", + "format": 1 + }, + { + "name": "docs/source/hacking.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b7f7733d08a926b3537ad8e308931d786db6fd0ae6647581044e16208615f01f", + "format": 1 + }, + { + "name": "docs/source/hacking", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "docs/source/hacking/docker-images.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "03f249236c97fa4d011b3b4bd59c3a5904cec68e3619e1497103738a07ed5dee", + "format": 1 + }, + { + "name": "docs/source/hacking/documentation.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "92059787adf3a776f37c5b75c0b0224d0a8003962978a45bcf623881bd290053", + "format": 1 + }, + { + "name": "docs/source/hacking/releases.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8e30ce3acea64d41c20ade3d02151d8f14e31b1cbec84fc9e95c59d939d572c2", + "format": 1 + }, + { + "name": "docs/source/hacking/setup.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8f3cfd38a07f6286c7a399e96355b00d82e8eca39b64662fdeffa046e97f4bb7", + "format": 1 + }, + { + "name": "docs/source/hacking/testing.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fe96a2b58205223dd3dd77134b8da01883635bac58af1b350ff7bee84ff7d3a7", + "format": 1 + }, + { + "name": "docs/source/hacking/windows.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cce9fe33390d8e455c2225622238382e3e71c9ad50e81f8b0df5b5978738a5c8", + "format": 1 + }, + { + "name": "docs/source/index.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c344f74b54edd788efe5cc17aa4cd68051d4c61d766454072f244ccc17835262", + "format": 1 + }, + { + "name": "docs/source/installation.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "77cdfdb6264909ab14ff3d59b110908f4dab01f01f1a4b75827739e2d2c52e40", + "format": 1 + }, + { + "name": "docs/source/modules.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "27160e8c9669767b0ae99c85992f335a6adcfb5bb28278f474a74f332835b901", + "format": 1 + }, + { + "name": "docs/source/quickstart-sensu-go-5.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b8bff4cea65c259a12a56e5fe88c127d2e36c4d9707f633d0834d84a3fe46046", + "format": 1 + }, + { + "name": "docs/source/quickstart-sensu-go-6.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9483d05e50c630e3ffa052b4de3f997945290a937ce0030cc08a7113ae20b047", + "format": 1 + }, + { + "name": "docs/source/release_notes.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2417f24afa8a3a024804075c5c42ecc09ac9de89d85840574b9cfe5180cdaef3", + "format": 1 + }, + { + "name": "docs/source/release_policy.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6fd25c423ac44189c533093827cce9d51d18aac1f35df85df0029edadf16a2f5", + "format": 1 + }, + { + "name": "docs/source/roles.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8354f00bf6d30b5de815cbb6bafa0d06b9ea62848f373df7516e395e0b30aaca", + "format": 1 + }, + { + "name": "docs/source/roles", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "docs/source/roles/agent.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "00fe6a0a69ed23a1247a059d48b9a188e3d654a8ba593d874d7fdc69ecb48ffe", + "format": 1 + }, + { + "name": "docs/source/roles/backend.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "94c3cf1124358f5b025c04debee293a866838c09aa63d68c4c0343d3c61ad36b", + "format": 1 + }, + { + "name": "docs/source/roles/install.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bc0aa89f658ec2a90395f137d82de226999ad4be837034860e3d48b0fbb384e7", + "format": 1 + }, + { + "name": "docs/source/sensu_go_5_6_migration.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "deffa1c41e850c1c7daf86580df9d9c64c02290e7747b06f8c1a53253660affb", + "format": 1 + }, + { + "name": "docs/source/versioning_sensu_go_installation.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "83f27f787298735fb1d940149260c41558e62c51ea005e672ea1caefea076f96", + "format": 1 + }, + { + "name": "docs/templates", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "docs/templates/module.rst.j2", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "aaa2e999c9e8136e7256980601dbd315509fcd8860a5784aa493c9345a690072", + "format": 1 + }, + { + "name": "integration.requirements", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "30a9a73b9186fffdb27b80c7b9cff8d1af86b058ef2dc5ae132368ca57699bd4", + "format": 1 + }, + { + "name": "meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "meta/execution-environment.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "65a3aa1ded18aca17b56c630b9b7a48abdb972f17e67125695f0f048daa94b06", + "format": 1 + }, + { + "name": "meta/runtime.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f47c409467a06433e256cd5f49da832888d307258672da9c7da11fe01d9d36e9", + "format": 1 + }, + { + "name": "plugins", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/action", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/action/bonsai_asset.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1a6b4afc70afe86769663e99956dbaec59c7d7092b8bc2ae79eda3e5544e3b0f", + "format": 1 + }, + { + "name": "plugins/doc_fragments", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/doc_fragments/annotations.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "de530f111b226d43bfc056f849e10f901d37e68468051fed09f8a6dc1876db41", + "format": 1 + }, + { + "name": "plugins/doc_fragments/auth.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b44608aa817a9d31eb0b7dc0e1f4f6f5943d7a0c9f8ed5ac4cc1781ba98b9bd7", + "format": 1 + }, + { + "name": "plugins/doc_fragments/info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e59e1dc61c6784b46fb221819335d5ff0624b5ab6a113a1b68f2fbf2ad97e567", + "format": 1 + }, + { + "name": "plugins/doc_fragments/labels.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "24a4f982431aa556bba0d825cb9279e8ea7edba278968d0e2f258c78a6e4bcb0", + "format": 1 + }, + { + "name": "plugins/doc_fragments/name.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a20a214a2cbbd2eaf7bb85b2e0ba9aa4c8d62fe6c23c6e8dec75989bbae3a118", + "format": 1 + }, + { + "name": "plugins/doc_fragments/namespace.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "65728308f42e3914228ccc1cd9e967cb697f9e02eb373614998d329a62919d57", + "format": 1 + }, + { + "name": "plugins/doc_fragments/requirements.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "337db3c5881c7bf1adf942df7651661667bd07501d3bdc92991672e56ea23cf0", + "format": 1 + }, + { + "name": "plugins/doc_fragments/secrets.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "289859549042df6829db33290830fde1f5e407fc11a5abeb7f6f3e0445b7e511", + "format": 1 + }, + { + "name": "plugins/doc_fragments/state.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8fa51fbe3b600d78e2a7f6a346409f553f9d25ca1229f05c13d59942863afe89", + "format": 1 + }, + { + "name": "plugins/filter", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/filter/backends.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e20a41dd39107122ae71c9d329ba07e11755ae7bd92e3c35e603d99a398a392a", + "format": 1 + }, + { + "name": "plugins/filter/package_name.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "32a8b7ec15fddac13ec5e5550da07785227ae03749febff4126d4c7f3d2c9a50", + "format": 1 + }, + { + "name": "plugins/module_utils", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/arguments.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a82bfca1d1ea8712676761d79f9691abd845aa9f8f1cd1d8fcd6dcd3b4acda77", + "format": 1 + }, + { + "name": "plugins/module_utils/bonsai.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a15c8ddec407b68279ab34d0de7ff05b5646f75cbd005e53677e09ee60dae7a1", + "format": 1 + }, + { + "name": "plugins/module_utils/client.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3cdbddecb640c0c0a9b5217fa4f8132bc398f0b4aedaaaceba0131199bf75d46", + "format": 1 + }, + { + "name": "plugins/module_utils/debug.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "55d365485cad59f0ecb8c04d4099627ff506212067fdea44691bd740f0c90471", + "format": 1 + }, + { + "name": "plugins/module_utils/errors.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "24b2d5e56b6963c5f2bd88ecff5eea457fdfe5d00bc82ac1b795231a05ad3b89", + "format": 1 + }, + { + "name": "plugins/module_utils/http.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1ab058ee166c85136210d767e4e57195f00a3837a830c743c14b04751cb08c59", + "format": 1 + }, + { + "name": "plugins/module_utils/role_utils.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dceb65553d08033c7d6e8bef8f524ae2a76c4c82c70e69ede1ba23569c35a25b", + "format": 1 + }, + { + "name": "plugins/module_utils/utils.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d00ca6a76c92cf855f3710d314831b7c2f22d10e52a72d046ed23e7cbd6dd07d", + "format": 1 + }, + { + "name": "plugins/modules", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/modules/ad_auth_provider.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a52ed13aef2b02642bcec861e5629cd6178aee15de0ae9bf94f406f044cce0ab", + "format": 1 + }, + { + "name": "plugins/modules/asset.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "726c579f6cbfe58d9089fe590cf029a0f5aefc23ba3a47cf1ff07b920bdc0294", + "format": 1 + }, + { + "name": "plugins/modules/asset_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "46df5f95d354f3b0d653b8b5118483ea6cc08f99be0953f1243fd6bc8c715ea5", + "format": 1 + }, + { + "name": "plugins/modules/auth_provider_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d53f358f8cbafdb9b21baf5a5d9f221013a6523cfb4e64eb7e1559490d58c37a", + "format": 1 + }, + { + "name": "plugins/modules/bonsai_asset.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dd1724000e3840d6fbcd9cda5b3c6c4784e557485765b78b840edb61c7a40d04", + "format": 1 + }, + { + "name": "plugins/modules/check.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0bdb0c4572a20e3898d0a5f82fe8fc08ef4c0d8ff9b502792343b49ed4d81548", + "format": 1 + }, + { + "name": "plugins/modules/check_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2c4fe61b4a70cc691dacf3877355d258790e53e688874b4216d6057bd219b7df", + "format": 1 + }, + { + "name": "plugins/modules/cluster.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ca9c885157ffdfee4aef50cec7acef89669c54e6dc9de82f41506372877407e5", + "format": 1 + }, + { + "name": "plugins/modules/cluster_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "889999db739e76c2c3d2ceec71ac7fd41d568d88cca5de898fbd82e91bf3e741", + "format": 1 + }, + { + "name": "plugins/modules/cluster_role.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f70395bbc47164b6bc1a336a2d1b80ac1f2aa1610c408174619752f38a5bc556", + "format": 1 + }, + { + "name": "plugins/modules/cluster_role_binding.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ae998eea21e8fd673b282d0242d6323bd3c93562df858404b86c160453e1d7ae", + "format": 1 + }, + { + "name": "plugins/modules/cluster_role_binding_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4a551163f5ed2e0bf06e397bd27b713cae52856071048a97fee78755a76b6a12", + "format": 1 + }, + { + "name": "plugins/modules/cluster_role_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "40d941b24fee942b5b6fa73d33dafa7ed0c3e816880bdcd39fc96b7ee4a25e06", + "format": 1 + }, + { + "name": "plugins/modules/datastore.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4a5d49a62b48d571dd49f660e13bb53509421d619a45b9c8a9b3f3c279293609", + "format": 1 + }, + { + "name": "plugins/modules/datastore_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0179dfd3073bdfe994622f1a0aa22d250f8d286188a5f47303140924540ac336", + "format": 1 + }, + { + "name": "plugins/modules/entity.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "36d6576284dad71da960e144fd0c3450b8520537a1a8b88220d6cfacecf4d110", + "format": 1 + }, + { + "name": "plugins/modules/entity_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "93465d322c93a87634fa485987307ad0e6149e2b319ac4559d51036a238cd408", + "format": 1 + }, + { + "name": "plugins/modules/etcd_replicator.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a3e17900f134791e7a210b9bb10cb7e08d7342d821dbdcbf34f6b6dea2b79264", + "format": 1 + }, + { + "name": "plugins/modules/etcd_replicator_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "20182f94690583b01d86dbb7e8b1929fe78a9c2af9d18cdc32a053c2f6db4382", + "format": 1 + }, + { + "name": "plugins/modules/event.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5bd6748018dd45fd614d5598da657f933299242d4bdd59959428cf5a16854393", + "format": 1 + }, + { + "name": "plugins/modules/event_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "73f02a988981be0f5c7e8403b4e7f72c7c47b0f5ec3b9c83dd7a1f59b13c1547", + "format": 1 + }, + { + "name": "plugins/modules/filter.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bc257b582f6538b191e96aed2162f422c7d9010716d345a9c3afb00ae1f115ac", + "format": 1 + }, + { + "name": "plugins/modules/filter_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1ff62d3e629e3676010df6d97ab24919dd90aa8e4ec7ba1e9012bba512397078", + "format": 1 + }, + { + "name": "plugins/modules/handler_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9a7bdf4c7fef96782e327070d46c213e98b99b3653c1f65b67a1c8b181d83bfb", + "format": 1 + }, + { + "name": "plugins/modules/handler_set.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "23b06861ce894e5aea1d750c99220c6600d6efeffb635cf46517bbdd6b691399", + "format": 1 + }, + { + "name": "plugins/modules/hook.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "245dd5fe347a7ea5922181e5b71a8a7bd58f43f7c143a958871d55469f185f6d", + "format": 1 + }, + { + "name": "plugins/modules/hook_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5ddb5b7239f4488fdfe08762ba5ea8aeb1093f752aedc027b9f29a0cecd3a818", + "format": 1 + }, + { + "name": "plugins/modules/ldap_auth_provider.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d095ad95ef74cceda34d46de7113dbb3f0fd6c4e54fe9afc6e23edab3d96b62d", + "format": 1 + }, + { + "name": "plugins/modules/mutator.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e9508e0bd9ec5688474220abe6e67c92f7252b27c80060bfbf98981880a3408f", + "format": 1 + }, + { + "name": "plugins/modules/mutator_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f51f88433a25ae292239fdc6a3391a83e0da6feaf9a8c856d792455de020fc05", + "format": 1 + }, + { + "name": "plugins/modules/namespace.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b2a549f56e7fcabdb669770da3e552549d0cfdf49e3f5649004a5679ad5587ca", + "format": 1 + }, + { + "name": "plugins/modules/namespace_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5eb7345ad6a316456e65f7b6c977df0792f38619e7f8f4e72cb38fdac977cbaf", + "format": 1 + }, + { + "name": "plugins/modules/oidc_auth_provider.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "52d850b3d6ace1be7bb8bf7e92e31271002c9ed6aa8d8ea56e2af4c980e33201", + "format": 1 + }, + { + "name": "plugins/modules/pipe_handler.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3dd56750eabe9fc6490307117daf88fa9ba685673c49f0b5af3b9d387440e7fa", + "format": 1 + }, + { + "name": "plugins/modules/role.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0aff9dfdad3c77a57cd3faec89612dbcd5af78efa610146f930d86f1e31f83f0", + "format": 1 + }, + { + "name": "plugins/modules/role_binding.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "16babc38cb70db50f4a6397adf24cabc5a2611291d8141756e8a50805bd06491", + "format": 1 + }, + { + "name": "plugins/modules/role_binding_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2d74a32830246670496043c40437c1dac025adcaa1877f366f7dbfa431f9e47a", + "format": 1 + }, + { + "name": "plugins/modules/role_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "38b6952d82b0a614c752fb9d11921885cd40fa4a96950280bfbfee0ee82c8b56", + "format": 1 + }, + { + "name": "plugins/modules/secret.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "61bae955742a34b16dec2b788191c2a50ef53a240fcfd1d39336b229a6d7b69c", + "format": 1 + }, + { + "name": "plugins/modules/secret_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "24781ec21d4fa0aa9196404ae2ce536e34ea74df156de06deb9f09c94a9d8eb4", + "format": 1 + }, + { + "name": "plugins/modules/secrets_provider_env.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ee050f7c54dbe7485717bf2820b3967679ca1f00d13523bb3c0a0472f51e8c19", + "format": 1 + }, + { + "name": "plugins/modules/secrets_provider_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2e9af254afc7b4ebee3421cba06721bf5848af7d25ae8e6a2a11a4a2be067ef3", + "format": 1 + }, + { + "name": "plugins/modules/secrets_provider_vault.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ce01d02aa45072f863274ffeaf83f607c9f2c78f3a56744698864e9471d85ce1", + "format": 1 + }, + { + "name": "plugins/modules/silence.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c06398c67b6192664d1eedf7743807500968a558548a0f4558df6fce0c2bf07c", + "format": 1 + }, + { + "name": "plugins/modules/silence_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3b0d1e7d8ed423dc8b7f6c7c98e0a83f46075b6887a245edb15d3593e2b0d002", + "format": 1 + }, + { + "name": "plugins/modules/socket_handler.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a4b440a8f143696302d5cbf3f8edca4ef4f438604196605fbdad9d1066dad07e", + "format": 1 + }, + { + "name": "plugins/modules/tessen.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5128759afadbba3edc7ed18dfd91176a86cda8b86bf2e283bc1509f9b58e2d82", + "format": 1 + }, + { + "name": "plugins/modules/user.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "973ab65cb2cff9083dd7a5f2164571d34d7981c306c7957c31f46565d99b419f", + "format": 1 + }, + { + "name": "plugins/modules/user_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4775877c375a1b546b9bd49de726e6f96d72d7947f098e801210cb8846357ae8", + "format": 1 + }, + { + "name": "pytest.ini", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4f2478cdf42d7e2a9493090e9aa62a058ee3a368ba396f03c1fe464728f089bc", + "format": 1 + }, + { + "name": "roles", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/agent", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/agent/README.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "06dca09fc919a0aa297410a2de953d9ed9e669d0ab929ec7537b04742c9cf574", + "format": 1 + }, + { + "name": "roles/agent/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/agent/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "33f01b24e393cbb7e3eb32e16ad1e916ec6ba6e436dfcdbf5a1c457ce6238fbf", + "format": 1 + }, + { + "name": "roles/agent/handlers", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/agent/handlers/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ddeac82dc681335eb889c0030ba7927b4e8c06188428218e86f058eddd3214bf", + "format": 1 + }, + { + "name": "roles/agent/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/agent/meta/argument_specs.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c365bbe5dc27a54515a79d5794b1df7ab82ba858d2d4fc1d52ca4de1a8d5ab65", + "format": 1 + }, + { + "name": "roles/agent/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b0066c8a5fbc6b76d9fa8ec9505141ab99982564a64a64b6d1c46eea0a316732", + "format": 1 + }, + { + "name": "roles/agent/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/agent/tasks/configure.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1a9e48b1277b28952d6efa8bb45f024b7ab41942aa63443b9c5bacffff4feb85", + "format": 1 + }, + { + "name": "roles/agent/tasks/linux", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/agent/tasks/linux/configure.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5b89b4d30cbd3534cc498f8eee583898aaa893003e4a4528085ef7cb5e91ce71", + "format": 1 + }, + { + "name": "roles/agent/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "01a7870ec401f3fa5f42b692c1441672d31d3caa2694c6367e15b980c463e1a0", + "format": 1 + }, + { + "name": "roles/agent/tasks/start.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5bf242f7f5a50861b0cc0432c350cc0153eeaa1692434159de0fef81dbb641d1", + "format": 1 + }, + { + "name": "roles/agent/tasks/windows", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/agent/tasks/windows/configure.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "164820a1d9481a43d2b88df2c3955c2d2a78d9dd50ef8d7db3fbe5a82382291f", + "format": 1 + }, + { + "name": "roles/agent/templates", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/agent/templates/agent.yml.j2", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cdf241ee60067c368fce4cf4b5c47311428d940dd2c404ee60fd5485f6614878", + "format": 1 + }, + { + "name": "roles/agent/vars", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/agent/vars/linux.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b5e9a4b8caf6a83b515dd2e642dcd105ae17c36382f50e495b48daddf81c1e57", + "format": 1 + }, + { + "name": "roles/agent/vars/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f88fc6f5f0fcf83dd503713eddda7c76e2da6de261ed6626b094b732cd3ce512", + "format": 1 + }, + { + "name": "roles/agent/vars/windows.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "36b7a1dadb91178a78b7c3e4aded558a0700939c0e99e0d9f1ed998585a40f69", + "format": 1 + }, + { + "name": "roles/backend", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/backend/README.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "abed04af924e7b21fae4477c9173d4f73d92aa094cf8ffa8b88b8a7f79fe1471", + "format": 1 + }, + { + "name": "roles/backend/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/backend/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "69b3e10221482a07e06c040274ba753082fcec2fb2f1aab09568faff82f0ae62", + "format": 1 + }, + { + "name": "roles/backend/handlers", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/backend/handlers/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d14dc66fe9244d03183fcab3c4e541788db284f4867b645c76dcaefdec0c58a1", + "format": 1 + }, + { + "name": "roles/backend/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/backend/meta/argument_specs.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "28af81d29571f849b58dad7e667b86d3c687825c3618e67ba2df1d2327da8d60", + "format": 1 + }, + { + "name": "roles/backend/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "63f743937b9ac5934aeec02276ac957df1938266a31eab041e409e169547a884", + "format": 1 + }, + { + "name": "roles/backend/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/backend/tasks/configure.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e98b97ba542db5c359b1beb3fe1f71aeb1785fc8bfb2edf5aebdca3185e4b9a4", + "format": 1 + }, + { + "name": "roles/backend/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2b5d73651c0a3d0be6d4855e392273b11af3b579cd147f05a5b7d2445793636b", + "format": 1 + }, + { + "name": "roles/backend/tasks/start.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1d5699a6e746a4b65a58787ec503840e38b5dae7370b5aad8c149af57b68611a", + "format": 1 + }, + { + "name": "roles/backend/templates", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/backend/templates/backend.yml.j2", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1b5232126bd8ce881d4c485c316a1277dc517d6854c8845140bcc9a5417550f1", + "format": 1 + }, + { + "name": "roles/backend/vars", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/backend/vars/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f88fc6f5f0fcf83dd503713eddda7c76e2da6de261ed6626b094b732cd3ce512", + "format": 1 + }, + { + "name": "roles/install", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/install/README.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6a2e966b8223c0a1a7537bda83d6cc580306e4905c78a1f203b69743ee847a5e", + "format": 1 + }, + { + "name": "roles/install/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/install/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f41fadb8a7ff1dc31113590444ea5704d77beb8771cc09f55993c92c469b6613", + "format": 1 + }, + { + "name": "roles/install/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/install/meta/argument_specs.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "151b93a5fc4edc8cb4cdef0b28daf6226ddcbd88227cf85f92f43bd38b9c4dc4", + "format": 1 + }, + { + "name": "roles/install/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f421fc4b8f713f9f11290bbff7ad2cb90d5f78d83309e1e6c7b683fa6e24e73a", + "format": 1 + }, + { + "name": "roles/install/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/install/tasks/apt", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/install/tasks/apt/install.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "898d2b4b57b332107c48dfb40dcccc3d40ea7b532eeaca4c9c201dc3558f5ee7", + "format": 1 + }, + { + "name": "roles/install/tasks/apt/prepare.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "547a5982a060392175b4e0981da7435add9b9471272fcf6d7163b6f1ab618b0a", + "format": 1 + }, + { + "name": "roles/install/tasks/dnf", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/install/tasks/dnf/install.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3d4519cef82e17225b4e82b72aba41c796cb8dedcde35412edb9e1b0231dfa88", + "format": 1 + }, + { + "name": "roles/install/tasks/dnf/prepare.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "87483a599eb30e4b21649ee15f3b772430f5f9fe4f801e27a3e590acbf6e96fb", + "format": 1 + }, + { + "name": "roles/install/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "79065db24a92c889558d118f4b3479cc5cc479a7f4cb82d336d058d8cf4c2417", + "format": 1 + }, + { + "name": "roles/install/tasks/msi", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/install/tasks/msi/install.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ecae9c9ff9b3365c5c1c51e4ba93871adfe6c8058fd322eda65f9d8cfd6d3314", + "format": 1 + }, + { + "name": "roles/install/tasks/packages.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c5cf23a3b44d5babcb0535c12d458031829c5ff9d611168934d7c52beae578a0", + "format": 1 + }, + { + "name": "roles/install/tasks/repositories.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "88334da8f1e5d828e97b05ef7157095fe2c661698566b85407b6b74bb0fa22ea", + "format": 1 + }, + { + "name": "roles/install/tasks/yum", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/install/tasks/yum/install.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "332bc4ce394890cb905ae28eae3592a94ac01ee53d15f3d411c63d65952f65ac", + "format": 1 + }, + { + "name": "roles/install/tasks/yum/prepare.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "87483a599eb30e4b21649ee15f3b772430f5f9fe4f801e27a3e590acbf6e96fb", + "format": 1 + }, + { + "name": "roles/install/vars", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/install/vars/Alma.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "40c19f543bbc4ec7a040d6d6ffea0ca3104e92169e1006948c1c35dc97e7f2ac", + "format": 1 + }, + { + "name": "roles/install/vars/Amazon.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5d5fc0a9ab57713b616ffd1c19ab046acafe3903ba5816763e5867732313687e", + "format": 1 + }, + { + "name": "roles/install/vars/CentOS.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "40c19f543bbc4ec7a040d6d6ffea0ca3104e92169e1006948c1c35dc97e7f2ac", + "format": 1 + }, + { + "name": "roles/install/vars/Debian.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "821d50ce3cbf271104507a01e56aa597b349ef1b82cd4426712b3ed6544207d5", + "format": 1 + }, + { + "name": "roles/install/vars/OracleLinux.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "40c19f543bbc4ec7a040d6d6ffea0ca3104e92169e1006948c1c35dc97e7f2ac", + "format": 1 + }, + { + "name": "roles/install/vars/RedHat.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "40c19f543bbc4ec7a040d6d6ffea0ca3104e92169e1006948c1c35dc97e7f2ac", + "format": 1 + }, + { + "name": "roles/install/vars/Rocky.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "40c19f543bbc4ec7a040d6d6ffea0ca3104e92169e1006948c1c35dc97e7f2ac", + "format": 1 + }, + { + "name": "roles/install/vars/Ubuntu.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dd63d426d344ec5ba922791b9bc0d1af5911ae3c87318a594a29222091da1496", + "format": 1 + }, + { + "name": "roles/install/vars/Windows.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "27c41ebf0fc68db661eaf70d17d0149634dff805081e8bd8d530443a52514ca9", + "format": 1 + }, + { + "name": "sanity.requirements", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bbe597d4a77973f8ab0f7eafd636df5b4b530f346adbf69afa11e0fd68d58e6c", + "format": 1 + }, + { + "name": "tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/config.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "af61b7b0c3206599d92a45abdff0b6fbc3fc0f30b88216688446b3eb8568b316", + "format": 1 + }, + { + "name": "tests/integration", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/base.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1f2c5d511980f16cd927ca1fb52a0f3e601650165ecc202c6a92c6651bad9004", + "format": 1 + }, + { + "name": "tests/integration/molecule", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/action_bonsai_asset", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/action_bonsai_asset/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e2dbfa5a6e7be57ad70994228ffaa50f3763429abe27b9343af0f042eb9fa5ef", + "format": 1 + }, + { + "name": "tests/integration/molecule/action_bonsai_asset/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/misc_api_authentication", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/misc_api_authentication/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a103594707ff263ec4775108b3885d40e3ba66181548ab7ff38c6427f839591b", + "format": 1 + }, + { + "name": "tests/integration/molecule/misc_api_authentication/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "178e6ea931d9385b4dfec893dba07bdf94dd71fbe1de003ccd1a0885c92f5a82", + "format": 1 + }, + { + "name": "tests/integration/molecule/misc_api_cert", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/misc_api_cert/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e4d689d76ec95c48b36ecfb982e943b2fbcab7d4555e210afa5ac1ea86c9ba58", + "format": 1 + }, + { + "name": "tests/integration/molecule/misc_api_cert/files", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/misc_api_cert/files/regenerate_cert.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e1cbeee84e2890ed38e8118a86d71b2357a3086e399c0d18847cb7219a7f8942", + "format": 1 + }, + { + "name": "tests/integration/molecule/misc_api_cert/files/sensu-api-ca.crt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6e15983981fc8c3353f5edb949594df0f127db756e510d965674da864cab4a22", + "format": 1 + }, + { + "name": "tests/integration/molecule/misc_api_cert/files/sensu-api.cnf", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2814df5948b90fcdbe7d9c7bf94da189df46acf958296ccae967e8a516c3f026", + "format": 1 + }, + { + "name": "tests/integration/molecule/misc_api_cert/files/sensu-api.crt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0bf1aa2f71222da60bff2696da9466cc90141827c25f34dd41d769d8880cce33", + "format": 1 + }, + { + "name": "tests/integration/molecule/misc_api_cert/files/sensu-api.key", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8cb76f736976fa2e46783377825eed07c39a08eca05bd52a7e0b37dc0ff64801", + "format": 1 + }, + { + "name": "tests/integration/molecule/misc_api_cert/files/sensu-api.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e4ebf381f9356973875265175984c5042503e6ed797e4082f14df6acdc16705b", + "format": 1 + }, + { + "name": "tests/integration/molecule/misc_api_cert/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "40408ccc064612c6d4f2651d4cdd58cd2f080c39edcf91e9ac7dda72431b4a44", + "format": 1 + }, + { + "name": "tests/integration/molecule/misc_api_cert/prepare.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "838fc000b3a0f408a84e701f276a749520f0db6de4cc6488bca1da2b01a7a0f6", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_ad_auth_provider", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_ad_auth_provider/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ec0284630a467d5b3d5f05399a82b3ad3d335c0696a21a84772912d16ba24a95", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_ad_auth_provider/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_asset", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_asset/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2b6f8db8c0524212e095bdb991b16c59898dcdd532f64f9c4da460abae005e5b", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_asset/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_check", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_check/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cdad4b4ac55395074b2274b1b69a7593a97491185b862d99fb612cc62da5e72b", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_check/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_cluster", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_cluster/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6d1ad6139c292c2480408c473fc428277c4816ee548914eed6f87218cfd8250e", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_cluster/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_cluster_role", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_cluster_role/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f5983392e530d703f4abb1e630068fd417681ad55fa114d3effdebf6e20ddcdd", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_cluster_role/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_cluster_role_binding", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_cluster_role_binding/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c2a2e52fda050054c7cdaa5dae37ec6ece94f655dbf6ae2a49f3c401ea975c20", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_cluster_role_binding/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_datastore", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_datastore/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1dfffb92990f266461a0be38b96ad53007217ac142471ece37f5a72fb54a157d", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_datastore/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_entity", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_entity/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "73c21f1f83697ca17523bcb184a58ed035795326c02ca8eb3c6b4607a6c68be3", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_entity/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_etcd_replicator", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_etcd_replicator/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0900748b08ca92df2e4db5fa8898c36d5d01a1bf00f8c74eb06f1d999a6d7210", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_etcd_replicator/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_event", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_event/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2d688472ed115932914dd5a4b03f288522f1f5951dd6e14aa7920c69907cf049", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_event/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_filter", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_filter/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e0052d11b6e228ff81dfa3dfe9335003381d31825bbcb01a0dc837c4c0db7094", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_filter/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_handler_set", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_handler_set/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3997eda49454c728c35d7598d052af3cb9d765dcd3773b309d8c01be09205f05", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_handler_set/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_hook", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_hook/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a71edd518ba58a6b65a9ea8556f074f4d667ea7e0d7d62daa645fcbbd17b4153", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_hook/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_ldap_auth_provider", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_ldap_auth_provider/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6bf3ea1c6a552d955500c71216a883eb0882e0ebd70f39678a1432400f925f53", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_ldap_auth_provider/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_mutator", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_mutator/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fe887e75c4eb72ccab90df429255493e74f756d02fd6d86848400210e1cc82a2", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_mutator/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_namespace", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_namespace/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d9acc623aecba6078a92642ca1589934731ad8e8b7d4a1d1b2db207b753e636a", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_namespace/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_oidc_auth_provider", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_oidc_auth_provider/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "38beff2e4a2d5ae16f629693049a66aed52be97bea8388e14ca44f01df71862f", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_oidc_auth_provider/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_pipe_handler", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_pipe_handler/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fcc73e6d100431a5c574436eb7ed4e4a39ac0fdb97da1fa81922d6d41c26cf13", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_pipe_handler/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_role", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_role/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fa4458370a2096eb4e17ab87211fe21f8674dc3e4547800725126d5db0f8f8ee", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_role/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_role_binding", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_role_binding/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "737ba36bda11bf0e6b6b0fa78df38f25e8b13562586b1c5a83856badc2814308", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_role_binding/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_secret", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_secret/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "86c3512a17b4fc59ce1be531d9555ec5d8e4d987e6cb885b23364769588a9128", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_secret/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_secrets_provider_env", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_secrets_provider_env/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f87d981967990828c647e9cfeee55d41773965da176f5f3252e2401254eff587", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_secrets_provider_env/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_secrets_provider_vault", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_secrets_provider_vault/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "71dafe888fba8c7b21fae6565418085c3dd9eb9a286395098ff20b554c6d5006", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_secrets_provider_vault/files", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_secrets_provider_vault/files/ca.crt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fe7aebed0559a563d303ad5402661734500bf759b541f5604aabc6f2784682fb", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_secrets_provider_vault/files/client.crt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c45c551e047d0e4503f842b3fd74b9d688352fb18c39d415f91639e52bad7104", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_secrets_provider_vault/files/client.key", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c814aec9b7f3147b1e95e0cad0520483ccf584c0b39da372749dd90dc5ee4a86", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_secrets_provider_vault/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_silence", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_silence/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "adf96830a4733c69f56bebfdf6dce57e7cedab88293717195b66af220c8d8f2d", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_silence/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_socket_handler", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_socket_handler/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "78651227496b976709bf419da5786798480534c8bb163a2b990c3b0a05e1198c", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_socket_handler/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_tessen", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_tessen/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e72c0d42335048393941d38135ca5d3c2432cc457d144087febb328229fa96cc", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_tessen/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_user", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/module_user/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a8ac86c519df0b738354382db5c6e5da8954affaf4d05bcc38cab98bee0e5ff9", + "format": 1 + }, + { + "name": "tests/integration/molecule/module_user/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_agent_config", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/role_agent_config/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8f08b5e71c731c4cde354962cde92cbecec4706d0ebcabf94c671050f6a52de0", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_agent_config/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9c426f8a43dc7873bfa6b612b3ec4a7202048bcfac7aa3bf6a1e43e6f7309004", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_agent_config/prepare.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "16470c6cd0197ec6fe497f8853e74bef3103c61eeabf4ab5aef027b622df50fb", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_agent_default", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/role_agent_default/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d25cbe6afe40aef0ad685ae0c2107ff210096430ae169926c454c266f312e630", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_agent_default/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ed9d8cd8d7d34d989588138da69b31b8796cc44fd91d2f4a3fc092c0c44dab83", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_agent_default/verify.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "85fe091e6356f0cdd69ebd8c72021861fb3bba3f0959ab01d04e2abf4859cc61", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_agent_secured", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/role_agent_secured/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6a51e6678ebce349d6aea2639a6cbf4b2151cb152230040746a11096b5f1b498", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_agent_secured/files", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/role_agent_secured/files/sensu-agent-trusted-ca.crt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b0cc89bb8e83a75c0a17e07039d97801100210eecaf83ba123ad751d31e6c8a5", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_agent_secured/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f5177a971e90cc7c13588e447b22452f1a33a94ab25e151d686d6bc17a01b690", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_config", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_config/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "73163b370569fd1c63f5932de6777d051c38d8c752230f300c2c1d0f2aa092cd", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_config/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9c426f8a43dc7873bfa6b612b3ec4a7202048bcfac7aa3bf6a1e43e6f7309004", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_config/prepare.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "16470c6cd0197ec6fe497f8853e74bef3103c61eeabf4ab5aef027b622df50fb", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_default", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_default/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4d481df5407ba137a0940116b8f61c1747d9a3ea8b3e85784aa407a853bfd5aa", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_default/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "da4958f5d8476f6fbd162618b6b619a2701a45d2c4c44796bb5f1439233f1e0c", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_default/verify.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2ec3ee5184c79e85939d3a870ace1a1f3c25592ec3d87f3184b2da81c433796c", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_secured", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_secured/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "42b0cb4ebc8220516575ce6cc8bca826ccc24e54068a83b253028f0dbdf8d09c", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_secured/files", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_secured/files/client-ca.crt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b0cc89bb8e83a75c0a17e07039d97801100210eecaf83ba123ad751d31e6c8a5", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_secured/files/etcd-client.crt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "91f1ad5e44a2ccda387a4fdb473a6b1eac220c0dffe7ed786de575d8b0ec89cf", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_secured/files/etcd-client.key", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ef8116f17b7fd958a579c58890af7bfd80f16e168b19aa8b7f8c402578ab7e7e", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_secured/files/etcd-peer-ca.crt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b0cc89bb8e83a75c0a17e07039d97801100210eecaf83ba123ad751d31e6c8a5", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_secured/files/etcd-peer.crt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fd16ec3d07a7cdfe74910e6a769b4fe5cc73b7f5ae49b22ea53fee53ad62ebef", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_secured/files/etcd-peer.key", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4f4b32ab8dd49b9f2a64c6c6600335b242d100f7e722cbb87efa427732af7aff", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_secured/files/sensu-api-ca.crt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b0cc89bb8e83a75c0a17e07039d97801100210eecaf83ba123ad751d31e6c8a5", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_secured/files/sensu-api.crt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8148d3f7b944ff7feac4ad469114c1779e2967aa33b9f58eae7ff42f204549a0", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_secured/files/sensu-api.key", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "543416d24894fc79aa28ec6d021dfe190c4cc94ed8939cc55d35779fb2808c84", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_secured/files/sensu-dashboard.crt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cf6928ab177a3df13777cb16d91db990a5771a7bbdb6464aa103dac948b4958d", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_secured/files/sensu-dashboard.key", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4a40d9838c79807115f56c9d5b40e6a212fc1c159aa7bb696d1b4a64e232bb2c", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_secured/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8b1c3eed7bee4ced542d603022ec774aea5ec75ed2dd618cce02b41269d85897", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_backend_secured/prepare.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "16470c6cd0197ec6fe497f8853e74bef3103c61eeabf4ab5aef027b622df50fb", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_install_custom_build", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/role_install_custom_build/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2ea525a1ac3c38143e65fcdf208471b53b8e76c6a599e07c712eca8948411499", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_install_custom_build/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2c2be29eaf5026cb751a24284ace2e84a0172460f929008a7267bdfab9d77991", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_install_custom_version", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/role_install_custom_version/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0cdb03edcd5234b9ba5dd6e186c2a5ca0efe817f78afd2729b919322f0cbfcc0", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_install_custom_version/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2c2be29eaf5026cb751a24284ace2e84a0172460f929008a7267bdfab9d77991", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_install_default_deb", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/role_install_default_deb/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "09c96f594df40aa4594df24eae84a309a41078f2b5964adaf88650961eae9786", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_install_default_deb/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a064a3118dae91ed67b5e0c57336439209a3ad34304c8ec7dbb97b3b750c52f4", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_install_default_deb/verify.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c4d0513f413851eaa1471d9b809066e36ee035d12fc358311955ef32d22f694c", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_install_default_rpm", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/role_install_default_rpm/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "09c96f594df40aa4594df24eae84a309a41078f2b5964adaf88650961eae9786", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_install_default_rpm/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "73e95af177c03d817a225c1ce68cdac372925249d89a6968041a71192feb1c6d", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_install_default_rpm/verify.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c4d0513f413851eaa1471d9b809066e36ee035d12fc358311955ef32d22f694c", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_install_downgrade", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/molecule/role_install_downgrade/converge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d87274c5bc8f0c3922bffce0baf273392b87fb6cfc0f82d4211f9e33615a5884", + "format": 1 + }, + { + "name": "tests/integration/molecule/role_install_downgrade/molecule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a14d6db1aa03cf0a5f22d94764dc4101af7c070108c69c54610a40db0d3366c7", + "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": "a8902a1d1eea3b9bae32533cecec1bf7eab225616092b4a045c68a97a3cfd236", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.11.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a8902a1d1eea3b9bae32533cecec1bf7eab225616092b4a045c68a97a3cfd236", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.12.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a8902a1d1eea3b9bae32533cecec1bf7eab225616092b4a045c68a97a3cfd236", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.13.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a8902a1d1eea3b9bae32533cecec1bf7eab225616092b4a045c68a97a3cfd236", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.14.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a8902a1d1eea3b9bae32533cecec1bf7eab225616092b4a045c68a97a3cfd236", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.15.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a8902a1d1eea3b9bae32533cecec1bf7eab225616092b4a045c68a97a3cfd236", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.9.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "de0a1d28d509982ef8ff2dc7216966ed6b3008ad60427058a52b4e1b1d86e4b3", + "format": 1 + }, + { + "name": "tests/sanity/validate-role-metadata.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5e06455bcce84f42f0ad39b10c375876390c859dfb2556ffb7f965868d2e9b0e", + "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/action", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/plugins/action/test_bonsai_asset.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "871bc396c94e567a91b05c54a5ad16ab5fe292cd44769a0a153ff8bf1dc98ef6", + "format": 1 + }, + { + "name": "tests/unit/plugins/filter", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/plugins/filter/test_backends.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e7cf82138ae1a81dc5d0ce6db0c7fb789bdd9bcc77f72d50ba2efd159088c4d1", + "format": 1 + }, + { + "name": "tests/unit/plugins/filter/test_package_name.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8c1635b5e7fa4647c9178bf8502e841baef47be79dce0757d69a54dbacc65d37", + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/test_arguments.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3121f352128997e9ea58abbcf59461d650b521ac8850215df6d45f33a5215168", + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/test_bonsai.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7ff1c4f9860b3da9fb53b6291588c8c1c5c07bb12f6e1c45c80bda67f7f43c4a", + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/test_client.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "addfba5e04ae22eaa7119df5a932aa75f8b2fae5e41d1cd2e4ff58e06d41d7bc", + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/test_http.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "95109bea3f5e9bdf9b8ffa5859f199d15df613519f3bac4fe442bbfb2252e9ed", + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/test_role_utils.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "93fee68e8e4c325004e0ad1a63871607e6c6453cc12cdb3871c1537db09b638d", + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/test_utils.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d08afb7804ff3f8b6027943c1f65009938c3293e696c796ad943fe4fad427861", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/common", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/common/utils.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9eccbf044ba71f3b9f2e4c8c86655e750d0abfb746f250bcb15cd56546590cbb", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_ad_auth_provider.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "88d306d1b3342477e23a74093718a485771270e80e975cef597f2d347813e4e7", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_asset.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5acfc811d1996e922e0da03704ff70c0bb0f16783810c4c26425552c3a26938a", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_asset_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a16e67d47c994d80a5853488600dc8a8b92ab4414783073a7aeff8eb579aef27", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_auth_provider_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7c0613ee09762fce2ca4a4586780875d1e677a5035a3027d85e209a456527ddb", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_bonsai_asset.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "82136ec7f29b91f1fc3579bc7fcd89e6ad04b61bb83f9e973f34c0e3c27e1266", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_check.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dc90d0ecd02b29aaae71e2243b072cf74dd716bc7310465d16e14d1bfe78372f", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_check_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eee13871e171c0794a59997220a459a860bbd61556845f6cf96719ddb5c79ab0", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_cluster.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "061a44ec3c0655853a136e01a7f4dd5d81e0d6866547e17e428f0001bd6732b2", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_cluster_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a3899d5ec41b9bf2bf1fa906667a00797bc8ce330a2a449ae7a423e055f52077", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_cluster_role.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ba3a056f56e4423245cbf08ad5603d3ab9f4abe685e208e4db96ffe33da42983", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_cluster_role_binding.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "de16415913adc61ac7fd4e53caacefdd1a43f58e4b5240ab15ec2abbfc525423", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_cluster_role_binding_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5dbfe0ca293d3b9894446c5e05974e1b51187b217d1d634db01d58bbbd06b583", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_cluster_role_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "379c271dc5dc1f68cffeaed9fa23b773cc68a6478dd2838f697a21f89ddcf0c3", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_datastore.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7c89afa9b5416779dc0e39fbac35a6b6834e805fc771c041d27425351e41deab", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_datastore_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "84a70506c2b917c456ba6ad8ecddf68e197b14b9d4eeb3964ad465a46111a490", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_entity.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3bc056f5759661d3dd497e1a17274d3de5a9e6c1be2a184b8debb213d82161ea", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_entity_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f7c84f01c9e6b12b7206c3762adbb2e7bedcdd9fe7788d53f9952b15039e68d3", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_etcd_replicator.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "393df7ccfc9dc9b2fa8098ac475e73594a9af918a324eb76c22be583a3529b93", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_etcd_replicator_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c96e92d05da16572893d66419fb16bbfed6e04cfa02c5e3709125fd84bdf5437", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_event.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e7c6b4e8b1dbde9ddad3de7e8f90f0916503936d8b8c1f1913d7508d6aa01409", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_event_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8fee8e2246f74a61a7cd1913d41298172033149218490b6d824ca7a95ae477a1", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_filter.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "84dc4c8b98d91c0b768ab4829dd5355dc1134bd03a7ebea35bd48bb2042f6000", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_filter_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e44e8b46140789b0d83d186a17423be9e30e4e8bffc3b1b4ca1c0a20153743b9", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_handler_set.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "92d2ae7a0fa1859e8955a6e4c789188b6316155f94978ebd17ce2bd19bf274e9", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_hook.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fe07af2e371eb273df4a04b614144d1e08b6d8baf4fa08d9210bb4249d7574e3", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_hook_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a847854deab3a0e69022ac957f666b372baa1ef6eb7feebe918dcf4e4b93488a", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_ldap_auth_provider.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b7bd8f8d935e45ec68c3e932c0dc06ad61cb0dee5a8a4ddd05106cf20442d437", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_mutator.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c4e6d54b436549595e3a9a817075e53a211e59aab55ac23ced055fc4c4626589", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_mutator_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9925a199b42447ddfade5ed36a2f8e453efd10119edc8fb2a5b00b3f322f0edb", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_namespace.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1c6c0516244674a8650efe22776fd33e57badaaa064e12b1ed5cead96900bca2", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_namespace_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cfbd0530d91f6b2532773b78480a87180433f2fc06db3f73d2fa368e62304b95", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_oidc_auth_provider.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5d91ceb486420b9efe8f9ff2e829d92a8773d06b780a1a0f6890d8290e47ae6b", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_pipe_handler.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "46528d93c9379ab4b16aae974fb19b4c3db03750b050044f7253147182b1c051", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_pipe_handler_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3ca59a788ddc46f76bcb965912ebe31854ffcc295fcbe2d2be7ae6a25d1e2df1", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_role.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c33cce895e0a209a7c3e9bf634fa49294df6a659db1198355deef181c5e2c97a", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_role_binding.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "feab97eb2ae29cc6c24d91ec4b325e4e9aec2b5bac41ccf9afd5fa269029ead0", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_role_binding_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bfaadac39deff693d35619eebd7f72b65ef111423bebe3bba941247903d83a69", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_role_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6a179d1ab7fa5aa18873b7b85127ac620b44f632fb8c542ce25c3d25192e7f99", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_secret.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f2b72c8660b78b33f3acd1aa7ff874cd6a104bc6391d8d56430c2ce780205c1f", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_secret_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "14c1c450ffbb6e25df0c52101c6b57baeb1b10c256f6552babac1b52a3a9a8d1", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_secrets_provider_env.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6c99934039a87a12663aaaf42ebe4677f5ce6643e07a8df7821938b8f911c083", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_secrets_provider_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5f7bbfecee49ca348f60862234a440a09eeb80e3f5a7209a73de32cc1fb49627", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_secrets_provider_vault.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "290c4d80e6722528598cd4bedfea312e38780e0ce33c39d7700bebe6171429b0", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_silence.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e9b2c9f2886bfd3e905eaa0dd8b36e90427d7693533335acbfeeffa6cab5bff4", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_silence_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "17253081f5391d67801ee3bb2bad00380d796026a82e0c3fa7a71e932819f34d", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_socket_handler.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1c6c8372257d8cb23635ca014529ed44547184c946dc6708761e4faa9d854e99", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_tessen.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cf15938a12906fb6e128368c86a29c159704b704ba720fb4cde60c7a636807c5", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_user.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "69831b92a8d7aa676811ecf8b96f25dd87412442412f4a37f47041030b639c7c", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_user_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a86969539a25edc7b257f3f6fbb6d66bbbd9c5ec056a225ab50afb435fca69f0", + "format": 1 + }, + { + "name": "tests/unit/requirements.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "06447fa81cc36d2bb270ccc7584d6fcd7adf61e700cd456642ce86d82ba4a8d2", + "format": 1 + }, + { + "name": "tools", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tools/windows-versions.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9f0e1b109a07d031b7741626fdb5efb11a06cd32e26d9faa6d1689ec2fc97b9f", + "format": 1 + }, + { + "name": "vagrant", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "vagrant/windows", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "vagrant/windows/Vagrantfile", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "577e722a616b4460c8616a3e157dff8bd9b0797c13f45746524aa092e41281ca", + "format": 1 + }, + { + "name": "vagrant/windows/back.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a77bcf415efb41013cdd8c5d792e58e204bce9c7fb2a00a54782d21290a22fde", + "format": 1 + }, + { + "name": "vagrant/windows/inventory.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f77cdd5742fc469605610da3a8477e9774e3ef1540542680517a108837911c23", + "format": 1 + }, + { + "name": "vagrant/windows/play.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "18933fb8ae7cc0906993f601b29f0898107c0b36204eb56d4dc7f14ff4bf480e", + "format": 1 + } + ], + "format": 1 +}
\ No newline at end of file diff --git a/ansible_collections/sensu/sensu_go/MANIFEST.json b/ansible_collections/sensu/sensu_go/MANIFEST.json new file mode 100644 index 00000000..71eedb23 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/MANIFEST.json @@ -0,0 +1,33 @@ +{ + "collection_info": { + "namespace": "sensu", + "name": "sensu_go", + "version": "1.13.2", + "authors": [ + "Paul Arthur <paul.arthur@flowerysong.com> (@flowerysong)", + "XLAB Steampunk <steampunk@xlab.si>" + ], + "readme": "README.md", + "tags": [ + "sensu", + "sensugo", + "monitoring" + ], + "description": "Roles and modules for installing and using Sensu Go", + "license": [], + "license_file": "COPYING", + "dependencies": {}, + "repository": "https://github.com/sensu/sensu-go-ansible", + "documentation": "https://sensu.github.io/sensu-go-ansible/", + "homepage": "https://sensu.github.io/sensu-go-ansible/", + "issues": "https://github.com/sensu/sensu-go-ansible/issues" + }, + "file_manifest_file": { + "name": "FILES.json", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "392d5becd5fe7bb69c6ad48108abdce5cfb54eb9791584d189e7783c36e252d8", + "format": 1 + }, + "format": 1 +}
\ No newline at end of file diff --git a/ansible_collections/sensu/sensu_go/Makefile b/ansible_collections/sensu/sensu_go/Makefile new file mode 100644 index 00000000..abadb048 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/Makefile @@ -0,0 +1,83 @@ +# Make sure we have ansible_collections/sensu/sensu_go as a prefix. This is +# ugly as heck, but it works. I suggest all future developer to treat next few +# lines as an opportunity to learn a thing or two about GNU make ;) +collection := $(notdir $(realpath $(CURDIR) )) +namespace := $(notdir $(realpath $(CURDIR)/.. )) +toplevel := $(notdir $(realpath $(CURDIR)/../..)) + +err_msg := Place collection at <WHATEVER>/ansible_collections/sensu/sensu_go +ifeq (true,$(CI)) + $(info Running in CI setting, skipping directory checks.) +else ifneq (sensu_go,$(collection)) + $(error $(err_msg)) +else ifneq (sensu,$(namespace)) + $(error $(err_msg)) +else ifneq (ansible_collections,$(toplevel)) + $(error $(err_msg)) +endif + +python_version := $(shell \ + python -c 'import sys; print(".".join(map(str, sys.version_info[:2])))' \ +) + +molecule_scenarios := $(wildcard tests/integration/molecule/*) + + +.PHONY: help +help: + @echo Available targets: + @fgrep "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sort + +.PHONY: sanity +sanity: ## Run sanity tests + pip3 install -r sanity.requirements -r collection.requirements + pip install pyyaml + flake8 + if which ansible-lint 2> /dev/null; then ansible-lint -p roles/*; fi + ansible-test sanity --docker + python3 ./tests/sanity/validate-role-metadata.py roles/* + +.PHONY: units +units: ## Run unit tests + pip3 install -r collection.requirements + -ansible-test coverage erase # On first run, there is nothing to erase. + ansible-test units --docker --coverage + ansible-test coverage html --requirements + ansible-test coverage report --omit 'tests/*' --show-missing + +.PHONY: integration +integration: ## Run integration tests + pip3 install -r integration.requirements -r collection.requirements + pytest -s --molecule-base-config=base.yml tests/integration/molecule + +.PHONY: $(molecule_scenarios) +$(molecule_scenarios): + pytest -s --molecule-base-config=base.yml $@ + +.PHONY: integration_ci +integration_ci: ## Run integration tests on CircleCI + pip3 install -r integration.requirements -r collection.requirements + mkdir -p test_results/integration + pytest -s \ + --junitxml=test_results/integration/junit.xml \ + --molecule-base-config=base.yml \ + $$(circleci tests glob "tests/integration/molecule/*/molecule.yml" \ + | circleci tests split --split-by=timings) + +.PHONY: docs +docs: ## Build collection documentation + pip3 install -r docs.requirements + $(MAKE) -C docs -f Makefile.custom docs + +.PHONY: clean +clean: ## Remove all auto-generated files + $(MAKE) -C docs -f Makefile.custom clean + rm -rf tests/output test_results + +.PHONY: check_windows_versions +check_windows_versions: ## Check if our and upstream versions drifed apart + tools/windows-versions.py check roles/install/vars/Windows.yml + +.PHONY: update_windows_versions +update_windows_versions: ## Update Windows versions in variable file + tools/windows-versions.py update roles/install/vars/Windows.yml diff --git a/ansible_collections/sensu/sensu_go/README.md b/ansible_collections/sensu/sensu_go/README.md new file mode 100644 index 00000000..82dd241b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/README.md @@ -0,0 +1,26 @@ +Development: [![Development status](https://circleci.com/gh/sensu/sensu-go-ansible/tree/master.svg?style=shield)](https://app.circleci.com/pipelines/github/sensu/sensu-go-ansible?branch=master) | +Stable: [![Stable status](https://circleci.com/gh/sensu/sensu-go-ansible/tree/stable.svg?style=shield)](https://app.circleci.com/pipelines/github/sensu/sensu-go-ansible?branch=stable) + + +# Sensu Go Ansible Collection + +Sensu Go Ansible Collection is a bundle of Ansible content that we can use to +manage all aspects of Sensu Go. It contains Ansible roles for installing and +configuring backends and agents. Collection also contains a wide selection of +modules for runtime management of Sensu Go backend. + +Collection is freely available on [Ansible Galaxy][galaxy]. For Red Hat +subscribers, this collection is also available on [Automation Hub][hub]. + + [galaxy]: https://galaxy.ansible.com/sensu/sensu_go + (Sensu Go on Ansible Galaxy) + [hub]: https://cloud.redhat.com/ansible/automation-hub/sensu/sensu_go + (Sensu Go on Automation Hub) + +For user guides and references, please visit [the documentation site][docs]. +And if you would like to help us out, check our [hacking docs][hacking]. + + [docs]: https://sensu.github.io/sensu-go-ansible/ + (Sensu Go Ansible Collection documentation) + [hacking]: https://sensu.github.io/sensu-go-ansible/hacking.html + (Developer guides) diff --git a/ansible_collections/sensu/sensu_go/changelogs/changelog.yaml b/ansible_collections/sensu/sensu_go/changelogs/changelog.yaml new file mode 100644 index 00000000..42569b99 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/changelogs/changelog.yaml @@ -0,0 +1,456 @@ +--- +releases: + 1.13.2: + release_date: "2022-12-22" + changes: + release_summary: | + Support for latest Ansible and Rocky and Alma linux + minor_changes: + - Added support for ansible 2.14 + - Added support for Rocky and Alma linux + 1.13.1: + release_date: "2022-04-19" + changes: + release_summary: | + Support for latest Ansible + minor_changes: + - Added support for ansible 2.13 + - Removed support for CentOS 8 + 1.13.0: + release_date: "2022-01-17" + changes: + release_summary: | + Bonsai asset definitions can be downloaded on controller or remote nodes + + A user can decide if Bonsai asset definitions should be downloaded to + remote nodes or the controller node. This is useful, but not limited to, + in case that the controller node's Internet connection is unstable or in + general worse than that of the remote nodes. + minor_changes: + - Added argument remote_on inside bonsai_asset module + + 1.12.1: + release_date: "2021-12-20" + changes: + release_summary: | + Keeping up with updates + minor_changes: + - Add Sensu Go 6.5.5 Windows metadata + - Add sensu Go 6.6.2 Windows metadata + 1.12.0: + release_date: "2021-08-31" + changes: + release_summary: | + Keeping up with the updates + + In this release, community contributed support for the OracleLinux. We + added a few tests to catch if things break in the future and this is + about it. And while we were at work, we also added support for Sensu Go + 6.4.0 and 6.4.1 on Windows. + minor_changes: + - Add support for OracleLinux. + - Add Sensu Go 6.4.0 Windows metadata. + - Add Sensu Go 6.4.1 Windows metadata. + + 1.11.1: + release_date: "2021-05-27" + changes: + release_summary: | + Sensu Go 6.3.0 is here + + For this release, we only updated the list of available Sensu Go agent + versions for Windows, and made sure collection works with the latest + Sensu Go version. + minor_changes: + - Update list of available Sensu Go agent packages for Windows + installations (added 6.3.0). + + 1.11.0: + release_date: "2021-05-18" + changes: + release_summary: | + Validate all the things! + + If you ever thought to yourself, "Ansible does not yell enough at me," + we have some great news. The Sensu Go Ansible Collection gained role + argument specifications, making it possible to validate variable values + before executing a role. You are welcome ;) + minor_changes: + - Add argument specification to the install role. + - Add argument specification to the backend role. + - Add argument specification to the agent role. + + 1.10.0: + release_date: "2021-05-04" + changes: + release_summary: | + Authentication, authentication on the wall, Who has Access to Them All? + + New modules allow Sensu Go users to configure authentication within + their Ansible playbooks. The users can authenticate via external + authentication providers such as Lightweight Directory Access Protocol (LDAP), + Active Directory (AD), or OpenID Connect 1.0 protocol (OIDC). + minor_changes: + - Add modules for managing Sensu Go authentication providers. + modules: + - name: ad_auth_provider + description: Manage Sensu AD authentication provider + namespace: "" + - name: ldap_auth_provider + description: Manage Sensu LDAP authentication provider + namespace: "" + - name: oidc_auth_provider + description: Manage Sensu OIDC authentication provider + namespace: "" + - name: auth_provider_info + description: List Sensu authentication providers + namespace: "" + + 1.9.4: + release_date: "2021-03-30" + changes: + release_summary: | + Opening Windows for real + + This is a bugfix release that makes sure Sensu Go Ansible Collection + can operate even in the absence of the Windows Ansible Collection + (assuming we do not want to manage agents on Windows hosts, that is). + bugfixes: + - Make sure we lazy-load Windows-related content. + + 1.9.3: + release_date: "2021-03-30" + changes: + release_summary: | + Opening Windows + + The only change in this release is removal of the ``ansible.windows`` + dependency. This should allow users that only use certified collections + to install and use the collection. + minor_hanges: + - Remove Windows Ansible Collection dependency. + + 1.9.2: + release_date: "2021-03-28" + changes: + release_summary: | + A fresh batch of updates + + For this release, we only updated the list of available Sensu Go agent + versions for Windows. + minor_hanges: + - Update list of available Sensu Go agent packages for Windows + installations (added 5.21.4, 5.21.5, 6.2.5, and 6.2.6). + 1.9.1: + release_date: "2021-03-08" + changes: + release_summary: | + Containerize all the things + + There are two main reasons for this release. We made sure the Sensu Go + Ansible Collection works with development version of Ansible (upcoming + ansible-core 2.11). And we added enough metadata to the collection that + ansible-builder can create an execution environment with the Sensu Go + Ansible Collection without having to manually specify dependencies. + bugfixes: + - Add ansible.windows dependency that we forgot to add when we + introducted the Sensu Go agent installation on Windows. + + 1.9.0: + release_date: "2021-02-28" + changes: + release_summary: | + Multi-cluster visibility with federation + + Two new module pairs allow Sensu Go users to configure federation from + the comfort of their Ansible playbooks. + minor_changes: + - Add modules for managing etcd replicatiors, which form the basis of + the Sensu Go federation. + - Add modules for managing Sensu Go clusters. + - Update list of available Sensu Go agent packages for Windows + installations. + bugfixes: + - Allow downgrading Sensu Go packages on Linux distributions that use + yum or dnf for package management. + modules: + - name: cluster + description: Manage Sensu Go clusters + namespace: "" + - name: cluster_info + description: List available Sensu Go clusters + namespace: "" + - name: etcd_replicator + description: Manage Sensu Go etcd replicators + namespace: "" + - name: etcd_replicator_info + description: List Sensu Go etcd replicators + namespace: "" + + 1.8.0: + release_date: "2021-01-26" + changes: + release_summary: | + Supporting hashed user passwords + + Starting with this release, Sensu Go users can use password hashes + directly when manipulating role-based access control resources. + minor_changes: + - Add support for hashed password in user module. + bugfixes: + - Mimic actual responses when user module runs in check mode. + - Make it possible to use modules on Sensu Go backends with no version + number. + + 1.7.2: + release_date: "2021-01-21" + changes: + release_summary: | + Be kind + + The main thing in this release is a small adjustment of our code of + conduct that is a bit more generic and less event-focused. + minor_changes: + - Specify minimal python version for modules. + - Update code of conduct. + - List version 6.2.1 and 6.2.2 in Windows lookup table. + + 1.7.1: + release_date: "2020-12-30" + changes: + release_summary: | + Slow and steady + + There are no major new features in this release, just honest little + fixes that should make using Sensu Go Ansible Collection a bit more + pleasant. + minor_changes: + - List version 6.2.0 and 6.1.3 in Windows lookup table. + - Add module return value samples. + + 1.7.0: + release_date: "2020-12-14" + changes: + release_summary: | + Say hello to Amazon Linux and Windows + + As the title suggests, we worked hard to bring you two new supported + platforms to the Sensu Go Ansible Colletions. And yes, all your + existing playbooks still work.All you need to do is run them against + the right host and voila ;) + minor_changes: + - Add support for installing Sensu Go on Amazon Linux. + - Add support for installing Sensu Go agents on Windows. + + 1.6.1: + release_date: "2020-11-04" + changes: + release_summary: | + Comparing entities is hard + + This is a bugfix release that makes sure agent entity changes are + properly detected. + bugfixes: + - Make subscriptions comparison insensitive to ordering. + - Make sure agent entities handle *entity:{name}* automatic + subscriptions. + + 1.6.0: + release_date: "2020-10-12" + changes: + release_summary: | + Our little secret + + This release contains a few new modules that allow you to manage all + things related to the Sensu Go secrets: from adding secrets providers + to passing secrets to resources that know how to use them. + minor_changes: + - Add modules for managing Sensu Go secret providers. + - Add modules for managing Sensu Go secrets. + - Add support for secrets to pipe handler module. + - Add support for secrets to check module. + - Add support for secrets to mutator module. + modules: + - name: secret + description: Manage Sensu Go secrets + namespace: "" + - name: secret_info + description: List available Sensu Go secrets + namespace: "" + - name: secrets_provider_env + description: Manage Sensu Env secrets provider + namespace: "" + - name: secrets_provider_vault + description: Manage Sensu VaultProvider secrets provider + namespace: "" + - name: secrets_provider_info + description: List Sensu secrets providers + namespace: "" + + 1.5.0: + release_date: "2020-07-24" + changes: + release_summary: | + Self-signed security + + The primary focus of this release is to enable configuration of Sensu + Go backends that use certificates that are not considered trusted when + using system-provided CA bundle. + minor_changes: + - Allow modules to supply custom CA bundle for backend certificate + validation or skip the validation entirely. + bugfixes: + - Expand documentation about the *check_hooks* parameter in the check + module. + - Explain how the resource name parameter is used and what invariants + need to hold in order for the Sensu Go to consider it a valid name. + + 1.4.2: + release_date: "2020-07-02" + changes: + release_summary: | + Break the fall + + There is really only one reason for this release: making sure user + management works with Sensu Go 5.21.0 and newer. And while the + upstream did break the API, we did not, so all your playbooks should + function as nothing happened. We had to add a *bcrypt* dependency to + our collection so make sure it is installed on hosts that will execute + the user module. + bugfixes: + - Make sure check module is as idempotent as possible. + - Make user module compatible with Sensu Go >= 5.21.0. + + 1.4.1: + release_date: "2020-06-25" + changes: + release_summary: | + Maintenance is the name of the game + + There are no nothing earth-shattering changes in this release, just + honest little bug fixes and compatibility improvements. + + **NOTE:** The *sensu.sensu_go.user* module currently **DOES NOT** work + on Sensu Go 5.21.0 and later. This is a know issue that will be fixed + as soon as the updated user-related backend API endpoints are + documented. + bugfixes: + - Make sure event module always returns a predicted result. + - Make user module fully-idempotent. Previous versions did not + properly detect the password changes. + - Use fully-qualified collection names in module documentation. + - Ensure backend initialization properly reports changed state. + - Make API key authentication work even for regular users with limited + permissions. + - Update the datastore module to cope with the minor API changes. + + 1.4.0: + release_date: "2020-04-30" + changes: + release_summary: | + Keeping up with the world + + Main changes in this release are related to updates in the Sensu Go's + web API that broke our change detection. + minor_changes: + - Add support for RHEL and CentOS 8. + bugfixes: + - Fix resource metadata comparison on Sensu Go 5.19.0 and newer. + - Update entity comparator to handle new fields. + + 1.3.1: + release_date: "2020-03-21" + changes: + release_summary: | + Bug fixing galore + + This release makes it possible to use the *asset* module when + replacing the deprecated, single-build assets that were created by + means other than Ansible. + bugfixes: + - Do not die when encountering a deprecated asset format. + - Update return value documentation for info modules. + - Add Sensu Go 5.17.x and 5.18.x to the test suite and remove the + unsupported versions (5.14.2 and lower). + - Update the role metadata with proper platform markers. + - Remove unsupported Ubuntu versions from the test suite. + + 1.3.0: + release_date: "2020-02-03" + changes: + release_summary: | + Authenticating with style on Debian + + Sensu Go 5.15.0 gained an API key authentication method and the + Ansible collection finally caught up. This means that we can now + replace *user* and *password* authentication parameters with a single + *api_key* value. + + And the other big news is the addition of Debian support to the + `install` role. + minor_changes: + - Add API key authentication support. + - Add support for Debian installation. + + 1.2.0: + release_date: "2020-01-17" + changes: + release_summary: | + Building support for builds + + This release adds support for specifying builds when installing + various Sensu Go components. + minor_changes: + - Add *build* variable to the *install* role that further pins down + the package version that gets installed. + + 1.1.1: + release_date: "2020-01-08" + changes: + release_summary: | + Python 2 is Still a Thing + + This is a bugfix release that makes sure the Sensu collection is + working when Ansible control node uses Python 2. + minor_changes: + - Add support for RHEL 7 to the install role (thanks, @danragnar). + bugfixes: + - Accept *str* and *unicode* instance as a valid string in + *bonsai_asset* action plugin. + + 1.1.0: + release_date: "2019-12-28" + changes: + release_summary: | + Hello Sensu Go 5.16 + + This is the first release that supports installing Sensu Go 5.16. + minor_changes: + - Support for Sensu Go 5.16 initialization in backend role. + - Support for external datastore management using *datastore* and + *datastore_info* modules. + bugfixes: + - Reintroduce namespace support to *bonsai_asset* module (thanks, + @jakeo) + modules: + - name: datastore + description: Manage Sensu external datastore providers + namespace: "" + - name: datastore_info + description: List external Sensu datastore providers + namespace: "" + + 1.0.0: + release_date: "2019-12-09" + changes: + release_summary: | + Rising From The Ashes + + This is the initial stable release of the Sensu Go Ansible Collection. + It contains roles for installing and configuring Sensu Go backends and + agents and a set of modules for managing Sensu Go resources. + + Where does the release name comes from? We took an existing Ansible + Collection that @flowerysong wrote, gave it a thorough tune-up and + added a comprehensive test suite. And now, it is ready to face the + world! diff --git a/ansible_collections/sensu/sensu_go/collection.requirements b/ansible_collections/sensu/sensu_go/collection.requirements new file mode 100644 index 00000000..7f0b6e75 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/collection.requirements @@ -0,0 +1 @@ +bcrypt diff --git a/ansible_collections/sensu/sensu_go/docker/alma-8.docker b/ansible_collections/sensu/sensu_go/docker/alma-8.docker new file mode 100644 index 00000000..88326b2f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/alma-8.docker @@ -0,0 +1,6 @@ +FROM almalinux:8 +RUN dnf makecache \ + && dnf install -y \ + /usr/bin/python3 /usr/bin/python3-config /usr/bin/dnf-3 \ + sudo bash iproute \ + && dnf clean all diff --git a/ansible_collections/sensu/sensu_go/docker/amazon-1.docker b/ansible_collections/sensu/sensu_go/docker/amazon-1.docker new file mode 100644 index 00000000..f6316c35 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/amazon-1.docker @@ -0,0 +1,9 @@ +FROM amazonlinux:1 +ENV container docker +RUN \ + yum makecache fast; \ + yum install -y \ + /usr/bin/python /usr/bin/python2-config sudo \ + yum-plugin-ovl bash iproute shadow-utils; \ + sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf; \ + yum clean all diff --git a/ansible_collections/sensu/sensu_go/docker/amazon-2.docker b/ansible_collections/sensu/sensu_go/docker/amazon-2.docker new file mode 100644 index 00000000..734c3c95 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/amazon-2.docker @@ -0,0 +1,9 @@ +FROM amazonlinux:2 +ENV container docker +RUN \ + yum makecache fast; \ + yum install -y \ + /usr/bin/python /usr/bin/python2-config sudo \ + yum-plugin-ovl bash iproute shadow-utils; \ + sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf; \ + yum clean all diff --git a/ansible_collections/sensu/sensu_go/docker/build-all.sh b/ansible_collections/sensu/sensu_go/docker/build-all.sh new file mode 100755 index 00000000..dae03194 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/build-all.sh @@ -0,0 +1,8 @@ +#!/bin/bash -eu + +set -o pipefail + +for f in *.docker +do + ./build.sh "$f" +done diff --git a/ansible_collections/sensu/sensu_go/docker/build.sh b/ansible_collections/sensu/sensu_go/docker/build.sh new file mode 100755 index 00000000..a6c56fcb --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/build.sh @@ -0,0 +1,13 @@ +#!/bin/bash -eu + +set -o pipefail + +readonly filename="$1"; shift + +readonly base=${filename%.docker} +readonly name=${base%-*} +readonly version=${base##*-} +readonly tag="quay.io/xlab-steampunk/sensu-go-tests-$name:$version" + +docker build --pull -f "$filename" -t "$tag" . +docker push "$tag" diff --git a/ansible_collections/sensu/sensu_go/docker/centos-6.docker b/ansible_collections/sensu/sensu_go/docker/centos-6.docker new file mode 100644 index 00000000..1e7ec0e4 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/centos-6.docker @@ -0,0 +1,7 @@ +FROM centos:6 +RUN yum makecache fast \ + && yum install -y \ + /usr/bin/python /usr/bin/python2-config sudo \ + yum-plugin-ovl bash iproute \ + && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf \ + && yum clean all diff --git a/ansible_collections/sensu/sensu_go/docker/centos-7.docker b/ansible_collections/sensu/sensu_go/docker/centos-7.docker new file mode 100644 index 00000000..c11f9b55 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/centos-7.docker @@ -0,0 +1,25 @@ +FROM centos:7 +ENV container docker +RUN ( \ + cd /lib/systemd/system/sysinit.target.wants/; \ + for i in *; do \ + [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; \ + done \ + ); \ + rm -f /lib/systemd/system/multi-user.target.wants/*;\ + rm -f /etc/systemd/system/*.wants/*;\ + rm -f /lib/systemd/system/local-fs.target.wants/*; \ + rm -f /lib/systemd/system/sockets.target.wants/*udev*; \ + rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \ + rm -f /lib/systemd/system/basic.target.wants/*;\ + rm -f /lib/systemd/system/anaconda.target.wants/*; \ + yum makecache fast; \ + yum install -y \ + /usr/bin/python /usr/bin/python2-config sudo \ + yum-plugin-ovl bash iproute; \ + sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf; \ + yum clean all; \ + chmod 777 /root; + +VOLUME [ "/sys/fs/cgroup" ] +CMD [ "/usr/sbin/init" ] diff --git a/ansible_collections/sensu/sensu_go/docker/centos-8.docker b/ansible_collections/sensu/sensu_go/docker/centos-8.docker new file mode 100644 index 00000000..11ceae57 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/centos-8.docker @@ -0,0 +1,6 @@ +FROM centos:8 +RUN dnf makecache \ + && dnf install -y \ + /usr/bin/python3 /usr/bin/python3-config /usr/bin/dnf-3 \ + sudo bash iproute \ + && dnf clean all diff --git a/ansible_collections/sensu/sensu_go/docker/debian-10.docker b/ansible_collections/sensu/sensu_go/docker/debian-10.docker new file mode 100644 index 00000000..c53007c0 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/debian-10.docker @@ -0,0 +1,8 @@ +FROM debian:10 +RUN apt-get update \ + && apt-get install -y \ + python sudo bash ca-certificates iproute2 python-apt aptitude \ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf /usr/share/doc \ + && rm -rf /usr/share/man \ + && apt-get clean diff --git a/ansible_collections/sensu/sensu_go/docker/debian-9.docker b/ansible_collections/sensu/sensu_go/docker/debian-9.docker new file mode 100644 index 00000000..a0013d77 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/debian-9.docker @@ -0,0 +1,5 @@ +FROM debian:9 +RUN apt-get update \ + && apt-get install -y \ + python sudo bash ca-certificates iproute2 python-apt aptitude \ + && apt-get clean diff --git a/ansible_collections/sensu/sensu_go/docker/oracle-8.docker b/ansible_collections/sensu/sensu_go/docker/oracle-8.docker new file mode 100644 index 00000000..c63a2bf7 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/oracle-8.docker @@ -0,0 +1,6 @@ +FROM oraclelinux:8 +RUN dnf makecache \ + && dnf install -y \ + /usr/bin/python3 /usr/bin/python3-config /usr/bin/dnf-3 \ + sudo bash iproute \ + && dnf clean all diff --git a/ansible_collections/sensu/sensu_go/docker/redhat-7.docker b/ansible_collections/sensu/sensu_go/docker/redhat-7.docker new file mode 100644 index 00000000..f48b1701 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/redhat-7.docker @@ -0,0 +1,7 @@ +FROM registry.access.redhat.com/ubi7/ubi-init:latest +RUN yum makecache fast \ + && yum install -y \ + /usr/bin/python /usr/bin/python2-config sudo \ + yum-plugin-ovl bash iproute \ + && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf \ + && yum clean all diff --git a/ansible_collections/sensu/sensu_go/docker/rocky-8.docker b/ansible_collections/sensu/sensu_go/docker/rocky-8.docker new file mode 100644 index 00000000..b9e149a8 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/rocky-8.docker @@ -0,0 +1,6 @@ +FROM rockylinux:8 +RUN dnf makecache \ + && dnf install -y \ + /usr/bin/python3 /usr/bin/python3-config /usr/bin/dnf-3 \ + sudo bash iproute \ + && dnf clean all diff --git a/ansible_collections/sensu/sensu_go/docker/sensu-5.14.2.docker b/ansible_collections/sensu/sensu_go/docker/sensu-5.14.2.docker new file mode 100644 index 00000000..ec833991 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/sensu-5.14.2.docker @@ -0,0 +1,8 @@ +FROM sensu/sensu:5.14.2 +RUN apk update \ + && apk add --no-cache python sudo bash ca-certificates +CMD [ \ + "sensu-backend", "start", \ + "--state-dir", "/var/lib/sensu/sensu-backend", \ + "--log-level", "debug" \ +] diff --git a/ansible_collections/sensu/sensu_go/docker/sensu-5.21.3.docker b/ansible_collections/sensu/sensu_go/docker/sensu-5.21.3.docker new file mode 100644 index 00000000..023f33c8 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/sensu-5.21.3.docker @@ -0,0 +1,9 @@ +FROM sensu/sensu:5.21.3 +RUN apk update \ + && apk add --no-cache python sudo bash ca-certificates \ + py-bcrypt py-six py-cffi +CMD [ \ + "sensu-backend", "start", \ + "--state-dir", "/var/lib/sensu/sensu-backend", \ + "--log-level", "debug" \ +] diff --git a/ansible_collections/sensu/sensu_go/docker/sensu-6.2.5.docker b/ansible_collections/sensu/sensu_go/docker/sensu-6.2.5.docker new file mode 100644 index 00000000..9fd19072 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/sensu-6.2.5.docker @@ -0,0 +1,8 @@ +FROM sensu/sensu:6.2.5 +RUN apk update \ + && apk add --no-cache python3 py3-bcrypt py3-six py3-cffi bash +CMD [ \ + "sensu-backend", "start", \ + "--state-dir", "/var/lib/sensu/sensu-backend", \ + "--log-level", "debug" \ +] diff --git a/ansible_collections/sensu/sensu_go/docker/sensu-6.3.0.docker b/ansible_collections/sensu/sensu_go/docker/sensu-6.3.0.docker new file mode 100644 index 00000000..12399d3b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/sensu-6.3.0.docker @@ -0,0 +1,8 @@ +FROM sensu/sensu:6.3.0 +RUN apk update \ + && apk add --no-cache python3 py3-bcrypt py3-six py3-cffi bash +CMD [ \ + "sensu-backend", "start", \ + "--state-dir", "/var/lib/sensu/sensu-backend", \ + "--log-level", "debug" \ +] diff --git a/ansible_collections/sensu/sensu_go/docker/sensu-6.4.3.docker b/ansible_collections/sensu/sensu_go/docker/sensu-6.4.3.docker new file mode 100644 index 00000000..7afcd120 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/sensu-6.4.3.docker @@ -0,0 +1,8 @@ +FROM sensu/sensu:6.4.3 +RUN apk update \ + && apk add --no-cache python3 py3-bcrypt py3-six py3-cffi bash +CMD [ \ + "sensu-backend", "start", \ + "--state-dir", "/var/lib/sensu/sensu-backend", \ + "--log-level", "debug" \ +] diff --git a/ansible_collections/sensu/sensu_go/docker/sensu-6.5.5.docker b/ansible_collections/sensu/sensu_go/docker/sensu-6.5.5.docker new file mode 100644 index 00000000..1ceea051 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/sensu-6.5.5.docker @@ -0,0 +1,8 @@ +FROM sensu/sensu:6.5.5 +RUN apk update \ + && apk add --no-cache python3 py3-bcrypt py3-six py3-cffi bash +CMD [ \ + "sensu-backend", "start", \ + "--state-dir", "/var/lib/sensu/sensu-backend", \ + "--log-level", "debug" \ +] diff --git a/ansible_collections/sensu/sensu_go/docker/sensu-6.6.2.docker b/ansible_collections/sensu/sensu_go/docker/sensu-6.6.2.docker new file mode 100644 index 00000000..613e7c28 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/sensu-6.6.2.docker @@ -0,0 +1,8 @@ +FROM sensu/sensu:6.6.2 +RUN apk update \ + && apk add --no-cache python3 py3-bcrypt py3-six py3-cffi bash +CMD [ \ + "sensu-backend", "start", \ + "--state-dir", "/var/lib/sensu/sensu-backend", \ + "--log-level", "debug" \ +] diff --git a/ansible_collections/sensu/sensu_go/docker/sensu-6.7.5.docker b/ansible_collections/sensu/sensu_go/docker/sensu-6.7.5.docker new file mode 100644 index 00000000..058ea335 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/sensu-6.7.5.docker @@ -0,0 +1,8 @@ +FROM sensu/sensu:6.7.5 +RUN apk update \ + && apk add --no-cache python3 py3-bcrypt py3-six py3-cffi bash +CMD [ \ + "sensu-backend", "start", \ + "--state-dir", "/var/lib/sensu/sensu-backend", \ + "--log-level", "debug" \ +] diff --git a/ansible_collections/sensu/sensu_go/docker/sensu-6.8.2.docker b/ansible_collections/sensu/sensu_go/docker/sensu-6.8.2.docker new file mode 100644 index 00000000..12228196 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/sensu-6.8.2.docker @@ -0,0 +1,8 @@ +FROM sensu/sensu:6.8.2 +RUN apk update \ + && apk add --no-cache python3 py3-bcrypt py3-six py3-cffi bash +CMD [ \ + "sensu-backend", "start", \ + "--state-dir", "/var/lib/sensu/sensu-backend", \ + "--log-level", "debug" \ +] diff --git a/ansible_collections/sensu/sensu_go/docker/sensu-6.9.0.docker b/ansible_collections/sensu/sensu_go/docker/sensu-6.9.0.docker new file mode 100644 index 00000000..c0d99556 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/sensu-6.9.0.docker @@ -0,0 +1,8 @@ +FROM sensu/sensu:6.9.0 +RUN apk update \ + && apk add --no-cache python3 py3-bcrypt py3-six py3-cffi bash +CMD [ \ + "sensu-backend", "start", \ + "--state-dir", "/var/lib/sensu/sensu-backend", \ + "--log-level", "debug" \ +] diff --git a/ansible_collections/sensu/sensu_go/docker/ubuntu-14.04.docker b/ansible_collections/sensu/sensu_go/docker/ubuntu-14.04.docker new file mode 100644 index 00000000..720f50d4 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/ubuntu-14.04.docker @@ -0,0 +1,8 @@ +FROM ubuntu:14.04 +RUN rm \ + /etc/apt/apt.conf.d/20apt-esm-hook.conf \ + /etc/apt/sources.list.d/ubuntu-esm-infra-trusty.list \ + && apt-get update \ + && apt-get install -y \ + python sudo bash ca-certificates iproute2 python-apt aptitude \ + && apt-get clean diff --git a/ansible_collections/sensu/sensu_go/docker/ubuntu-16.04.docker b/ansible_collections/sensu/sensu_go/docker/ubuntu-16.04.docker new file mode 100644 index 00000000..37a96a02 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/ubuntu-16.04.docker @@ -0,0 +1,5 @@ +FROM ubuntu:16.04 +RUN apt-get update \ + && apt-get install -y \ + python sudo bash ca-certificates iproute2 python-apt aptitude \ + && apt-get clean diff --git a/ansible_collections/sensu/sensu_go/docker/ubuntu-18.04.docker b/ansible_collections/sensu/sensu_go/docker/ubuntu-18.04.docker new file mode 100644 index 00000000..7d6230af --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docker/ubuntu-18.04.docker @@ -0,0 +1,5 @@ +FROM ubuntu:18.04 +RUN apt-get update \ + && apt-get install -y \ + python sudo bash ca-certificates iproute2 python-apt aptitude \ + && apt-get clean diff --git a/ansible_collections/sensu/sensu_go/docs.requirements b/ansible_collections/sensu/sensu_go/docs.requirements new file mode 100644 index 00000000..a28b3763 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs.requirements @@ -0,0 +1,3 @@ +Sphinx +sphinx-rtd-theme +ansible-doc-extractor diff --git a/ansible_collections/sensu/sensu_go/docs/Makefile b/ansible_collections/sensu/sensu_go/docs/Makefile new file mode 100644 index 00000000..d0c3cbf1 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/ansible_collections/sensu/sensu_go/docs/Makefile.custom b/ansible_collections/sensu/sensu_go/docs/Makefile.custom new file mode 100644 index 00000000..b896ab9c --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/Makefile.custom @@ -0,0 +1,19 @@ +modules := $(wildcard ../plugins/modules/*.py) +module_docs := $(modules:../plugins/modules/%.py=source/modules/%.rst) +doc_fragments := $(wildcard ../plugins/doc_fragments/*.py) +template := templates/module.rst.j2 + +export ANSIBLE_COLLECTIONS_PATHS ?= $(realpath $(CURDIR)/../../../..) + + +.PHONY: all +docs: $(module_docs) + $(MAKE) html + +.PHONY: clean +clean: + rm -rf build source/modules + +source/modules/%.rst: ../plugins/modules/%.py $(doc_fragments) $(template) + mkdir -p source/modules + ansible-doc-extractor --template $(template) source/modules $< diff --git a/ansible_collections/sensu/sensu_go/docs/examples/installation/ansible.cfg b/ansible_collections/sensu/sensu_go/docs/examples/installation/ansible.cfg new file mode 100644 index 00000000..72427f5b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/examples/installation/ansible.cfg @@ -0,0 +1,10 @@ +[galaxy] +server_list = automation_hub, galaxy + +[galaxy_server.automation_hub] +url=https://cloud.redhat.com/api/automation-hub/ +auth_url=https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/token +token=AABBccddeeff112233gghh... + +[galaxy_server.galaxy] +url=https://galaxy.ansible.com/ diff --git a/ansible_collections/sensu/sensu_go/docs/examples/quickstart/inventory.yaml b/ansible_collections/sensu/sensu_go/docs/examples/quickstart/inventory.yaml new file mode 100644 index 00000000..baa986e7 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/examples/quickstart/inventory.yaml @@ -0,0 +1,9 @@ +all: + children: + backends: + hosts: + 192.168.50.4: + + agents: + hosts: + 192.168.50.5: diff --git a/ansible_collections/sensu/sensu_go/docs/examples/quickstart/playbook-5.yaml b/ansible_collections/sensu/sensu_go/docs/examples/quickstart/playbook-5.yaml new file mode 100644 index 00000000..eb89c7b5 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/examples/quickstart/playbook-5.yaml @@ -0,0 +1,51 @@ +--- +- name: Install, configure and run Sensu backend + hosts: backends + become: true + + tasks: + - name: Install backend + include_role: + name: sensu.sensu_go.backend + vars: + version: 5.21.2 + +- name: Install, configure and run Sensu agents + hosts: agents + become: true + + tasks: + - name: Install agent + include_role: + name: sensu.sensu_go.agent + vars: + version: 5.21.2 + agent_config: + deregister: true + keepalive-interval: 5 + keepalive-timeout: 10 + subscriptions: + - linux + +- name: Configure your first monitor + hosts: localhost + tasks: + - name: Create sensu asset + sensu.sensu_go.bonsai_asset: + auth: &auth + url: http://{{ groups['backends'][0] }}:8080 + name: sensu/monitoring-plugins + version: 2.2.0-1 + + - name: Create sensu ntp check + sensu.sensu_go.check: + auth: *auth + name: ntp + runtime_assets: sensu/monitoring-plugins + command: check_ntp_time -H time.nist.gov --warn 0.5 --critical 1.0 + output_metric_format: nagios_perfdata + publish: true + interval: 30 + timeout: 10 + subscriptions: + - linux diff --git a/ansible_collections/sensu/sensu_go/docs/examples/quickstart/playbook-6.yaml b/ansible_collections/sensu/sensu_go/docs/examples/quickstart/playbook-6.yaml new file mode 100644 index 00000000..184ce5b7 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/examples/quickstart/playbook-6.yaml @@ -0,0 +1,58 @@ +--- +- name: Install, configure and run Sensu backend + hosts: backends + become: true + + tasks: + - name: Install backend + include_role: + name: sensu.sensu_go.backend + vars: + version: 6.0.0 + +- name: Install, configure and run Sensu agents + hosts: agents + become: true + + tasks: + - name: Install agent + include_role: + name: sensu.sensu_go.agent + vars: + version: 6.0.0 + agent_config: + name: my-agent + keepalive-interval: 5 + keepalive-timeout: 10 + +- name: Configure your first monitor + hosts: localhost + tasks: + - name: Add subscriptions to agent entity + sensu.sensu_go.entity: + auth: &auth + url: http://{{ groups['backends'][0] }}:8080 + name: my-agent + entity_class: agent + deregister: true + subscriptions: + - linux + + - name: Create sensu asset + sensu.sensu_go.bonsai_asset: + auth: *auth + name: sensu/monitoring-plugins + version: 2.2.0-1 + + - name: Create sensu ntp check + sensu.sensu_go.check: + auth: *auth + name: ntp + runtime_assets: sensu/monitoring-plugins + command: check_ntp_time -H time.nist.gov --warn 0.5 --critical 1.0 + output_metric_format: nagios_perfdata + publish: true + interval: 30 + timeout: 10 + subscriptions: + - linux diff --git a/ansible_collections/sensu/sensu_go/docs/examples/roles/agent.yaml b/ansible_collections/sensu/sensu_go/docs/examples/roles/agent.yaml new file mode 100644 index 00000000..f90ac934 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/examples/roles/agent.yaml @@ -0,0 +1,8 @@ +--- +- name: Install, configure and run Sensu agent + hosts: agents + roles: + - sensu.sensu_go.agent + vars: + agent_config: + backend-url: [ "ws://upstream-backend:4321" ] diff --git a/ansible_collections/sensu/sensu_go/docs/examples/roles/backend.yaml b/ansible_collections/sensu/sensu_go/docs/examples/roles/backend.yaml new file mode 100644 index 00000000..9573a378 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/examples/roles/backend.yaml @@ -0,0 +1,7 @@ +- name: Install, configure and run Sensu backend + hosts: backends + roles: + - sensu.sensu_go.backend + vars: + backend_config: + log-level: debug diff --git a/ansible_collections/sensu/sensu_go/docs/examples/roles/install.yaml b/ansible_collections/sensu/sensu_go/docs/examples/roles/install.yaml new file mode 100644 index 00000000..4e028236 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/examples/roles/install.yaml @@ -0,0 +1,23 @@ +--- +- name: Install latest sensu-backend binary from stable channel + hosts: backends + roles: + - sensu.sensu_go.install + vars: + components: [ sensu-go-backend ] + +- name: Install latest sensu-agent binary from testing channel + hosts: agents + roles: + - sensu.sensu_go.install + vars: + components: [ sensu-go-agent ] + channel: testing + +- name: Install a specific version of sensuctl binary from stable channel + hosts: localhost + roles: + - sensu.sensu_go.install + vars: + components: [ sensu-go-cli ] + version: 5.14.2 diff --git a/ansible_collections/sensu/sensu_go/docs/source/conf.py b/ansible_collections/sensu/sensu_go/docs/source/conf.py new file mode 100644 index 00000000..46c1505f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/source/conf.py @@ -0,0 +1,17 @@ +project = "Sensu Go Ansible Collection" +copyright = "2019, XLAB Steampunk" +author = "XLAB Steampunk" + +extensions = [ + "sphinx_rtd_theme", +] +exclude_patterns = [] + +html_theme = "sphinx_rtd_theme" +html_context = { + "display_github": True, + "github_user": "sensu", + "github_repo": "sensu-go-ansible", + "github_version": "master", + "conf_py_path": "/docs/source/", +} diff --git a/ansible_collections/sensu/sensu_go/docs/source/hacking.rst b/ansible_collections/sensu/sensu_go/docs/source/hacking.rst new file mode 100644 index 00000000..8192945c --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/source/hacking.rst @@ -0,0 +1,17 @@ +Developing Sensu Go Ansible Collection +====================================== + +So, you have decided to help us out. Great! Let us set up a development +environment together, and then we can start hacking ;) + + +.. toctree:: + :maxdepth: 1 + :caption: Content + + hacking/setup + hacking/testing + hacking/docker-images + hacking/documentation + hacking/windows + hacking/releases diff --git a/ansible_collections/sensu/sensu_go/docs/source/hacking/docker-images.rst b/ansible_collections/sensu/sensu_go/docs/source/hacking/docker-images.rst new file mode 100644 index 00000000..6196dba0 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/source/hacking/docker-images.rst @@ -0,0 +1,19 @@ +Preparing docker images for integration tests +============================================= + +Our test suite relies heavily on custom docker images for testing. The main +reason that we use custom images is test speed: having prepared docker images +vs. building them on each test execution saves a ton of time. + +Adding a new image is relatively straightforward process. We can just copy one +of the preexisting definitions from the *docker* directory and update it. All +docker files should have a name in the format of ``<name>-<tag>.docker``. + +To build and publish our image, we can run the *docker/build.sh* script:: + + $ cd docker + $ ./build.sh sensu-5.21.0.docker + +Note that the command will add a *sensu-go-tests-* prefix to all images. In +the previous example, the build script will build the +*quay.io/xlab-steampunk/sensu-go-tests-sensu:5.21.0* image. diff --git a/ansible_collections/sensu/sensu_go/docs/source/hacking/documentation.rst b/ansible_collections/sensu/sensu_go/docs/source/hacking/documentation.rst new file mode 100644 index 00000000..7830b10e --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/source/hacking/documentation.rst @@ -0,0 +1,74 @@ +Documenting Sensu Go Ansible collection +======================================= + +Documentation for the Sensu Go Ansible collection consists of two distinct +kinds: the general guides and the API documentation. The general guides live +in the ``docs/source`` directory while the API documentation is part of the +modules' source code. + + +Authoring the documentation +--------------------------- + +Adding a new piece of general documentation is reasonably straightforward. We +need to create a new file somewhere inside the ``docs/source`` directory and +fill it with reStructuredText-formatted content. + +As a general rule, we use two levels of headings: a document title and as many +section titles as needed. If you feel you need something more complex, try +simplifying the documentation first. The end-users will thank you for this. + +First few lines of the document you are just reading look like this: + +.. literalinclude:: documentation.rst + :language: rst + :lines: 1-20 + +Once we have the content ready, we need to bind it with the rest of the +documentation. We do this by adding our document to one of the ``toctree`` +directives. We can find the important ones in the ``index.rst`` file. + +We follow the `upstream guides`_ when it comes to documenting the modules, +with one exception: ``version_added`` field holds the Sensu Go collection +version instead of Ansible version. Typical module starts with the next few +lines: + +.. literalinclude:: ../../../plugins/modules/handler_set.py + :language: python + :lines: 1-30 + +.. _upstream guides: + https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_documenting.html + + +Publishing the documentation +---------------------------- + +We use the `ansible-doc-extractor`_ tool to convert the embedded module +documentation into a set of reStructuredText documents. Once we have those +documents ready, we bind the various parts of the documentation together using +Sphinx_. In practice, we just run the ``make docs`` command in the root of the +repository and open the ``docs/build/html/index.html`` file in our browser of +choice. + +.. _ansible-doc-extractor: https://github.com/xlab-si/ansible-doc-extractor +.. _Sphinx: https://www.sphinx-doc.org + +We use `GitHub Pages`_ for hosting the online version of our documentation. To +update the content of our `documentation site`_, we must copy the rendered +documentation into the ``gh-pages`` branch, commit the changes, and push them +to GitHub. The described procedure translates into the following series of +steps:: + + (venv) $ make docs + (venv) $ git worktree add gh-pages gh-pages + (venv) $ rm -rf gh-pages/* + (venv) $ cp -r docs/build/html/* gh-pages + (venv) $ cd gh-pages + (venv) $ git add . && git commit + (venv) $ git push origin gh-pages + (venv) $ cd .. + (venv) $ git worktree remove gh-pages + +.. _GitHub Pages: https://pages.github.com/ +.. _documentation site: https://sensu.github.io/sensu-go-ansible/ diff --git a/ansible_collections/sensu/sensu_go/docs/source/hacking/releases.rst b/ansible_collections/sensu/sensu_go/docs/source/hacking/releases.rst new file mode 100644 index 00000000..edf993af --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/source/hacking/releases.rst @@ -0,0 +1,49 @@ +Releasing the Sensu Go Ansible Collection +========================================= + +The Sensu Go Ansible collection is primarily available from `Ansible Galaxy`_ +and `Automation Hub`_. Which means that we need to get our content up there +somehow. But before we can start uploading things, we need to do some chores +first. + +.. _Ansible Galaxy: + https://galaxy.ansible.com/sensu/sensu_go + +.. _Automation Hub: + https://cloud.redhat.com/ansible/automation-hub/sensu/sensu_go + + +First, we need to tag the commit and move the ``stable`` branch forward:: + + $ VERSION=$(grep version: galaxy.yml | cut -d" " -f2) + $ git tag -am "Version $VERSION" v$VERSION + $ git branch -f stable v$VERSION + +Now, we need to package the collection. Because the ``ansible-galaxy +collection build`` command will package anything that it can find next to the +``galaxy.yml`` file, we need to execute it in a clean environment. This is why +we will temporarily check out the *stable* branch into the *release* +subdirectory, build the collection package, and then delete the checkout. This +translates into the following sequence of commands:: + + $ git worktree add release stable + $ cd release + $ ansible-galaxy collection build + $ mv sensu-sensu_go-$VERSION.tar.gz .. + $ cd .. + $ git worktree remove release + +Now we can upload the package to Ansible Galaxy:: + + $ API_KEY=api-key-from-https://galaxy.ansible.com/me/preferences + $ ansible-galaxy collection publish \ + --api-key "$API_KEY" \ + sensu-sensu_go-$VERSION.tar.gz + +Last thing we need to do is push the ``stable`` branch and created tag to the +GitHub and attach the package to the GitHub release:: + + $ git push origin stable v$VERSION + +We need to attach the asset manually at the moment. Fully automated solution +is in the works. diff --git a/ansible_collections/sensu/sensu_go/docs/source/hacking/setup.rst b/ansible_collections/sensu/sensu_go/docs/source/hacking/setup.rst new file mode 100644 index 00000000..6bbd02dc --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/source/hacking/setup.rst @@ -0,0 +1,94 @@ +Preparing a development environment +=================================== + +In this guide, we will go through the process of setting up a development +environment. + + +Prerequisites +------------- + +There are a few things we will need to have installed on our workstation. They +are, in no particular order: + + * a python 3 interpreter with virtual environment support, + * ``git`` tools, + * a functioning docker installation (for integration tests), and + * SELinux python bindings (only on systems with SELinux installed). + +It should be relatively straightforward to get those things installed on our +machine. Once we have them ready, we can start customizing our environment for +Ansible development. + + +Obtaining the source code +------------------------- + +Sensu Go Ansible collection lives on GitHub, which means that we are just one +command away from the full source code. But before we can clone the +repository, we need to create a few directories that will hold the source +code:: + + $ mkdir -p ansible_collections/sensu + $ cd ansible_collections/sensu + $ git clone git@github.com:sensu/sensu-go-ansible.git sensu_go + $ cd sensu_go + +.. note:: + + It is vitally important that we + + 1. create two parent directories before checking out the code, and + 2. that we clone the code into the `sensu_go` directory. + + Ansible development tools often assume that we are working from the + ``ansible_collections/<namespace>/<collection>`` directory and complain + loudly if they do not have it their way. + +And this is it. + + +Installing development tools +---------------------------- + +The first thing we need to do is create a new virtual environment and activate +it. :: + + $ python3 -m venv ../../../venv + $ . ../../../venv/bin/activate + +.. note:: + + We intentionally created our virtual environment outside the collection + directory. This is a simple way of making sure that various static analysis + tools only report issues with our code and not with any of the + dependencies. + +Now we need to install Ansible. Since we are just starting, latest stable +version will do just fine:: + + (venv) $ pip install ansible + +All that separates us now from the fully functioning development environment +are a few dependencies. We can install them by running the next command:: + + (venv) $ make requirements + +There is just one more thing left for us to do: test the setup. + + +Testing the setup +----------------- + +To validate our setup, we can run the bundled tests and render the +documentation:: + + (venv) $ make sanity + (venv) $ make units + (venv) $ make docs + (venv) $ make -j4 integration + +We may want to go grab a cup of tea after running that last command, since it +will take a while (about fifteen minutes on a semi-decent development +machine). And if all of the commands finished with no errors, we are ready to +start developing. diff --git a/ansible_collections/sensu/sensu_go/docs/source/hacking/testing.rst b/ansible_collections/sensu/sensu_go/docs/source/hacking/testing.rst new file mode 100644 index 00000000..217749b9 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/source/hacking/testing.rst @@ -0,0 +1,125 @@ +Testing Sensu Go Ansible Collection +=================================== + +Sensu Go Ansible collection contains a comprehensive test suite. We use this +test suite to make sure collection's source code + +1. meets the Ansible code quality standards, +2. works with supported versions of Ansible, +3. works with supported versions of Sensu Go, and +4. works with supported versions of Python. + + +Static analysis +--------------- + +We use a wide variety of linters and static code analysis tools to ensure our +code base stays healthy. To run all checks, we can execute the following +command:: + + (venv) $ make sanity + +This command will take care of installing requirements and running them. + +The tools we use are: + + 1. ``flake8`` for making sure our python code is nice and tidy. + 2. ``ansible-lint`` to make sure our Ansible roles follow the best practices. + 3. ``ansible-test sanity`` for ensuring our modules and plugins will work on a + wide varienty of platforms. + 4. A custom script that makes sure roles metadata is in sync with Ansible + Galaxy. + + +Unit tests +---------- + +We use unit tests to test the logic and implementation of our helper functions +and various Ansible plugins that are part of the Sensu Go collection. + +Unit tests live in the ``tests/unit`` directory and mirror the structure of +the code. For example, unit tests for the code in the +``plugins/modules/asset.py`` are stored in the +``tests/unit/plugins/modules/test_asset.py`` file. When we add new code to the +collection, we must also include the tests for this piece of code in the same +git commit. + +We group our tests using classes. Each function or method gets its own +dedicated test class that contains one or more tests that check different +aspects of the code under scrutiny. + +We use ``ansible-test units`` to execute the unit tests. But we can also +execute:: + + (venv) $ make units + +to run them. This convenience ``make`` rule will all of our unit tests in a +dedicated docker container againts all Python versions that Ansible supports on +managed nodes. Additionally, it will also generate a code coverage report and +place it in the ``tests/output/reports/coverage/index.html`` HTML file. + +Our CI process runs unit tests on each push to GitHub. And while developers +usually run the tests only against one version of Ansible, CI tools run them +against all supported versions, making sure our code is ready to be used on a +variety of systems. + + +Integration tests +----------------- + +Integration tests are basically a collection of Ansible playbooks that we run +against all supported Sensu Go versions and operating systems. Integration +tests live in the ``tests/integration/molecule`` directory. We use Molecule_ +to manage our integration tests. + +.. _Molecule: https://molecule.readthedocs.io/en/stable/ + +We can add a new test scenario by creating a suitably named directory and +populating it with a ``playbook.yml`` playbook and ``molecule.yml`` +configuration file. + +If we are creating an integration test for a module, we can leave the molecule +configuration file empty. But we must still create the configuration file, or +Molecule will not detect this scenario. For example, if we would like to +create a new Molecule scenario that is testing the ``asset`` module, we would +run this sequence of commands:: + + (venv) $ mkdir tests/integration/molecule/module_asset + (venv) $ touch tests/integration/molecule/module_asset/{molecule,playbook}.yml + +Now we need to add some content to the playbook we just created. There are +plenty of examples in our test directory if you need some inspiration ;) + +Once we have our playbook ready, we can test our scenario by running:: + + (venv) $ make tests/integration/molecule/module_asset + +We can also run all integration tests with a single command: + + (venv) $ make integration + +Do note that this will take about an hour or so, so make sure you have +something else to do in the mean time ;) + +Adding a scenario for role is a bit more complicated since we need to inform +Molecule what docker images it should use for running tests, but nothing +drastic. + +We run our integration tests as part of our CI process, further reducing the +chances of broken code getting into our repository. + + +Continuous integration +---------------------- + +We use CircleCI to run our test suite on each push and pull request. Our CI +pipeline is composed of two stages: a fast one and a slow one. + +The fast stage is composed of sanity and unit tests. Once those tests are done +executing and passing, we start execution of the slow stage that runs +integration tests. + +In order to keep the test times as short as possible, the slow stage is +parallelized. And in order to maximize the benefits of this parallel +execution, we need to split the work into similarly sized chunks. This is done +automatically on CircleCI based on the timings from the previous runs. diff --git a/ansible_collections/sensu/sensu_go/docs/source/hacking/windows.rst b/ansible_collections/sensu/sensu_go/docs/source/hacking/windows.rst new file mode 100644 index 00000000..6139fa9e --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/source/hacking/windows.rst @@ -0,0 +1,80 @@ +Windows things +============== + +Working with Windows is a bit different from what most of us are probably used +to since there is no package manager to delegate installation to and services +behave a bit differently. Here is a collection of things that might come in +handy when working with installation and agent role. + + +Adding new Sensu Go version manually +------------------------------------ + +As already state, Windows has no built-in package manager backed by a +repository, which means we have to do a bit more work when installing +packages. And this is why our installation role contains a few pieces of +information about the available Sensu Go agent packages. + +We can add a new version of Sensu Go for Windows using the following steps: + + 1. Find the version and build we are interested in on packagecloud_. + 2. Download the x64 and x86 msi files from the download page. Use the + download URL from the `installation guide`_ as a base and adjust as + needed. + 3. Extract product codes from the msi packages (document author used orca msi + editor, would be very much interested in a PowerShell script). + 4. Add a new entry to the roles/install/vars/Windows.yml file. + +.. _packagecloud: https://packagecloud.io/sensu/stable +.. _installation guide: https://docs.sensu.io/sensu-go/latest/operations/deploy-sensu/install-sensu/#install-sensu-agents + + +Automating version sync +----------------------- + +Because manually extracting the product code from the msi archive is not +something we would like to devote our lives to, we created a script that knows +how to do two things: + + 1. It can check if the variable file is missing any upstream versions. + 2. It can automatically update the Windows variable file. + +To check for the missing versions in out current file, we can run the following +command:: + + $ tools/windows-versions.py check roles/install/vars/Windows.yml + The following versions are missing: 6.2.3.3986, 6.2.4.4013, 6.2.5.4040 + The following versions are obsolete: 5.20.0.12118, 5.20.1.12427 + +This command will check if our variable file contains any outdated versions and +if it is missing any new versions Sensu released since our last update. The +command also exits with a non-zero status if action is required, which is great +for CI/CD usage. + +We also added the common invocation to the Makefile, which makes running check +as simple as:: + + $ make check_windows_versions + +Once we know our variables and upstream availability diverged, we can run the +update command to get things back in sync:: + + $ tools/windows-versions.py update roles/install/vars/Windows.yml + +This command will download any missing packages, extract product codes from +them, and update the variable file. By default, temporary files are put ino the +``/tmp`` directory, but this is configurable via the ``--cache`` command-line +switch. + +As with the check command, we also added the common invocation to the Makefile, +which allows us to update the variable file with a simple make run:: + + $ make update_windows_versions + +.. note:: + + The update process needs ``msiinfo`` executable. The package should be + available for most mainstream Linux distributions. + +Once the update is over, we must commit the changes to the variable file and we +are done. diff --git a/ansible_collections/sensu/sensu_go/docs/source/index.rst b/ansible_collections/sensu/sensu_go/docs/source/index.rst new file mode 100644 index 00000000..4dc189a1 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/source/index.rst @@ -0,0 +1,53 @@ +Sensu Go Ansible Collection +=========================== + +Sensu Go Ansible Collection is a bundle of Ansible content that we can use to +manage all aspects of Sensu Go. It contains Ansible roles for installing and +configuring backends and agents. Collection also contains a wide selection of +modules for runtime management of Sensu Go backend. + +Collection is freely available on `Ansible Galaxy`_. For Red Hat subscribers, +this collection is also available on `Automation Hub`_. + +.. _Ansible Galaxy: + https://galaxy.ansible.com/sensu/sensu_go +.. _Automation Hub: + https://cloud.redhat.com/ansible/automation-hub/sensu/sensu_go + + +.. toctree:: + :maxdepth: 2 + :caption: Using Sensu Go Ansible Collection + + quickstart-sensu-go-6 + quickstart-sensu-go-5 + installation + + +.. toctree:: + :maxdepth: 2 + :caption: Guides + + versioning_sensu_go_installation + sensu_go_5_6_migration + + +.. toctree:: + :maxdepth: 1 + :caption: References + + roles + modules + +.. toctree:: + :maxdepth: 1 + :caption: Hacker's guides + + hacking + +.. toctree:: + :maxdepth: 1 + :caption: Appendices + + release_policy + release_notes diff --git a/ansible_collections/sensu/sensu_go/docs/source/installation.rst b/ansible_collections/sensu/sensu_go/docs/source/installation.rst new file mode 100644 index 00000000..08fbe6fd --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/source/installation.rst @@ -0,0 +1,94 @@ +Installation +============ + +We can install Sensu Go Ansible collection using the ``ansible-galaxy`` tool +that comes bundled with Ansible. This tool can install Ansible collections +from different sources. + + +Installing from Ansible Galaxy +------------------------------ + +`Ansible Galaxy`_ is the default source of Ansible collections for the +``ansible-galaxy`` tool. We can install Sensu Go Ansible collection by +running:: + + $ ansible-galaxy collection install sensu.sensu_go + +.. _Ansible Galaxy: https://galaxy.ansible.com + +After the command finishes, we will have the latest version of the Sensu Go +Ansible collection installed and ready to be used. + +We can also install a specific version of the collection by appending a +version after the name:: + + $ ansible-galaxy collection install sensu.sensu_go:1.0.0 + +.. note:: + + ``ansible-galaxy`` command will not overwrite the existing collection if it + is already installed. We can change this default behavior by adding a + ``--force`` command line switch:: + + $ ansible-galaxy collection install --force sensu.sensu_go:1.0.0 + +The official Ansible documentation contains more information about the +installation options in the `Using collections`_ document. + +.. _Using collections: + https://docs.ansible.com/ansible/latest/user_guide/collections_using.html#installing-collections + + +Installing from Automation Hub +------------------------------ + +If we have a valid Red Hat subscription, we can also install Sensu Go Ansible +collection from Red Hat Ansible Automation Hub. But before we can do that, we +need to tell Ansible about the second source of collections. We do this by +placing the following content into the +:download:`ansible.cfg <../examples/installation/ansible.cfg>` configuration +file: + +.. literalinclude:: ../examples/installation/ansible.cfg + :language: ini + + +Make sure you replace the ``token`` value in the above configuration with the +value obtained from the `token Automation Hub UI`_. + +.. _token Automation Hub UI: + https://cloud.redhat.com/ansible/automation-hub/token + +From here on, we can follow the steps from the previous section. + + +Installing from a local file +---------------------------- + +This last method of installation might come in handy in situations where our +Ansible control node cannot access Ansible Galaxy or Automation Hub. + +First, we need to download the Sensu Go Ansible collection archive from the +GitHub `releases page`_ and then transfer that archive to the Ansible control +node. Once we have that archive on our control node, we can install the Sensu +Go collection by running:: + + $ ansible-galaxy collection install path/to/sensu-sensu_go-1.0.0.tar.gz + +.. _releases page: + https://github.com/sensu/sensu-go-ansible/releases + + +Installing the Windows Ansible Collection +----------------------------------------- + +If we are using Ansible Base or Ansible Core, we need to install the +`ansible.windows` Ansible Collection manually:: + + $ ansible-galaxy collection install ansible.windows + +Why is this manual step needed? While it is technically possible to declare +collection dependencies, this may pose a problem for Automation Hub users. +The Windows Ansible Collection is not yet certified, so we had to make it an +optional dependency for the time being. diff --git a/ansible_collections/sensu/sensu_go/docs/source/modules.rst b/ansible_collections/sensu/sensu_go/docs/source/modules.rst new file mode 100644 index 00000000..d8f6af46 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/source/modules.rst @@ -0,0 +1,190 @@ +Modules +======= + +While different modules perform different tasks, their interfaces all follow +the same pattern as much as possible. For example, all Sensu Go modules +support check mode, most of them can have their state set to either +``present`` or ``absent``, and they identify the resource to operate on using +the *name* and *namespace* parameters. + +The API of each module is composed of two parts. The *auth* parameter contains +the pieces of information that are related to the Sensu Go backend that the +module is connecting to. All other parameters hold the information related to +the resource that we are operating on. + + +Authentication parameters +------------------------- + +Each module has an *auth* parameter that holds the following information about +the Sensu Go backend: + +1. The **url** key holds the address of the Sensu Go backend. If this key is + not present in the task's definition, Ansible will consult the *SENSU_URL* + environment variable and, if the variable is not set, use the default value + of ``http://localhost:8080``. +2. The **user** and **password** keys contain the credentials that the module + will use when connecting to the backend. It not present, Ansible will try + to look up the *SENSU_USER* and *SENSU_PASSWORD* environment variables, + falling back to the default values of ``admin`` and ``P@ssw0rd!``. +3. The **api_key** field should contain an `API key`_ that module will use to + authenticate with the backend. If not present, Ansible will try to use the + value stored in the *SENSU_API_KEY*. + +.. _API key: + https://docs.sensu.io/sensu-go/latest/guides/use-apikey-feature/ + +.. note:: + + The API key authentication is only available in Sensu Go 5.15 or newer. + +When Ansible tries to connect to the Sensu Go backend, it will try to +authenticate using the API key. If the *api_key* is not set, Ansible will +fallback to using the *user* and *password* values for authentication. What +this basically means is that there are two valid sets of *auth* parameters: + +.. code-block:: yaml + + - name: Use API key authentication + asset: + auth: + url: http://my.sensu.host:8765 + api_key: 7f63b5bc-41f4-4b3e-b59b-5431afd7e6a2 + # Other asset parameters here + + - name: Use user and password authentication + asset: + auth: + url: http://my.sensu.host:8765 + user: my-user + password: my-password + # Other asset parameters here + +It is not an error to specify all four parameters when writing an Ansible +task, but the *user* and *password* fields are ignored in this case: + +.. code-block:: yaml + + - name: Use API key authentication and ignore user and password values + asset: + auth: + url: http://my.sensu.host:8765 + user: my-user # IGNORED + password: my-password # IGNORED + api_key: 7f63b5bc-41f4-4b3e-b59b-5431afd7e6a2 + # Other asset parameters here + +.. note:: + + If the *api_key* parameter is set to an invalid value, Ansible will **NOT** + fallback to the second method of authentication. Instead, it will report + an error and abort the run. + + +Managing Sensu Go resources +--------------------------- + +There are three things we can do using the Sensu Go Ansible modules: + +1. Make sure that the specified resource is present on the backend. +2. Make sure that the named resource is not present on the backend. +3. List all currently available resources on the backend. + +.. note:: + + We left out the *auth* parameter from the following examples in order to + keep them short and readable. + +A typical task for creating (and by *creating* we mean *making sure it +exists*) a resource on the backend would look like this: + +.. code-block:: yaml + + - name: Make sure asset is present + asset: + namespace: my-namespace + name: my-asset-name + # Other asset parameters go here + +We need to specify the resource's name and decide into what namespace to place +it if the resource is not a cluster-wide resource. There are a few exceptions +to this rule, but not many. + +If we would like to remove a certain resource from the Sensu backend (and by +*remove* we mean *make sure it is not present*), we can write a task and set +its *state* parameter to ``absent``: + +.. code-block:: yaml + + - name: Make sure asset is absent + asset: + namespace: my-namespace + name: my-asset-name + state: absent + +Almost every module for manipulating resources has its counterpart module that +can be used to retrieve information about the corresponding resources, for +instance *asset* and *asset_info* modules. + +.. code-block:: yaml + + - name: Fetch a list of all assets + asset_info: + namespace: my-namespace + register: result + +Note the usage of the *asset_info* module in the example above. We can also +retrieve information about a single asset by adding a *name* parameter to the +previous task: + +.. code-block:: yaml + + - name: Fetch a specific asset + asset_info: + namespace: my-namespace + name: my-asset-name + register: result + +Info modules always return a list of objects that Sensu API returned. And if +we try to fetch a non-existing resource, the result will hold an empty list. +This makes it easy to write conditional tasks using next pattern: + +.. code-block:: yaml + + - name: Fetch a specific asset + asset_info: + namespace: my-namespace + name: my-asset-name + register: result + + - name: Do something if asset is there + debug: + msg: We are doing something + when: result.objects | length == 1 + +Of course, you can also loop over the result using a *loop* construct: + +.. code-block:: yaml + + - name: Fetch a list of all assets + asset_info: + namespace: my-namespace + register: result + + - name: Display number of builds in an asset + debug: + msg: "{{ item.metadata.name }}: {{ item.builds | length }}" + loop: result.objects + +Reference material for each module contains documentation on what parameters +certain modules accept and what values they expect those parameters to be. + + +Module reference +---------------- + +.. toctree:: + :glob: + :maxdepth: 1 + + modules/* diff --git a/ansible_collections/sensu/sensu_go/docs/source/quickstart-sensu-go-5.rst b/ansible_collections/sensu/sensu_go/docs/source/quickstart-sensu-go-5.rst new file mode 100644 index 00000000..81e21855 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/source/quickstart-sensu-go-5.rst @@ -0,0 +1,42 @@ +Quickstart for Sensu Go 5 +========================= + +Before we can do anything, we need to install Sensu Go Ansible Collection. +Luckily, we are just one short command away from that goal:: + + $ ansible-galaxy collection install sensu.sensu_go + +Now we can set up a simple Sensu Go sandbox using the following +:download:`playbook <../examples/quickstart/playbook-5.yaml>`: + +.. literalinclude:: ../examples/quickstart/playbook-5.yaml + :language: yaml + +When we run it, Ansible will install and configure backend and agents on +selected hosts, and then configure a ntp check that agents will execute twice +a minute. Note that we do not need to inform agents explicitly where the +backend is because the :doc:`agent role <roles/agent>` can obtain the +backend's address from the inventory. + +Now, before we can run this playbook, we need to prepare an inventory file. +The inventory should contain two groups of hosts: *backends* and *agents*. A +:download:`minimal inventory <../examples/quickstart/inventory.yaml>` with +only two hosts will look somewhat like this: + +.. literalinclude:: ../examples/quickstart/inventory.yaml + :language: yaml + +Replace the IP addresses with your own and make sure you can ssh into those +hosts. If you need help with building your inventory file, consult `official +documentation on inventory`_. + +.. _official documentation on inventory: + https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html + +All that we need to do now is to run the playbook:: + + $ ansible-playbook -i inventory.yaml playbook-5.yaml + +And in a few minutes, things should be ready to go. And if we now visit +http://192.168.50.4:3000 (replace that IP address with the address of your +backend), we can log in and start exploring. diff --git a/ansible_collections/sensu/sensu_go/docs/source/quickstart-sensu-go-6.rst b/ansible_collections/sensu/sensu_go/docs/source/quickstart-sensu-go-6.rst new file mode 100644 index 00000000..6a20da3b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/source/quickstart-sensu-go-6.rst @@ -0,0 +1,42 @@ +Quickstart for Sensu Go 6 +========================= + +Before we can do anything, we need to install Sensu Go Ansible Collection. +Luckily, we are just one short command away from that goal:: + + $ ansible-galaxy collection install sensu.sensu_go + +Now we can set up a simple Sensu Go sandbox using the following +:download:`playbook <../examples/quickstart/playbook-6.yaml>`: + +.. literalinclude:: ../examples/quickstart/playbook-6.yaml + :language: yaml + +When we run it, Ansible will install and configure backend and agents on +selected hosts, and then configure a ntp check that agents will execute twice +a minute. Note that we do not need to inform agents explicitly where the +backend is because the :doc:`agent role <roles/agent>` can obtain the +backend's address from the inventory. + +Now, before we can run this playbook, we need to prepare an inventory file. +The inventory should contain two groups of hosts: *backends* and *agents*. A +:download:`minimal inventory <../examples/quickstart/inventory.yaml>` with +only two hosts will look somewhat like this: + +.. literalinclude:: ../examples/quickstart/inventory.yaml + :language: yaml + +Replace the IP addresses with your own and make sure you can ssh into those +hosts. If you need help with building your inventory file, consult `official +documentation on inventory`_. + +.. _official documentation on inventory: + https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html + +All that we need to do now is to run the playbook:: + + $ ansible-playbook -i inventory.yaml playbook-6.yaml + +And in a few minutes, things should be ready to go. And if we now visit +http://192.168.50.4:3000 (replace that IP address with the address of your +backend), we can log in and start exploring. diff --git a/ansible_collections/sensu/sensu_go/docs/source/release_notes.rst b/ansible_collections/sensu/sensu_go/docs/source/release_notes.rst new file mode 100644 index 00000000..8accd204 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/source/release_notes.rst @@ -0,0 +1,412 @@ +Release notes +============= +Version 1.13.2 -- Support for latest Ansible +-------------------------------------------- +Added support for Ansible 2.14. Added support for Alma and Rocky linux + +**New features:** + +* Added support for ansible 2.14 +* Added support for Alma and Rocky linux + +Version 1.13.1 -- Support for latest Ansible +-------------------------------------------- + +Added support for Ansible 2.13. + +**New features:** + +* Added support for ansible 2.13 +* Removed support for CentOS 8 + +Version 1.13.0 -- Bonsai asset definitions can be downloaded on controller or remote nodes +------------------------------------------------------------------------------------------ + +A user can decide to download the Bonsai asset definitions on either the +controller node (previous functionality) or remote nodes (new functionality). +A use case mandating such a need is when the controller node's Internet +connection is unstable or worse than the connection of the remote nodes. + +**New features:** + +* Add argument remote_on inside bonsai_asset module + +Version 1.12.1 -- Sensu Go 6.6.0 +-------------------------------- + +Added support for Sensu Go 6.5.5 and 6.6.2 on Windows. + +**New features:** + +* Add Sensu Go 6.5.5 Windows metadata +* Add Sensu Go 6.6.2 Windows metadata + +Version 1.12.0 -- Keeping up with the updates +--------------------------------------------- + +In this release, community contributed support for the OracleLinux. We added a +few tests to catch if things break in the future and this is about it. And +while we were at work, we also added support for Sensu Go 6.4.0 and 6.4.1 on +Windows. + +**New features:** + +* Add support for OracleLinux. +* Add Sensu Go 6.4.0 Windows metadata. +* Add Sensu Go 6.4.1 Windows metadata. + + +Version 1.11.1 -- Sensu Go 6.3.0 is here +---------------------------------------- + +For this release, we only updated the list of available Sensu Go agent versions +for Windows, and made sure collection works with the latest Sensu Go version. + +**New features:** + +* Update list of available Sensu Go agent packages for Windows installations + (added 6.3.0). + + +Version 1.11.0 -- Validate all the things! +------------------------------------------ + +If you ever thought to yourself, "Ansible does not yell enough at me," we have +some great news. The Sensu Go Ansible Collection gained role argument +specifications, making it possible to validate variable values before executing +a role. You are welcome ;) + +**New features:** + +* Add argument specification to the install role. +* Add argument specification to the backend role. +* Add argument specification to the agent role. + + +Version 1.10.0 -- Authentication, authentication on the wall, Who has Access to Them All? +----------------------------------------------------------------------------------------- + +New modules allow Sensu Go users to configure authentication within +their Ansible playbooks. The users can authenticate via external +authentication providers such as Lightweight Directory Access Protocol (LDAP), +Active Directory (AD), or OpenID Connect 1.0 protocol (OIDC). + +**New features:** + +* Add modules for managing Sensu Go authentication providers. + + +Version 1.9.4 -- Opening Windows for real +----------------------------------------- + +This is a bugfix release that makes sure Sensu Go Ansible Collection can +operate even in the absence of the Windows Ansible Collection (assuming we do +not want to manage agents on Windows hosts, that is). + +**Bug fixes:** + +* Make sure we lazy-load Windows-related content. + + +Version 1.9.3 -- Opening Windows +-------------------------------- + +The only change in this release is removal of the ``ansible.windows`` +dependency. This should allow users that only use certified +collections to install and use the collection. + +**New features:** + +* Remove Windows Ansible Collection dependency. + + +Version 1.9.2 -- A fresh batch of updates +----------------------------------------- + +For this release, we only updated the list of available Sensu Go agent versions +for Windows. + +**New features:** + +* Update list of available Sensu Go agent packages for Windows installations + (added 5.21.4, 5.21.5, 6.2.5, and 6.2.6). + + +Version 1.9.1 -- Containerize all the things +-------------------------------------------- + +There are two main reasons for this release. We made sure the Sensu Go Ansible +Collection works with development version of Ansible (upcoming ansible-core +2.11). And we added enough metadata to the collection that ansible-builder can +create an execution environment with the Sensu Go Ansible Collection without +having to manually specify dependencies. + +**Bug fixes:** + +* Add ansible.windows dependency that we forgot to add when we introducted the + Sensu Go agent installation on Windows. + + +Version 1.9.0 -- Multi-cluster visibility with federation +--------------------------------------------------------- + +Two new module pairs allow Sensu Go users to configure federation from the +comfort of their Ansible playbooks. + +**New features:** + +* Add modules for managing etcd replicatiors, which form the basis of the Sensu + Go federation. +* Add modules for managing Sensu Go clusters. +* Update list of available Sensu Go agent packages for Windows installations. + +**Bug fixes:** + +* Allow downgrading Sensu Go packages on Linux distributions that use yum or + dnf for package management. + + +Version 1.8.0 -- Supporting hashed user passwords +------------------------------------------------- + +Starting with this release, Sensu Go users can use password hashes directly +when manipulating role-based access control resources. + +**New features:** + +* Add support for hashed password in user module. + +**Bug fixes:** + +* Mimic actual responses when user module runs in check mode. +* Make it possible to use modules on Sensu Go backends with no version number. + + +Version 1.7.2 -- Be kind +------------------------ + +The main thing in this release is a small adjustment of our code of conduct +that is a bit more generic and less event-focused. + +**Bug fixes:** + +* List version 6.2.1 and 6.2.2 in Windows lookup table. +* Specify minimal python version for modules. +* Update code of conduct. + + +Version 1.7.1 -- Slow and steady +-------------------------------- + +There are no major new features in this release, just honest little fixes that +should make using Sensu Go Ansible Collection a bit more pleasant. + +**Bug fixes:** + +* List version 6.2.0 and 6.1.3 in Windows lookup table. +* Add module return value samples. + + +Version 1.7.0 -- Say hello to Amazon Linux and Windows +------------------------------------------------------ + +As the title suggests, we worked hard to bring you two new supported platforms +to the Sensu Go Ansible Colletions. And yes, all your existing playbooks still +work.All you need to do is run them against the right host and voila ;) + +**New features:** + +* Add support for installing Sensu Go on Amazon Linux. +* Add support for installing Sensu Go agents on Windows. + + + +Version 1.6.1 -- Comparing entities is hard +------------------------------------------- + +This is a bugfix release that makes sure agent entity changes are properly +detected. + +**Bug fixes:** + +* Make subscriptions comparison insensitive to ordering. +* Make sure agent entities handle *entity:{name}* automatic subscriptions. + + +Version 1.6.0 -- Our little secret +---------------------------------- + +This release contains a few new modules that allow you to manage all things +related to the Sensu Go secrets: from adding secrets providers to passing +secrets to resources that know how to use them. + +**New features:** + +* Add modules for managing Sensu Go secret providers. +* Add modules for managing Sensu Go secrets. +* Add support for secrets to pipe handler module. +* Add support for secrets to check module. +* Add support for secrets to mutator module. + + +Version 1.5.0 -- Self-signed security +------------------------------------- + +The primary focus of this release is to enable configuration of Sensu Go +backends that use certificates that are not considered trusted when using +system-provided CA bundle. + +**New features:** + +* Allow modules to supply custom CA bundle for backend certificate validation + or skip the validation entirely. + +**Bug fixes:** + +* Expand documentation about the *check_hooks* parameter in the check module. +* Explain how the resource name parameter is used and what invariants need to + hold in order for the Sensu Go to consider it a valid name. + +Version 1.4.2 -- Break the fall +------------------------------- + +There is really only one reason for this release: making sure user management +works with Sensu Go 5.21.0 and newer. And while the upstream did break the +API, we did not, so all your playbooks should function as nothing happened. We +had to add a *bcrypt* dependency to our collection so make sure it is +installed on hosts that will execute the user module. + +**Bug fixes:** + +* Make sure check module is as idempotent as possible. +* Make user module compatible with Sensu Go >= 5.21.0. + + +Version 1.4.1 -- Maintenance is the name of the game +---------------------------------------------------- + +There are no nothing earth-shattering changes in this release, just honest +little bug fixes and compatibility improvements. + +**NOTE:** The *sensu.sensu_go.user* module currently **DOES NOT** work on +Sensu Go 5.21.0 and later. This is a know issue that will be fixed as soon as +the updated user-related backend API endpoints are documented. + + +**Bug fixes:** + +* Make sure event module always returns a predicted result. +* Make user module fully-idempotent. Previous versions did not properly detect + the password changes. +* Use fully-qualified collection names in module documentation. +* Ensure backend initialization properly reports changed state. +* Make API key authentication work even for regular users with limited + permissions. +* Update the datastore module to cope with the minor API changes. + + +Version 1.4.0 -- Keeping up with the world +------------------------------------------ + +Main changes in this release are related to updates in the Sensu Go's web API +that broke our change detection. + +**New features:** + +* Add support for RHEL and CentOS 8. + +**Bug fixes:** + +* Fix resource metadata comparison on Sensu Go 5.19.0 and newer. +* Update entity comparator to handle new fields. + + +Version 1.3.1 -- Bug fixing galore +---------------------------------- + +This release makes it possible to use the *asset* module when replacing the +deprecated, single-build assets that were created by means other than Ansible. + +**Bug fixes:** + +* Do not die when encountering a deprecated asset format. +* Update return value documentation for info modules. +* Add Sensu Go 5.17.x and 5.18.x to the test suite and remove the unsupported + versions (5.14.2 and lower). +* Update the role metadata with proper platform markers. +* Remove unsupported Ubuntu versions from the test suite. + + +Version 1.3.0 -- Authenticating with style on Debian +---------------------------------------------------- + +Sensu Go 5.15.0 gained an API key authentication method and the Ansible +collection finally caught up. This means that we can now replace *user* and +*password* authentication parameters with a single *api_key* value. + +And the other big news is the addition of Debian support to the `install` +role. + +**New features:** + +* Add API key authentication support. +* Add support for Debian installation. + + +Version 1.2.0 -- Building support for builds +-------------------------------------------- + +This release adds support for specifying builds when installing various Sensu +Go components. + +**New features:** + +* Add *build* variable to the *install* role that further pins down the + package version that gets installed. + + +Version 1.1.1 -- Python 2 is Still a Thing +------------------------------------------ + +This is a bugfix release that makes sure the Sensu collection is working when +Ansible control node uses Python 2. + +**New features:** + +* Add support for RHEL 7 to the install role (thanks, @danragnar). + +**Bug fixes:** + +* Accept *str* and *unicode* instance as a valid string in *bonsai_asset* + action plugin. + + +Version 1.1 -- Hello Sensu Go 5.16 +---------------------------------- + +This is the first release that supports installing Sensu Go 5.16. + +**New features:** + +* Support for Sensu Go 5.16 initialization in backend role. +* Support for external datastore management using *datastore* and + *datastore_info* modules. + +**Bug fixes:** + +* Reintroduce namespace support to *bonsai_asset* module (thanks, @jakeo) + + +Version 1.0 -- Rising From The Ashes +------------------------------------ + +This is the initial stable release of the Sensu Go Ansible Collection. It +contains roles for installing and configuring Sensu Go backends and agents and +a set of modules for managing Sensu Go resources. + +Where does the release name comes from? We took an existing Ansible Collection +that `@flowerysong`_ wrote, gave it a thorough tune-up and added a +comprehensive test suite. And now, it is ready to face the world! + +.. _@flowerysong: https://github.com/flowerysong/ansible-sensu-go + diff --git a/ansible_collections/sensu/sensu_go/docs/source/release_policy.rst b/ansible_collections/sensu/sensu_go/docs/source/release_policy.rst new file mode 100644 index 00000000..decde868 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/source/release_policy.rst @@ -0,0 +1,50 @@ +Release policy +============== + +Our release policy could be summarized as: + + 1. We only support latest stable release. + 2. We create new bugfix and feature releases as things progress and not on a + fixed schedule. + 3. We support deprecated content for at least a year and a half since its + deprecation. + 4. We have no plans on releasing version 2.0.0 anytime soon and are fully + commited on making current version of collection work on all supported + Sensu Go versions. + + +Supported releases +------------------ + +As already stated, we only support latest stable version of Sensu Go Ansible +Collection. Currently, this means that only latest 1.x.y version is supported. + +Once we next major version of collection, we will continue to backport +security fixes to previous major version 1 for at least half a year. No new +features will be backported. + + +Release schedule +---------------- + +To get new features and bugfixes to our users as soon as possible, we have no +fixed release schedule. Instead, we create a new release after each +significant change (bugfix or added feature). We may delay release for a day +or two if we have a few thing lined up to reduce the administrative load. + + +Compatibility +------------- + +Sensu Go Ansible Collection follows `semantic versioning`_. In short, we +guarantee backward compatibility between releases with the same major version +number. In practice, this means that if we have a playbook that works with +some version of Sensu Go Ansible Collection, it will continue to work with +newer versions up until major version changes. + +.. _semantic versioning: https://semver.org/ + +We do not guarantee forward compatibility (we are adding new modules in +releases that increment minor version number). Downgrading between patch +versions should be safe, but we would advice against it because patch releases +can potentially contain important security fixes. diff --git a/ansible_collections/sensu/sensu_go/docs/source/roles.rst b/ansible_collections/sensu/sensu_go/docs/source/roles.rst new file mode 100644 index 00000000..12293cba --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/source/roles.rst @@ -0,0 +1,11 @@ +Roles +===== + +Sensu Go Ansible Collection contains three roles that allow us install and +configure Sensu Go backend and agents. + +.. toctree:: + :glob: + :maxdepth: 1 + + roles/* diff --git a/ansible_collections/sensu/sensu_go/docs/source/roles/agent.rst b/ansible_collections/sensu/sensu_go/docs/source/roles/agent.rst new file mode 100644 index 00000000..870d59bf --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/source/roles/agent.rst @@ -0,0 +1,53 @@ +Sensu Go agent role +=================== + +This role installs, configures and starts the ``sensu-agent`` service. + + +Example playbook +---------------- + +The most basic +:download:`agent playbook <../../examples/roles/agent.yaml>` looks like this: + +.. literalinclude:: ../../examples/roles/agent.yaml + :language: yaml + +This playbook will install the latest stable version of the Sensu Go agent +and configure it. We can customize the agent's configuration by adding more +options to the *agent_config* variable. + + +Agent configuration options +--------------------------- + +The *agent_config* variable can contain any option that is valid for the Sensu +Go agent version we are installing. All valid options are listed in the +`official Sensu documentation`_. + +.. _official Sensu documentation: + https://docs.sensu.io/sensu-go/latest/reference/agent/#configuration + +.. note:: + + Role copies the key-value pairs from the *agent_config* variable verbatim + to the configuration file. This means that we must copy the key names + **exactly** as they appear in the configuration reference. In a way, the + *agent_config* variable should contain a properly indented copy of the + ``/etc/sensu/agent.yml`` file. + + +Tested Platforms (CI/CD) +------------------------ + ++-------+--------------+-----------------------------------+ +| OS | distribution | versions | ++=======+==============+===================================+ +| Linux | CentOS | 7 | +| +--------------+-----------------------------------+ +| | RedHat | 7, 8 | +| +--------------+-----------------------------------+ +| | Debian | 9, 10 | +| +--------------+-----------------------------------+ +| | Ubuntu | 14.04, 16.04, 18.04 | ++-------+--------------+-----------------------------------+ diff --git a/ansible_collections/sensu/sensu_go/docs/source/roles/backend.rst b/ansible_collections/sensu/sensu_go/docs/source/roles/backend.rst new file mode 100644 index 00000000..d89c573c --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/source/roles/backend.rst @@ -0,0 +1,192 @@ +Sensu Go backend role +===================== + +This role installs, configures and starts the ``sensu-backend`` service. + + +Example playbook +---------------- + +The most basic +:download:`backend playbook <../../examples/roles/backend.yaml>` looks like +this: + +.. literalinclude:: ../../examples/roles/backend.yaml + :language: yaml + +This playbook will install the latest stable version of the Sensu Go backend +and configure it. We can customize the backend's configuration by adding more +options to the *backend_config* variable. + + +Backend configuration options +----------------------------- + +The *backend_config* variable can contain any option that is valid for the +Sensu Go backend version we are installing. All valid options are listed in +the `official Sensu documentation`_. + +.. _official Sensu documentation: + https://docs.sensu.io/sensu-go/latest/reference/backend/#configuration + +.. note:: + + Role copies the key-value pairs from the *backend_config* variable verbatim + to the configuration file. This means that we must copy the key names + **exactly** as they appear in the configuration reference. In a way, the + *backend_config* variable should contain a properly indented copy of the + ``/etc/sensu/backend.yml`` file. + +Users of Sensu Go >= 5.16 have two additional variables at their disposal that +control the first-time backend initialization: + +.. list-table:: + :header-rows: 1 + :widths: 25 30 45 + + * - Variable + - Default + - Description + + * - cluster_admin_username + - admin + - Initial admin user to create when initializing backend for the first + time. + + * - cluster_admin_password + - P@ssw0rd! + - Initial admin password to create when initializing backend for the + first time. + +On Sensu Go version below 5.16, these two variables have no effect since +default admin credentials are baked into the Sensu Go backend. + + +Securing Sensu Go backend +------------------------- + +This role enables users to establish secure end-to-end communications of the +components that comprise the Sensu Go backend. The user needs to supply the +paths to the PKI files by placing the appropriate public and private key files +somewhere within the Ansible playbook search path. They then need to reference +these paths in the appropriate inventory variables, as described below. + +.. note:: + + All of the files referenced in each of the following subsections need to be + supplied. If even a single file is missing or not defined, the play will + fail. If none of the variables within a subsection is defined, those + services will be configured without the secure communication. + +Etcd peer communication +^^^^^^^^^^^^^^^^^^^^^^^ + +To secure the etcd communication, create the appropriate files for the PKI +and define **all** of the following variables: + +.. list-table:: + :header-rows: 1 + :widths: 25 30 45 + + * - Variable + - Examples + - Description + + * - etcd_cert_file + - files/pki/etcd-client.crt + - Path to the certificate used for SSL/TLS connections **to** etcd. This + is a client certificate. + + * - etcd_key_file + - files/pki/etcd-client.key + - Path to the private key for the etcd client certificate file. Must be + unencrypted. + + * - etcd_trusted_ca_file + - files/pki/client-ca.crt + - Path to the trusted certificate authority for the etcd client + certificates. + + * - etcd_peer_cert_file + - files/pki/etcd-peer.crt + - Path to the certificate used for SSL/TLS connections between peers. + This will be used both for listening on the peer address as well as + sending requests to other peers. + + * - etcd_peer_key_file + - files/pki/etcd-peer.key + - Path to the peer certificate's key. Must be unencrypted. + + * - etcd_peer_trusted_ca_file + - files/pki/etcd-peer-ca.crt + - Path to the trusted certificate authority for the peer certificates. + +Backend API +^^^^^^^^^^^ + +To secure the Sensu Go backend API communication, create the appropriate files +for the PKI and define **all** of the following variables: + +.. list-table:: + :header-rows: 1 + :widths: 25 30 45 + + * - Variable + - Examples + - Description + + * - api_cert_file + - files/pki/sensu-api.crt + - Path to the certificate used to secure the Sensu Go API. + + * - api_key_file + - files/pki/sensu-api.key + - Path to the private key corresponding to the Sensu Go API certificate. + Must be unencrypted. + + * - api_trusted_ca_file + - files/pki/sensu-api-ca.crt + - Path to the trusted certificate authority for the Sensu Go API + certificates. + +Dashboard +^^^^^^^^^ + +To secure the Sensu dashboard communication, create the appropriate files for the +PKI and define **all** of the following variables: + +.. list-table:: + :header-rows: 1 + :widths: 25 30 45 + + * - Variable + - Examples + - Description + + * - dashboard_cert_file + - files/pki/sensu-dashboard.crt + - Path to the certificate used for SSL/TLS connections to the dashboard. + + * - dashboard_key_file + - files/pki/sensu-dashboard.key + - Path to the private key corresponding to the dashboard certificate. + Must be unencrypted. + +The role will automatically configure the dashboard endpoint to use HTTPS, +e.g.: `https://localhost:3000`. + + +Tested Platforms (CI/CD) +------------------------ + ++-------+--------------+-----------------------------------+ +| OS | distribution | versions | ++=======+==============+===================================+ +| Linux | CentOS | 7 | +| +--------------+-----------------------------------+ +| | RedHat | 7, 8 | +| +--------------+-----------------------------------+ +| | Debian | 9, 10 | +| +--------------+-----------------------------------+ +| | Ubuntu | 14.04, 16.04, 18.04 | ++-------+--------------+-----------------------------------+ diff --git a/ansible_collections/sensu/sensu_go/docs/source/roles/install.rst b/ansible_collections/sensu/sensu_go/docs/source/roles/install.rst new file mode 100644 index 00000000..a06f5188 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/source/roles/install.rst @@ -0,0 +1,87 @@ +Sensu Go install role +===================== + +This role installs selected Sensu Go components from the official precompiled +packages. + +.. note:: + + This role only configures your package manager (like yum or apt) and + installs the binaries. It does **not** configure anything and it does + **not** run any services. + + +Example playbook +---------------- + +The next :download:`playbook <../../examples/roles/install.yaml>` demonstrates +how to install different versions of Sensu components from specific channels. + +.. literalinclude:: ../../examples/roles/install.yaml + :language: yaml + + +Role Variables +-------------- + +This role consults the following variables to determine what packages to +install: + +.. list-table:: + :header-rows: 1 + :widths: 25 30 45 + + * - Variable + - Default value + - Description + + * - components + - - sensu-go-backend + - sensu-go-agent + - sensu-go-cli + - List of components to install. Valid values are ``sensu-go-backend``, + ``sensu-go-agent`` and ``sensu-go-cli``. + + * - channel + - stable + - Repository channel that serves as a source of packages. We can see all the + available channels on packagecloud_ site. + + * - version + - latest + - Package version to install. Can be any valid version string such as + ``5.14.2`` or special value ``latest``. + + * - build + - latest + - Package build to install. Can be any valid build string such as + ``8290`` or a special value ``latest``. If the *version* variable is + set to ``latest``, this variable is ignored and the latest available + build is installed. + + * - packagecloud_auth + - "" + - Credentials to use when setting up Sensu Go repos. For publicly + available repos, this can be left empty. For private repos, this should + be set to the value of the read token, folloved by the ``:@``. Example: + ``ofb9123457acdddef6734524:@``. + +.. _packagecloud: https://packagecloud.io/sensu + + +Tested Platforms (CI/CD) +------------------------ + ++-------+--------------+-----------------------------------+ +| OS | distribution | versions | ++=======+==============+===================================+ +| Linux | Amazon | 1, 2 | +| +--------------+-----------------------------------+ +| | CentOS | 7 | +| +--------------+-----------------------------------+ +| | RedHat | 7, 8 | +| +--------------+-----------------------------------+ +| | Debian | 9, 10 | +| +--------------+-----------------------------------+ +| | Ubuntu | 14.04, 16.04, 18.04 | ++-------+--------------+-----------------------------------+ diff --git a/ansible_collections/sensu/sensu_go/docs/source/sensu_go_5_6_migration.rst b/ansible_collections/sensu/sensu_go/docs/source/sensu_go_5_6_migration.rst new file mode 100644 index 00000000..482b422d --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/source/sensu_go_5_6_migration.rst @@ -0,0 +1,74 @@ +Updating playbooks for Sensu Go 6 +================================= + +One of the more significant changes in Sensu Go 6 is how we manage agents and +agent entities. In previous Sensu Go versions, configuration for both agents and +agent entities resided in the agent's configuration file. A typical Ansible +playbook for managing Sensu Go 5 agents looked like this: + +.. code-block:: yaml + + - name: Install, configure and run Sensu agents + hosts: agents + become: true + tasks: + - name: Install agent + include_role: + name: sensu.sensu_go.agent + vars: + version: 5.21.2 + agent_config: + name: my-agent + backend-url: + - ws://backend.host:8081 + keepalive-interval: 5 + keepalive-timeout: 10 + deregister: true + subscriptions: + - linux + +In Sensu Go 6, we manage the entity representing the agent via the API, just +like all other Sensu Go resources. Unfortunately, this means that we must move +specific configuration options from the ``agent_config`` variable into a +separate task in our play for managing monitoring configuration: + +.. code-block:: yaml + + - name: Install, configure and run Sensu agents + hosts: agents + become: true + tasks: + - name: Install agent + include_role: + name: sensu.sensu_go.agent + vars: + version: 6.0.0 + agent_config: + name: my-agent + backend-url: + - ws://backend.host:8081 + keepalive-interval: 5 + keepalive-timeout: 10 + + - name: Configure monitoring resources + hosts: localhost + tasks: + - name: Add subscriptions to agent entity + sensu.sensu_go.entity: + name: my-agent + entity_class: agent + deregister: true + subscriptions: + - linux + +Options that we should move are **annotations**, **deregister**, +**deregistration-handler**, **labels**, **redact**, and **subscriptions**. We +can copy over most of the options from one play into another verbatim. The only +exception is the **deregistration-handler** agent configuration option that +corresponds to the **deregistration_handler** entity module parameter. + +.. note:: + + If we leave entity-related configuration options in the agent's configuration + file, Sensu Go will use them when creating an entity. Sensu Go will ignore + any subsequent updates of the configuration file. diff --git a/ansible_collections/sensu/sensu_go/docs/source/versioning_sensu_go_installation.rst b/ansible_collections/sensu/sensu_go/docs/source/versioning_sensu_go_installation.rst new file mode 100644 index 00000000..009b947e --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/source/versioning_sensu_go_installation.rst @@ -0,0 +1,97 @@ +Versioning Sensu Go installation +================================ + +When dealing with software installation, we want to pin the versions of our +components as tightly as possible. In the first part of this guide, we will +look at how we can pin the Sensu Go version when using Ansible. We will focus +our attention on updates in the second part of this guide. + + +Initial install +--------------- + +The :doc:`backend </roles/backend>` and :doc:`agent </roles/agent>` roles both +use the :doc:`install </roles/install>` role for installing required +components. And the installation Ansible role consults three variables when +determining what component version to install: **version**, **build**, and +**channel**. + +In the vast majority of scenarios, we can set the **version** variable and +leave the build and channel variables set to their default values: + +.. code-block:: yaml + + - name: Install, configure and run Sensu agents + hosts: agents + become: true + tasks: + - name: Install agent + include_role: + name: sensu.sensu_go.agent + vars: + version: 6.1.4 + +When we run the previous playbook, Ansible will install the latest 6.1.4 +version build from the `stable channel`_. And since stable channel only +contains one package build per released version, we pinned down the Sensu Go +version to a single package. + +Unstable versions of Sensu Go can have more than one build associated with +them. In scenarios where we are dealing with prerelease versions, we can use +the **build** variable to make installation predictable or use an older +package build: + + +.. code-block:: yaml + + - name: Install, configure and run Sensu agents + hosts: agents + become: true + tasks: + - name: Install agent + include_role: + name: sensu.sensu_go.agent + vars: + channel: testing + version: 6.2.0 + build: 3881 + +.. note:: + + The installation role ignores both the **channel** and the **build** + variables values when installing the Sensu Go agent on Windows. For the + time being, Windows users can only install stable versions with the Sensu + Go agent role. + +We can also install the latest available version if we omit the version +variable or set it to ``latest``. But we strongly advise against this approach +since we may inadvertently update our Sensu Go version when all we wanted to +do is update our configuration file. + + +Updating existing installation +------------------------------ + +If you think updating our Sensu Go installation is as simple as bumping the +version number and rerunning the playbook, you are almost right. Ansible will +update and restart the updated Sensu Go backend or agent services, but it will +not run any version-specific migration tasks. + +Thankfully, for most updates, restarting services is all that is needed. For +the rest of the cases, update instructions are usually straightforward. You +can find them all in the `Sensu Go documentation`_. + + +Downgrading versions +-------------------- + +The Sensu Go Ansible Collection does not support downgrades. You might be able +to install an older version of Sensu Go, but there are no guarantees that your +installation will still work. + + +.. _stable channel: + https://packagecloud.io/sensu/stable + +.. _Sensu Go documentation: + https://docs.sensu.io/sensu-go/latest/operations/maintain-sensu/upgrade/ diff --git a/ansible_collections/sensu/sensu_go/docs/templates/module.rst.j2 b/ansible_collections/sensu/sensu_go/docs/templates/module.rst.j2 new file mode 100644 index 00000000..2a400c24 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/docs/templates/module.rst.j2 @@ -0,0 +1,108 @@ +:github_url: https://github.com/sensu/sensu-go-ansible/blob/master/plugins/modules/{{ module }}.py + +.. _sensu.sensu_go.{{ module }}_module: + +{% set title = module + ' -- ' + short_description | rst_ify %} +{{ title }} +{{ '=' * title | length }} + +{% for desc in description %} +{{ desc | rst_ify }} + +{% endfor %} + +{% if version_added is defined -%} +.. versionadded:: {{ version_added }} +{% endif %} + +{% if requirements -%} +Requirements +------------ + +The below requirements are needed on the host that executes this module: + +{% for req in requirements %} +- {{ req | rst_ify }} +{% endfor %} +{% endif %} + + +Examples +-------- + +.. code-block:: yaml+jinja + +{{ examples | indent(3, True) }} + + +{% if notes -%} +Notes +----- + +.. note:: +{% for note in notes %} + {{ note | rst_ify }} + +{% endfor %} +{% endif %} + + +{% if seealso -%} +See Also +-------- + +.. seealso:: + +{% for item in seealso %} + - :ref:`{{ item.module }}_module` +{% endfor %} +{% endif %} + + +{% macro option_desc(opts, level) %} +{% for name, spec in opts | dictsort recursive %} +{% set req = "required" if spec.required else "optional" %} +{% set default = ", default: " ~ spec.default if spec.default else "" %} +{{ " " * level }}{{ name }} ({{ req }}) +{% for para in spec.description %} + {{ " " * level }}{{ para | rst_ify }} + +{% endfor %} + {{ " " * level }}| **type**: {{ spec.type | default("str") }} +{% if spec.default %} + {{ " " * level }}| **default**: {{ spec.default }} +{% endif %} +{% if spec.choices %} + {{ " " * level }}| **choices**: {{ ", ".join(spec.choices) }} +{% endif %} + +{% if spec.suboptions %} +{{ option_desc(spec.suboptions, level + 1) }} +{% endif %} +{% endfor %} +{% endmacro %} + +{% if options -%} +Parameters +---------- + +{{ option_desc(options, 0) }} +{% endif %} + +{% if returndocs -%} +Return Values +------------- + +{% for name, spec in returndocs.items() %} +{{ name }} +{% for para in spec.description %} + {{ para | rst_ify }} + +{% endfor %} + **sample**: + + .. code-block:: yaml + + {{ spec.sample | to_yaml(default_flow_style=False, indent=2) | indent(6) }} +{% endfor %} +{% endif %} diff --git a/ansible_collections/sensu/sensu_go/integration.requirements b/ansible_collections/sensu/sensu_go/integration.requirements new file mode 100644 index 00000000..5d073458 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/integration.requirements @@ -0,0 +1,6 @@ +molecule ~= 3.2.3 +# Temporary fix until molecule-docker can only support Ansible >= 2.9 +git+https://github.com/xlab-steampunk/molecule-docker.git@temp-fixes#egg=molecule-docker +pytest +# Temporary fix until pytest-molecule is updated to work with ansible-core +git+https://github.com/xlab-steampunk/pytest-molecule.git@temp-fixes#egg=pytest-molecule diff --git a/ansible_collections/sensu/sensu_go/meta/execution-environment.yml b/ansible_collections/sensu/sensu_go/meta/execution-environment.yml new file mode 100644 index 00000000..0c2bd227 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/meta/execution-environment.yml @@ -0,0 +1,4 @@ +--- +version: 1 +dependencies: + python: collection.requirements diff --git a/ansible_collections/sensu/sensu_go/meta/runtime.yml b/ansible_collections/sensu/sensu_go/meta/runtime.yml new file mode 100644 index 00000000..25ff974c --- /dev/null +++ b/ansible_collections/sensu/sensu_go/meta/runtime.yml @@ -0,0 +1,2 @@ +--- +requires_ansible: ">=2.9.0" diff --git a/ansible_collections/sensu/sensu_go/plugins/action/bonsai_asset.py b/ansible_collections/sensu/sensu_go/plugins/action/bonsai_asset.py new file mode 100644 index 00000000..74473aeb --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/action/bonsai_asset.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +from ansible.module_utils.six import text_type +from ansible.plugins.action import ActionBase +from ansible.utils.vars import merge_hash + +from ..module_utils import bonsai, errors + + +def validate(name, args, required, typ): + """ + Make sure that required values are not None and that if the value is + present, it is of the correct type. + """ + value = args.get(name) + if required and value is None: + raise errors.Error("{0} is required argument".format(name)) + if value is not None and not isinstance(value, typ): + raise errors.Error("{0} should be {1}".format(name, typ)) + + +class ActionModule(ActionBase): + + _VALID_ARGS = frozenset(( + "auth", "name", "version", "namespace", "rename", "labels", + "annotations", "on_remote", + )) + + def run(self, _tmp=None, task_vars=None): + self._supports_check_mode = True + self._supports_async = True + + result = super(ActionModule, self).run(task_vars=task_vars) + + wrap_async = ( + self._task.async_val and not self._connection.has_native_async + ) + + try: + self.validate_arguments(self._task.args) + asset = self.download_asset_definition( + self._task.args.get("on_remote", False), + self._task.args["name"], + self._task.args["version"], + task_vars, + ) + asset_args = self.build_asset_args(self._task.args, asset) + return merge_hash( + result, + self._execute_module( + module_name="sensu.sensu_go.asset", module_args=asset_args, + task_vars=task_vars, wrap_async=wrap_async, + ), + ) + except errors.Error as e: + return dict(result, failed=True, msg=str(e)) + finally: + if not wrap_async: + self._remove_tmp_path(self._connection._shell.tmpdir) + + @staticmethod + def validate_arguments(args): + # We only validate arguments that we use. We let the asset module + # validate the rest (like auth data). + + # Next three string validations might seem strange at first, but there + # is a reason for this strangenes. On python 2, we should consider + # string to be instance of str or unicode. On python 3, strings are + # always instances of str. In order to avoid having a separate + # validate calls for python 2 and python 3, we always pass a pair of + # types that just happen to be the same on python 3. + validate("name", args, required=True, typ=(str, text_type)) + validate("version", args, required=True, typ=(str, text_type)) + validate("rename", args, required=False, typ=(str, text_type)) + validate("labels", args, required=False, typ=dict) + validate("annotations", args, required=False, typ=dict) + validate("on_remote", args, required=False, typ=bool) + + def download_asset_definition(self, on_remote, name, version, task_vars): + if not on_remote: + return bonsai.get_asset_parameters(name, version) + + args = dict(name=name, version=version) + result = self._execute_module( + module_name="sensu.sensu_go.bonsai_asset", module_args=args, + task_vars=task_vars, wrap_async=False, + ) + if result.get("failed", False): + raise errors.Error(result["msg"]) + + return result["asset"] + + @staticmethod + def build_asset_args(args, bonsai_args): + asset_args = dict( + name=args.get("rename", args["name"]), + state="present", + builds=bonsai_args["builds"], + ) + + if "auth" in args: + asset_args["auth"] = args["auth"] + + if "namespace" in args: + asset_args["namespace"] = args["namespace"] + + # Only add optional parameter if it is present in at least one source. + for meta in ("labels", "annotations"): + if bonsai_args[meta] or args.get(meta): + asset_args[meta] = merge_hash( + bonsai_args[meta] or {}, args.get(meta, {}), + ) + + return asset_args diff --git a/ansible_collections/sensu/sensu_go/plugins/doc_fragments/annotations.py b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/annotations.py new file mode 100644 index 00000000..3e93c948 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/annotations.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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): + DOCUMENTATION = """ +options: + annotations: + description: + - Custom metadata fields with fewer restrictions, as key/value pairs. + - These are preserved by Sensu but not accessible as tokens or + identifiers, and are mainly intended for use with external tools. + type: dict + default: {} +""" diff --git a/ansible_collections/sensu/sensu_go/plugins/doc_fragments/auth.py b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/auth.py new file mode 100644 index 00000000..4fe8aee0 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/auth.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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): + DOCUMENTATION = """ +options: + auth: + description: + - Authentication parameters. Can define each of them with ENV as well. + type: dict + suboptions: + user: + description: + - The username to use for connecting to the Sensu API. + If this is not set the value of the SENSU_USER environment + variable will be checked. + - This parameter is ignored if the I(auth.api_key) parameter is set. + type: str + default: admin + password: + description: + - The Sensu user's password. + If this is not set the value of the SENSU_PASSWORD environment + variable will be checked. + - This parameter is ignored if the I(auth.api_key) parameter is set. + type: str + default: P@ssw0rd! + url: + description: + - Location of the Sensu backend API. + If this is not set the value of the SENSU_URL environment variable + will be checked. + type: str + default: http://localhost:8080 + api_key: + description: + - The API key that should be used when authenticating. If this is + not set, the value of the SENSU_API_KEY environment variable will + be checked. + - This replaces I(auth.user) and I(auth.password) parameters. + - For more information about the API key, refer to the official + Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/guides/use-apikey-feature/). + type: str + version_added: 1.3.0 + verify: + description: + - Flag that controls the certificate validation. + - If you are using self-signed certificates, you can set this + parameter to C(false). + - ONLY USE THIS PARAMETER IN DEVELOPMENT SCENARIOS! In you use + self-signed certificates in production, see the I(auth.ca_path) + parameter. + - It is also possible to set this parameter via the I(SENSU_VERIFY) + environment variable. + type: bool + default: true + version_added: 1.5.0 + ca_path: + description: + - Path to the CA bundle that should be used to validate the backend + certificate. + - If this parameter is not set, module will use the CA bundle that + python is using. + - It is also possible to set this parameter via the I(SENSU_CA_PATH) + environment variable. + type: path + version_added: 1.5.0 +""" diff --git a/ansible_collections/sensu/sensu_go/plugins/doc_fragments/info.py b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/info.py new file mode 100644 index 00000000..32614368 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/info.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.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 + + +class ModuleDocFragment(object): + DOCUMENTATION = """ +options: + name: + description: + - Retrieve information about this specific object instead of listing all objects. + type: str +""" diff --git a/ansible_collections/sensu/sensu_go/plugins/doc_fragments/labels.py b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/labels.py new file mode 100644 index 00000000..849cd85d --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/labels.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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): + DOCUMENTATION = """ +options: + labels: + description: + - Custom metadata fields that can be accessed within Sensu, as key/value + pairs. + type: dict + default: {} +""" diff --git a/ansible_collections/sensu/sensu_go/plugins/doc_fragments/name.py b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/name.py new file mode 100644 index 00000000..68efc27a --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/name.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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): + DOCUMENTATION = """ +options: + name: + description: + - The Sensu resource's name. This name (in combination with the + namespace where applicable) uniquely identifies the resource that + Ansible operates on. + - If the resource with selected name already exists, Ansible module will + update it to match the specification in the task. + - Consult the I(name) metadata attribute specification in the upstream + docs on U(https://docs.sensu.io/sensu-go/latest/reference/) for + more details about valid names and other restrictions. + type: str + required: yes +""" diff --git a/ansible_collections/sensu/sensu_go/plugins/doc_fragments/namespace.py b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/namespace.py new file mode 100644 index 00000000..169a1ee0 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/namespace.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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): + DOCUMENTATION = """ +options: + namespace: + description: + - RBAC namespace to operate in. If this is not set the value of the + SENSU_NAMESPACE environment variable will be used. + type: str + default: default +""" diff --git a/ansible_collections/sensu/sensu_go/plugins/doc_fragments/requirements.py b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/requirements.py new file mode 100644 index 00000000..15ca542b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/requirements.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2021, XLAB Steampunk <steampunk@xlab.si> +# +# 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): + # We have an empty options key because ansible fails without it. + DOCUMENTATION = """ +options: {} +requirements: + - python >= 2.7 +""" diff --git a/ansible_collections/sensu/sensu_go/plugins/doc_fragments/secrets.py b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/secrets.py new file mode 100644 index 00000000..44c48145 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/secrets.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk <steampunk@xlab.si> +# +# 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): + DOCUMENTATION = """ +options: + secrets: + description: + - List of secrets that are available to the command. + type: list + elements: dict + version_added: 1.6.0 + suboptions: + name: + description: + - Variable name that will contain the sensitive data. + type: str + required: true + version_added: 1.6.0 + secret: + description: + - Name of the secret that contains sensitive data. + type: str + required: true + version_added: 1.6.0 +""" diff --git a/ansible_collections/sensu/sensu_go/plugins/doc_fragments/state.py b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/state.py new file mode 100644 index 00000000..513c2065 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/state.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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): + DOCUMENTATION = """ +options: + state: + description: + - Target state of the Sensu object. + type: str + choices: [ present, absent ] + default: present +""" diff --git a/ansible_collections/sensu/sensu_go/plugins/filter/backends.py b/ansible_collections/sensu/sensu_go/plugins/filter/backends.py new file mode 100644 index 00000000..ecafd458 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/filter/backends.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + + +def _format_backend(vars): + if "api_key_file" in vars: + protocol = "wss" + else: + protocol = "ws" + return "{0}://{1}:{2}".format(protocol, vars["inventory_hostname"], 8081) + + +def backends(hostvars, groups): + return [ + _format_backend(hostvars[name]) for name in groups.get("backends", []) + ] + + +class FilterModule(object): + def filters(self): + return dict( + backends=backends, + ) diff --git a/ansible_collections/sensu/sensu_go/plugins/filter/package_name.py b/ansible_collections/sensu/sensu_go/plugins/filter/package_name.py new file mode 100644 index 00000000..80e9a875 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/filter/package_name.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + + +def _apt_package_name(name, version, build): + if version == "latest": + return name + if build == "latest": + return "{0}={1}-*".format(name, version) + return "{0}={1}-{2}".format(name, version, build) + + +def _yum_package_name(name, version, build): + if version == "latest": + return name + if build == "latest": + return "{0}-{1}".format(name, version) + return "{0}-{1}-{2}".format(name, version, build) + + +KIND_HANDLERS = dict( + apt=_apt_package_name, + yum=_yum_package_name, +) + + +def package_name(kind, name, version, build): + return KIND_HANDLERS[kind](name, version, build) + + +class FilterModule(object): + def filters(self): + return dict( + package_name=package_name, + ) diff --git a/ansible_collections/sensu/sensu_go/plugins/module_utils/arguments.py b/ansible_collections/sensu/sensu_go/plugins/module_utils/arguments.py new file mode 100644 index 00000000..aed632dc --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/module_utils/arguments.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +from ansible.module_utils.basic import env_fallback + +from . import client + + +SHARED_SPECS = dict( + auth=dict( + type="dict", + apply_defaults=True, + options=dict( + user=dict( + default="admin", + fallback=(env_fallback, ["SENSU_USER"]), + ), + password=dict( + default="P@ssw0rd!", + no_log=True, + fallback=(env_fallback, ["SENSU_PASSWORD"]), + ), + url=dict( + default="http://localhost:8080", + fallback=(env_fallback, ["SENSU_URL"]), + ), + api_key=dict( + fallback=(env_fallback, ["SENSU_API_KEY"]), + no_log=True, + ), + verify=dict( + default=True, + fallback=(env_fallback, ["SENSU_VERIFY"]), + type="bool", + ), + ca_path=dict( + fallback=(env_fallback, ["SENSU_CA_PATH"]), + type="path", + ), + ), + ), + state=dict( + default="present", + choices=["present", "absent"], + ), + name=dict( + required=True, + ), + namespace=dict( + default="default", + fallback=(env_fallback, ["SENSU_NAMESPACE"]), + ), + labels=dict( + type="dict", + default={}, + ), + annotations=dict( + type="dict", + default={}, + ), + secrets=dict( + type="list", + elements="dict", + no_log=False, + options=dict( + name=dict(type="str", required=True), + secret=dict(type="str", required=True, no_log=False), + ), + ), +) + + +def get_spec(*param_names): + return dict((p, SHARED_SPECS[p]) for p in param_names) + + +def get_spec_payload(source, *wanted_params): + return dict( + (k, source[k]) for k in wanted_params if source.get(k) is not None + ) + + +def get_renamed_spec_payload(source, param_mapping): + return dict( + (n, source[k]) for k, n in param_mapping.items() + if source.get(k) is not None + ) + + +def get_mutation_payload(source, *wanted_params): + payload = get_spec_payload(source, *wanted_params) + payload["metadata"] = dict( + name=source["name"], + ) + # Cluster-wide objects are not limited to a single namespace. This is why we set + # metadata.namespace field only if namespace is present in parameters. + if "namespace" in source: + if not source["namespace"]: + # We are raising an exception here for the sake of sanity test. + raise AssertionError("BUG: namespace should not be None") + payload["metadata"]["namespace"] = source["namespace"] + + for kind in "labels", "annotations": + if source.get(kind): + payload["metadata"][kind] = dict( + (k, str(v)) for k, v in source[kind].items() + ) + return payload + + +def get_sensu_client(auth): + return client.Client( + auth["url"], auth["user"], auth["password"], auth["api_key"], + auth["verify"], auth["ca_path"], + ) diff --git a/ansible_collections/sensu/sensu_go/plugins/module_utils/bonsai.py b/ansible_collections/sensu/sensu_go/plugins/module_utils/bonsai.py new file mode 100644 index 00000000..b7962cf8 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/module_utils/bonsai.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +from . import errors, http + + +def get(path): + url = "https://bonsai.sensu.io/api/v1/assets/{0}".format(path) + resp = http.request("GET", url) + + if resp.status != 200: + raise errors.BonsaiError( + "Server returned status {0}".format(resp.status), + ) + if resp.json is None: + raise errors.BonsaiError("Server returned invalid JSON document") + + return resp.json + + +def get_available_asset_versions(namespace, name): + asset_data = get("{0}/{1}".format(namespace, name)) + try: + return set(v["version"] for v in asset_data["versions"]) + except (TypeError, KeyError): + raise errors.BonsaiError( + "Cannot extract versions from {0}".format(asset_data), + ) + + +def get_asset_version_builds(namespace, name, version): + asset = get("{0}/{1}/{2}/release_asset_builds".format( + namespace, name, version, + )) + if "spec" not in asset or "builds" not in asset["spec"]: + raise errors.BonsaiError("Invalid build spec: {0}".format(asset)) + return asset + + +def get_asset_parameters(name, version): + try: + namespace, asset_name = name.split("/") + except ValueError: + raise errors.BonsaiError( + "Bonsai asset names should be formatted as <namespace>/<name>.", + ) + + available_versions = get_available_asset_versions(namespace, asset_name) + if version not in available_versions: + raise errors.BonsaiError( + "Version {0} is not available. Choose from: {1}.".format( + version, ", ".join(available_versions), + ), + ) + + asset_builds = get_asset_version_builds(namespace, asset_name, version) + + return dict( + labels=asset_builds.get("metadata", {}).get("labels"), + annotations=asset_builds.get("metadata", {}).get("annotations"), + builds=asset_builds["spec"]["builds"], + ) diff --git a/ansible_collections/sensu/sensu_go/plugins/module_utils/client.py b/ansible_collections/sensu/sensu_go/plugins/module_utils/client.py new file mode 100644 index 00000000..cd53077d --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/module_utils/client.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +try: + from ansible.module_utils.compat import version +except ImportError: + from distutils import version + +from . import errors, http + + +class Client: + BAD_VERSION = version.StrictVersion("9999.99.99") + + def __init__(self, address, username, password, api_key, verify, ca_path): + self.address = address.rstrip("/") + self.username = username + self.password = password + self.api_key = api_key + self.verify = verify + self.ca_path = ca_path + + self._auth_header = None # Login when/if required + self._version = None # Set version only if the consumer needs it + + @property + def auth_header(self): + if not self._auth_header: + self._auth_header = self._login() + return self._auth_header + + @property + def version(self): + if self._version is None: + resp = self.get("/version") + if resp.status != 200: + raise errors.SensuError( + "Version API returned status {0}".format(resp.status), + ) + if resp.json is None: + raise errors.SensuError( + "Version API did not return a valid JSON", + ) + if "sensu_backend" not in resp.json: + raise errors.SensuError( + "Version API did not return backend version", + ) + try: + self._version = version.StrictVersion( + resp.json["sensu_backend"].split("#")[0] + ) + except ValueError: + # Backend has no version compiled in - we are probably running + # againts self-compiled version from git. + self._version = self.BAD_VERSION + + return self._version + + def _login(self): + if self.api_key: + return self._api_key_login() + return self._username_password_login() + + def _api_key_login(self): + # We cannot validate the API key because there is no API endpoint that + # we could hit for verification purposes. This means that the error + # reporting will be a mess but there is not much we can do here. + return dict(Authorization="Key {0}".format(self.api_key)) + + def _username_password_login(self): + resp = http.request( + "GET", "{0}/auth".format(self.address), force_basic_auth=True, + url_username=self.username, url_password=self.password, + validate_certs=self.verify, ca_path=self.ca_path, + ) + + if resp.status != 200: + raise errors.SensuError( + "Authentication call returned status {0}".format(resp.status), + ) + + if resp.json is None: + raise errors.SensuError( + "Authentication call did not return a valid JSON", + ) + + if "access_token" not in resp.json: + raise errors.SensuError( + "Authentication call did not return access token", + ) + + return dict( + Authorization="Bearer {0}".format(resp.json["access_token"]), + ) + + def request(self, method, path, payload=None): + url = self.address + path + headers = self.auth_header + + response = http.request( + method, url, payload=payload, headers=headers, + validate_certs=self.verify, ca_path=self.ca_path, + ) + + if response.status in (401, 403): + raise errors.SensuError( + "Authentication problem. Verify your credentials." + ) + + return response + + def get(self, path): + return self.request("GET", path) + + def put(self, path, payload): + return self.request("PUT", path, payload) + + def delete(self, path): + return self.request("DELETE", path) + + def validate_auth_data(self, username, password): + resp = http.request( + "GET", "{0}/auth/test".format(self.address), + force_basic_auth=True, url_username=username, + url_password=password, validate_certs=self.verify, + ca_path=self.ca_path, + ) + if resp.status not in (200, 401): + raise errors.SensuError( + "Authentication test returned status {0}".format(resp.status), + ) + return resp.status == 200 diff --git a/ansible_collections/sensu/sensu_go/plugins/module_utils/debug.py b/ansible_collections/sensu/sensu_go/plugins/module_utils/debug.py new file mode 100644 index 00000000..25bee388 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/module_utils/debug.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +import os +import tempfile +from datetime import datetime + + +DEBUG = os.environ.get("SENSU_ANSIBLE_DEBUG", "").lower() in ["yes", "true"] + + +def log(message, *args, **kwargs): + """ + Log message to a file (/tmp/sensu-ansible.log) at remote target + + Sensu API returns fairly modest error messages (e.g. when PUT payload contains + unsupported parameter, the error message won't tell you which one) and that + makes it difficult to debug. For that reason we decided to support at least + the most primitive type of logging: write to /tmp/sensu-ansible.log file. + Beware the log file resides on Ansible target and not host because this is + where the module gets executed. + + This function won't do anything unless target has environment variable + SENSU_ANSIBLE_DEBUG set to "yes". When troubleshooting, just set the env + variable in the playbook. + """ + if DEBUG: + with open(os.path.join(tempfile.gettempdir(), "sensu-ansible.log"), "a") as f: + f.write("[{0}]: {1}\n".format(datetime.utcnow(), message.format(*args, **kwargs))) + + +def log_request(method, url, payload, resp=None, comment=None): + """Log API request and response""" + if DEBUG: + if resp: + code, data = resp.status, resp.data + else: + code = data = "?" + fmt = "{0} {1} {2}\nPAYLOAD:{3}\nRESPONSE:{4}\nCOMMENT:{5}" + log(fmt, code, method, url, payload, data, comment) diff --git a/ansible_collections/sensu/sensu_go/plugins/module_utils/errors.py b/ansible_collections/sensu/sensu_go/plugins/module_utils/errors.py new file mode 100644 index 00000000..5be0675c --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/module_utils/errors.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 Error(Exception): + """ Base error that serves as a parent for all other errors. """ + + +class HttpError(Error): + """ Error that signals failure in HTTP connection. """ + + +class SyncError(Error): + """ Error that signals failure when syncing state with remote. """ + + +class SensuError(Error): + """ Error that signals problems with Sensu Go web API. """ + + +class BonsaiError(Error): + """ Error that signals problems with Bonsai assets. """ diff --git a/ansible_collections/sensu/sensu_go/plugins/module_utils/http.py b/ansible_collections/sensu/sensu_go/plugins/module_utils/http.py new file mode 100644 index 00000000..054fa766 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/module_utils/http.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +import json + +try: + from ssl import CertificateError +except ImportError: + # This will never match the ssl exception, which will cause exception to + # bubble up the call stack. + class CertificateError(Exception): + pass + +from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError +from ansible.module_utils.urls import open_url + +from . import errors, debug + + +class Response: + def __init__(self, status, data): + self.status = status + self.data = data + self._json = None + + @property + def json(self): + if self._json is None: + try: + self._json = json.loads(self.data) + except TypeError: # Handle python 3.5 returning bytes + try: + self._json = json.loads(self.data.decode('utf-8')) + except ValueError: + self._json = None + except ValueError: # Cannot use JSONDecodeError here (python 2) + self._json = None + + return self._json + + +def request(method, url, payload=None, data=None, headers=None, **kwargs): + if payload is not None: + data = json.dumps(payload, separators=(",", ":")) + headers = dict(headers or {}, **{"content-type": "application/json"}) + + try: + raw_resp = open_url( + method=method, url=url, data=data, headers=headers, **kwargs + ) + resp = Response(raw_resp.getcode(), raw_resp.read()) + debug.log_request(method, url, payload, resp) + return resp + except HTTPError as e: + # This is not an error, since client consumers might be able to + # work around/expect non 20x codes. + resp = Response(e.code, e.reason) + debug.log_request(method, url, payload, resp) + return resp + except URLError as e: + debug.log_request(method, url, payload, comment=e.reason) + raise errors.HttpError( + "{0} request failed: {1}".format(method, e.reason), + ) + except CertificateError as e: + raise errors.HttpError("Certificate error: {0}".format(e)) diff --git a/ansible_collections/sensu/sensu_go/plugins/module_utils/role_utils.py b/ansible_collections/sensu/sensu_go/plugins/module_utils/role_utils.py new file mode 100644 index 00000000..4079473f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/module_utils/role_utils.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +from . import utils + + +def validate_module_params(params): + if params['state'] == 'present': + if not params['rules']: + return 'state is present but all of the following are missing: rules' + return None + + +def validate_binding_module_params(params): + if params["state"] == "present": + if not (params["users"] or params["groups"]): + return 'missing required parameters: users or groups' + + +def type_name_dict(obj_type, name): + return { + 'type': obj_type, + 'name': name, + } + + +def build_subjects(groups, users): + groups_dicts = [type_name_dict('Group', g) for g in (groups or [])] + users_dicts = [type_name_dict('User', u) for u in (users or [])] + + return groups_dicts + users_dicts + + +def do_role_bindings_differ(current, desired): + if _do_subjects_differ(current['subjects'], desired['subjects']): + return True + + return utils.do_differ(current, desired, 'subjects') + + +# sorts a list of subjects (dicts returned by type_name_dict) +# by 'type' and 'name' keys and returns the result of comparison. +def _do_subjects_differ(a, b): + sorted_a = sorted(a, key=lambda x: (x['type'], x['name'])) + sorted_b = sorted(b, key=lambda x: (x['type'], x['name'])) + return sorted_a != sorted_b + + +def _rule_set(rules): + return set( + ( + frozenset(r.get('verbs', []) or []), + frozenset(r.get('resources', []) or []), + frozenset(r.get('resource_names', []) or []) + ) for r in rules + ) + + +def _do_rules_differ(current_rules, desired_rules): + if len(current_rules) != len(desired_rules): + return True + if _rule_set(current_rules) != _rule_set(desired_rules): + return True + return False + + +def do_roles_differ(current, desired): + if _do_rules_differ(current['rules'], desired['rules']): + return True + + return utils.do_differ(current, desired, 'rules') diff --git a/ansible_collections/sensu/sensu_go/plugins/module_utils/utils.py b/ansible_collections/sensu/sensu_go/plugins/module_utils/utils.py new file mode 100644 index 00000000..bf97120f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/module_utils/utils.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +from ansible.module_utils.six.moves.urllib.parse import quote + +from . import errors + + +def do_differ(current, desired, *ignored_keys): + if "metadata" in desired: + # We treat metadata as any regular dict but ignore the created_by + # value since this piece of data is autogenerated on the backend and + # must be skipped in the comparison. + if do_differ(current["metadata"], desired["metadata"], "created_by"): + return True + + ignored_keys = ignored_keys + ("metadata",) + + for key, value in desired.items(): + if key in ignored_keys: + continue + + if value != current.get(key): + return True + + return False + + +def do_differ_v1(current, desired, *ignored_keys): + # Default comparator for v1 API (mostly enterprise features) + + if "metadata" in desired: + # We treat metadata as any regular dict but ignore the created_by + # value since this piece of data is autogenerated on the backend and + # must be skipped in the comparison. + if do_differ(current["metadata"], desired["metadata"], "created_by"): + return True + + for key, value in desired.get("spec", {}).items(): + if key in ignored_keys: + continue + + if value != current["spec"].get(key): + return True + + return False + + +def sync(state, client, path, payload, check_mode, compare=do_differ): + remote_object = get(client, path) + + if state == "absent" and remote_object is None: + return False, None + + if state == "absent": + if not check_mode: + delete(client, path) + return True, None + + # Making sure remote_object is present from here on + + if remote_object is None or compare(remote_object, payload): + if check_mode: + return True, payload + put(client, path, payload) + return True, get(client, path) + + return False, remote_object + + +def sync_v1(state, client, path, payload, check_mode, compare=do_differ_v1): + changed, result = sync(state, client, path, payload, check_mode, compare) + return changed, convert_v1_to_v2_response(result) + + +def _abort(msg, *args, **kwargs): + raise errors.SyncError(msg.format(*args, **kwargs)) + + +def get(client, path): + resp = client.get(path) + if resp.status not in (200, 404): + _abort( + "GET {0} failed with status {1}: {2}", path, resp.status, resp.data, + ) + if resp.status == 200 and resp.json is None: + _abort("Server returned invalid JSON {0}", resp.data) + return resp.json + + +def delete(client, path): + resp = client.delete(path) + if resp.status != 204: + _abort( + "DELETE {0} failed with status {1}: {2}", + path, resp.status, resp.data, + ) + return None + + +def put(client, path, payload): + resp = client.put(path, payload) + if resp.status not in (200, 201): + _abort( + "PUT {0} failed with status {1}: {2}", + path, resp.status, resp.data, + ) + return None + + +def dict_to_single_item_dicts(data): + return [{k: v} for k, v in data.items()] + + +def single_item_dicts_to_dict(data): + result = {} + for item in data: + (k, v), = item.items() + result[k] = v + return result + + +def dict_to_key_value_strings(data): + return ["{0}={1}".format(k, v) for k, v in data.items()] + + +def build_url_path(api_group, api_version, namespace, *parts): + prefix = "/api/{0}/{1}/".format(api_group, api_version) + if namespace: + prefix += "namespaces/{0}/".format(quote(namespace, safe="")) + return prefix + "/".join(quote(p, safe="") for p in parts if p) + + +def build_core_v2_path(namespace, *parts): + return build_url_path("core", "v2", namespace, *parts) + + +def prepare_result_list(result): + if isinstance(result, list): + return result + return [] if result is None else [result] + + +def convert_v1_to_v2_response(response): + # dict(metadata=<meta>, spec=dict(a=1)) -> dict(metadata=<meta>, a=1) + + if not response: + return response + + if "metadata" not in response: + return response["spec"] + + # Move metadata key into the spec. + return dict(response["spec"], metadata=response["metadata"]) + + +def do_secrets_differ(current, desired): + return set( + (c["name"], c["secret"]) for c in (current.get("secrets") or []) + ) != set( + (d["name"], d["secret"]) for d in (desired.get("secrets") or []) + ) + + +def deprecate(module, msg, version): + try: + module.deprecate(msg, version=version, collection_name="sensu.sensu_go") + except TypeError: + # Ansible < 2.9.10 does not support collection_name kwarg. Output at + # least msg and version of collection when things will stop working. + module.deprecate(msg, version=version) diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/ad_auth_provider.py b/ansible_collections/sensu/sensu_go/plugins/modules/ad_auth_provider.py new file mode 100644 index 00000000..609b16cb --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/ad_auth_provider.py @@ -0,0 +1,396 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: ad_auth_provider + +author: + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Miha Dolinar (@mdolin) + - Tadej Borovsak (@tadeboro) + +short_description: Manage Sensu AD authentication provider + +description: + - Create, update or delete a Sensu Go AD authentication provider. + - For more information, refer to the Sensu Go documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/control-access/ad-auth/). + +version_added: 1.10.0 + +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.state + +options: + servers: + description: + - An array of AD servers for your directory. + type: list + elements: dict + suboptions: + host: + description: + - AD server IP address. + required: true + type: str + port: + description: + - AD server port. + type: int + insecure: + description: + - Skips SSL certificate verification when set to true. + type: bool + default: false + security: + description: + - Encryption type to be used for the connection to the AD server. + type: str + choices: [ insecure, tls, starttls ] + default: tls + trusted_ca_file: + description: + - Path to an alternative CA bundle file. + type: str + client_cert_file: + description: + - Path to the certificate that should be sent to the server if requested. + type: str + client_key_file: + description: + - Path to the key file associated with the client_cert_file. + - Required if I(client_cert_file) is present. + type: str + default_upn_domain: + description: + - Enables UPN authentication when set. The default UPN suffix that will be appended + to the username when a domain is not specified during login + (for example, user becomes user@defaultdomain.xyz). + type: str + include_nested_groups: + description: + - If true, the group search includes any nested groups a user is a member of. + If false, the group search includes only the top-level groups a user is a member of. + type: bool + binding: + description: + - The AD account that performs user and group lookups. + - If your sever supports anonymous binding, you can omit the user_dn or password + attributes to query the directory without credentials. + type: dict + suboptions: + user_dn: + description: + - The AD account that performs user and group lookups. + - If your sever supports anonymous binding, you can omit this attribute. + type: str + required: true + password: + description: + - Password for the user_dn account. + - If your sever supports anonymous binding, you can omit this attribute. + type: str + required: true + group_search: + description: + - Search configuration for groups. + type: dict + suboptions: + base_dn: + description: + - Which part of the directory tree to search. + required: true + type: str + attribute: + description: + - Used for comparing result entries. + type: str + default: member + name_attribute: + description: + - Represents the attribute to use as the entry name. + type: str + default: cn + object_class: + description: + - Identifies the class of objects returned in the search result. + type: str + default: group + user_search: + description: + - Search configuration for users. + type: dict + suboptions: + base_dn: + description: + - Which part of the directory tree to search. + required: true + type: str + attribute: + description: + - Used for comparing result entries. + type: str + default: sAMAccountName + name_attribute: + description: + - Represents the attribute to use as the entry name. + type: str + default: displayName + object_class: + description: + - Identifies the class of objects returned in the search result. + type: str + default: person + groups_prefix: + description: + - The prefix added to all AD groups. + type: str + username_prefix: + description: + - The prefix added to all AD usernames. + type: str + +seealso: + - module: sensu.sensu_go.auth_provider_info + - module: sensu.sensu_go.ldap_auth_provider + - module: sensu.sensu_go.oidc_auth_provider +""" + +EXAMPLES = """ +- name: Create a AD auth provider + sensu.sensu_go.ad_auth_provider: + name: activedirectory + servers: + - host: 127.0.0.1 + group_search: + base_dn: dc=acme,dc=org + user_search: + base_dn: dc=acme,dc=org +- name: Delete a AD auth provider + sensu.sensu_go.ad_auth_provider: + name: activedirectory + state: absent +""" + +RETURN = """ +object: + description: Object representing Sensu AD authentication provider. + returned: success + type: dict + sample: + metadata: + name: 'activedirectory' + servers: + host: '127.0.0.1' + port: '636' + insecure: 'False' + security: 'tls' + trusted_ca_file: '/path/to/trusted-certificate-authorities.pem' + client_cert_file: '/path/to/ssl/cert.pem' + client_key_file: '/path/to/ssl/key.pem' + default_upn_domain: 'example.org' + binding: + user_dn: 'cn=binder,dc=acme,dc=org' + group_search: + base_dn: 'dc=acme,dc=org' + attribute: 'member' + name_attribute': 'cn' + object_class: 'group' + user_search: + base_dn: 'dc=acme,dc=org' + attribute: 'sAMAccountName' + name_attribute: 'displayName' + object_class: 'person' + groups_prefix: 'AD' + username_prefix: 'AD' +""" + + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "authentication/v2" + + +def remove_item(result): + if result: + for server in result["servers"]: + if server["binding"] and "password" in server["binding"]: + del server["binding"]["password"] + + return result + + +def _filter(payload): + # Remove keys with None values from dict + return dict((k, v) for k, v in payload.items() if v is not None) + + +def do_differ(current, desired): + if utils.do_differ_v1(current, desired, "servers"): + return True + + if len(current["spec"]["servers"]) != len(desired["spec"]["servers"]): + return True + + for c, d in zip(current["spec"]["servers"], desired["spec"]["servers"]): + if utils.do_differ(c, _filter(d)): + return True + + return False + + +def main(): + required_if = [("state", "present", ["servers"])] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec( + "auth", + "name", + "state", + ), + servers=dict( + type="list", + elements="dict", + options=dict( + host=dict( + type="str", + required=True, + ), + port=dict( + type="int", + ), + insecure=dict( + type="bool", + default=False, + ), + security=dict( + type="str", + choices=["insecure", "tls", "starttls"], + default="tls", + ), + trusted_ca_file=dict( + type="str", + ), + client_cert_file=dict( + type="str", + ), + client_key_file=dict( + type="str", + ), + default_upn_domain=dict( + type="str", + ), + include_nested_groups=dict( + type="bool", + ), + binding=dict( + type="dict", + options=dict( + user_dn=dict( + type="str", + required=True, + ), + password=dict( + type="str", + no_log=True, + required=True, + ), + ), + ), + group_search=dict( + type="dict", + options=dict( + base_dn=dict( + type="str", + required=True, + ), + attribute=dict( + type="str", + default="member", + ), + name_attribute=dict( + type="str", + default="cn", + ), + object_class=dict(type="str", default="group"), + ), + ), + user_search=dict( + type="dict", + options=dict( + base_dn=dict( + type="str", + required=True, + ), + attribute=dict( + type="str", + default="sAMAccountName", + ), + name_attribute=dict( + type="str", + default="displayName", + ), + object_class=dict( + type="str", + default="person", + ), + ), + ), + ), + ), + groups_prefix=dict( + type="str", + ), + username_prefix=dict( + type="str", + ), + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, "authproviders", module.params["name"] + ) + + payload = dict( + type="ad", + api_version=API_VERSION, + metadata=dict(name=module.params["name"]), + spec=arguments.get_spec_payload( + module.params, "servers", "groups_prefix", "username_prefix" + ), + ) + + try: + changed, ad_provider = utils.sync_v1( + module.params["state"], client, path, payload, module.check_mode, do_differ + ) + module.exit_json(changed=changed, object=remove_item(ad_provider)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/asset.py b/ansible_collections/sensu/sensu_go/plugins/modules/asset.py new file mode 100644 index 00000000..627bfabb --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/asset.py @@ -0,0 +1,213 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Cameron Hurst <cahurst@cisco.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: asset +author: + - Cameron Hurst (@wakemaster39) + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu assets +description: + - Create, update or delete Sensu Go asset. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/assets/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state + - sensu.sensu_go.labels + - sensu.sensu_go.annotations +seealso: + - module: sensu.sensu_go.asset_info + - module: sensu.sensu_go.bonsai_asset +options: + builds: + description: + - A list of asset builds used to define multiple artefacts which + provide the named asset. + - Required if I(state) is C(present). + type: list + elements: dict + suboptions: + url: + description: + - The URL location of the asset. + type: str + required: yes + sha512: + description: + - The checksum of the asset. + type: str + required: yes + filters: + description: + - A set of Sensu query expressions used to determine if the asset + should be installed. + type: list + elements: str + headers: + description: + - Additional headers to send when retrieving the asset, e.g. for + authorization. + type: dict +""" + +EXAMPLES = """ +- name: Create a multiple-build asset + sensu.sensu_go.asset: + name: sensu-plugins-cpu-checks + builds: + - url: https://assets.bonsai.sensu.io/68546e739d96fd695655b77b35b5aabfbabeb056/sensu-plugins-cpu-checks_4.0.0_centos_linux_amd64.tar.gz + sha512: 518e7c17cf670393045bff4af318e1d35955bfde166e9ceec2b469109252f79043ed133241c4dc96501b6636a1ec5e008ea9ce055d1609865635d4f004d7187b + filters: + - entity.system.os == 'linux' + - entity.system.arch == 'amd64' + - entity.system.platform == 'rhel' + - url: https://assets.bonsai.sensu.io/68546e739d96fd695655b77b35b5aabfbabeb056/sensu-plugins-cpu-checks_4.0.0_alpine_linux_amd64.tar.gz + sha512: b2da25ecd7642e6de41fde37d674fe19dcb6ee3d680e145e32289f7cfc352e6b5f9413ee9b701d61faeaa47b399aa30b25885dbc1ca432c4061c8823774c28f3 + filters: + - entity.system.os == 'linux' + - entity.system.arch == 'amd64' + - entity.system.platform == 'alpine' + +- name: Delete an asset + sensu.sensu_go.asset: + name: sensu-plugins-cpu-check + state: absent +""" + +RETURN = """ +object: + description: Object representing Sensu asset. + returned: success + type: dict + sample: + metadata: + name: check_script + namespace: default + builds: + - sha512: 4f926bf4328f...2c58ad9ab40c9e2edc31b288d066b195b21b + url: http://example.com/asset.tar.gz +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def validate_module_params(params): + if params["state"] == "present": + if not params['builds']: + return "builds must include at least one element" + return None + + +def _build_set(builds): + return set(( + b.get('sha512'), + b.get('url'), + frozenset((b.get('headers', {}) or {}).items()), + frozenset(b.get('filters', []) or []), + ) for b in builds) + + +def _do_builds_differ(current, desired): + # Since Sensu Go 5.16, the web API returns builds: None if the asset + # in question is a deprecated, single-build asset. + if current is None: + return True + + if len(current) != len(desired): + return True + + return _build_set(current) != _build_set(desired) + + +def do_differ(current, desired): + if _do_builds_differ(current['builds'], desired['builds']): + return True + + return utils.do_differ(current, desired, 'builds') + + +def build_api_payload(params): + payload = arguments.get_mutation_payload(params) + if params['state'] == 'present': + builds = [arguments.get_spec_payload(b, *b.keys()) for b in params['builds']] + payload["builds"] = builds + return payload + + +def main(): + required_if = [ + ("state", "present", ["builds"]) + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec( + "auth", "name", "namespace", "state", "labels", "annotations", + ), + builds=dict( + type="list", + elements="dict", + options=dict( + url=dict( + required=True, + ), + sha512=dict( + required=True, + ), + filters=dict( + type="list", + elements="str", + ), + headers=dict( + type="dict", + ), + ) + ), + ), + ) + + msg = validate_module_params(module.params) + if msg: + module.fail_json(msg=msg) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "assets", module.params["name"], + ) + payload = build_api_payload(module.params) + + try: + changed, asset = utils.sync( + module.params["state"], client, path, payload, module.check_mode, do_differ + ) + module.exit_json(changed=changed, object=asset) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/asset_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/asset_info.py new file mode 100644 index 00000000..3c128dcb --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/asset_info.py @@ -0,0 +1,99 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: asset_info +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu assets +description: + - Retrieve information about Sensu Go assets. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/assets/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.asset + - module: sensu.sensu_go.bonsai_asset +""" + +EXAMPLES = """ +- name: List all Sensu assets + sensu.sensu_go.asset_info: + register: result + +- name: List the selected Sensu asset + sensu.sensu_go.asset_info: + name: my_asset + register: result + +- name: Do something with result + ansible.builtin.debug: + msg: "{{ result.objects.0.metadata.name }}" + +""" + +RETURN = """ +objects: + description: List of Sensu assets. + returned: success + type: list + elements: dict + sample: + - metadata: + name: check_script + namespace: default + builds: + - sha512: 4f926bf4328f...2c58ad9ab40c9e2edc31b288d066b195b21b + url: http://example.com/asset.tar.gz +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "assets", module.params["name"], + ) + + try: + assets = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=assets) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/auth_provider_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/auth_provider_info.py new file mode 100644 index 00000000..6215af44 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/auth_provider_info.py @@ -0,0 +1,145 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: auth_provider_info + +author: + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Miha Dolinar (@mdolin) + - Tadej Borovsak (@tadeboro) + +short_description: List Sensu authentication providers + +description: + - Retrieve information about Sensu Go authentication providers. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/control-access/). + +version_added: 1.10.0 + +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + +seealso: + - module: sensu.sensu_go.ad_auth_provider + - module: sensu.sensu_go.ldap_auth_provider + - module: sensu.sensu_go.oidc_auth_provider +""" + +EXAMPLES = """ +- name: List all Sensu authentication providers + sensu.sensu_go.auth_provider_info: + register: result + +- name: List the selected Sensu authentication provider + sensu.sensu_go.auth_provider_info: + name: my_auth_provider + register: result + +- name: Do something with result + ansible.builtin.debug: + msg: "{{ result.objects.0.metadata.name }}" +""" + +RETURN = """ +objects: + description: List of Sensu authentication providers. + returned: success + type: list + elements: dict + sample: + - metadata: + name: 'openldap' + groups_prefix: '' + servers: + binding: + user_dn: 'cn=binder,dc=acme,dc=org' + client_cert_file: '' + client_key_file: '' + default_upn_domain: '' + group_search: + attribute: 'member' + base_dn: 'dc=acme,dc=org' + name_attribute: 'cn' + object_class: 'groupOfNames' + host: '127.0.0.1' + insecure: false + port: 636 + security: 'tls' + trusted_ca_file: '' + user_search: + attribute: 'uid' + base_dn: 'dc=acme,dc=org' + name_attribute: 'cn' + object_class: 'person' + username_prefix: '' +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "authentication/v2" + + +def remove_item(result): + for server in result.get("servers", []): + if server["binding"] and "password" in server["binding"]: + del server["binding"]["password"] + + if "client_secret" in result: + del result["client_secret"] + + return result + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, + API_VERSION, + None, + "authproviders", + module.params["name"], + ) + + try: + providers = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + # We simulate the behavior of v2 API here and only return the spec. + module.exit_json( + changed=False, + objects=[remove_item(utils.convert_v1_to_v2_response(p)) for p in providers], + ) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/bonsai_asset.py b/ansible_collections/sensu/sensu_go/plugins/modules/bonsai_asset.py new file mode 100644 index 00000000..417218ec --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/bonsai_asset.py @@ -0,0 +1,131 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: bonsai_asset +author: + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Tadej Borovsak (@tadeboro) +short_description: Add Sensu assets from Bonsai +description: + - Create or update a Sensu Go asset whose definition is available in the + Bonsai, the Sensu asset index. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/assets/) + and U(https://bonsai.sensu.io/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.labels + - sensu.sensu_go.annotations +options: + version: + description: + - Version number of the asset to install. + type: str + required: true + rename: + description: + - The name that will be used when adding the asset to Sensu. + - If not present, value of the I(name) parameter will be used. + type: str + on_remote: + description: + - If set to C(true), module will download asset defnition on remote host. + - If not set or set to C(false), ansible downloads asset definition + on control node. + type: bool + version_added: 1.13.0 +notes: + - I(labels) and I(annotations) values are merged with the values obtained + from Bonsai. Values passed-in as parameters take precedence over the + values obtained from Bonsai. + - To delete an asset, use regular M(sensu.sensu_go.asset) module. +seealso: + - module: sensu.sensu_go.asset + - module: sensu.sensu_go.asset_info +""" + +EXAMPLES = """ +- name: Make sure specific version of asset is installed + sensu.sensu_go.bonsai_asset: + name: sensu/monitoring-plugins + version: 2.2.0-1 + +- name: Remove previously added asset + sensu.sensu_go.asset: + name: sensu/monitoring-plugins + state: absent + +- name: Store Bonsai asset under a different name + sensu.sensu_go.bonsai_asset: + name: sensu/monitoring-plugins + version: 2.2.0-1 + rename: sensu-monitoring-2.2.0-1 + +- name: Display asset info + sensu.sensu_go.asset_info: + name: sensu-monitoring-2.2.0-1 # value from rename field +""" + +RETURN = """ +object: + description: Object representing Sensu asset. + returned: success + type: dict + sample: + metadata: + name: check_script + namespace: default + builds: + - sha512: 4f926bf4328f...2c58ad9ab40c9e2edc31b288d066b195b21b + url: http://example.com/asset.tar.gz +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import bonsai, errors + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + name=dict( + type="str", + required=True, + ), + version=dict( + type="str", + required=True, + ), + ), + ) + + try: + asset = bonsai.get_asset_parameters( + module.params["name"], module.params["version"], + ) + module.exit_json(changed=False, asset=asset) + except errors.Error as e: + module.fail_json(changed=False, msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/check.py b/ansible_collections/sensu/sensu_go/plugins/modules/check.py new file mode 100644 index 00000000..47f44693 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/check.py @@ -0,0 +1,449 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: check +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu checks +description: + - Create, update or delete Sensu Go check. + - For more information, refer to the Sensu Go documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/checks/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state + - sensu.sensu_go.labels + - sensu.sensu_go.annotations + - sensu.sensu_go.secrets +seealso: + - module: sensu.sensu_go.check_info +options: + command: + description: + - Check command to run. + - Required if I(state) is C(present). + type: str + subscriptions: + description: + - List of subscriptions which receive check requests. + - Required if I(state) is C(present). + type: list + elements: str + handlers: + description: + - List of handlers which receive check results. + type: list + elements: str + interval: + description: + - Check request interval. + - Cannot be used when I(cron) option is used. + type: int + cron: + description: + - Schedule check requests using crontab syntax. + - Cannot be used when I(interval) option is used. + type: str + publish: + description: + - Enables or disables scheduled publication of check requests. + type: bool + timeout: + description: + - Check execution timeout. + type: int + ttl: + description: + - Amount of time after which a check result is considered stale. + type: int + stdin: + description: + - Enables writing of serialized JSON data to the check command's stdin. + - Only usable with checks written specifically for Sensu Go. + type: bool + low_flap_threshold: + description: + - Low flap threshold. + type: int + high_flap_threshold: + description: + - High flap threshold. + type: int + runtime_assets: + description: + - List of runtime assets required to run the check. + type: list + elements: str + check_hooks: + description: + - A mapping of response codes to hooks which will be run by the agent + when that code is returned. + - Note that the structure of this parameter is a bit different from the + one described at + U(https://docs.sensu.io/sensu-go/latest/reference/checks/#check-hooks-attribute). + - See check hooks example below for more information on exact mapping + structure. + type: dict + proxy_entity_name: + description: + - Entity name to associate this check with instead of the agent it ran on. + type: str + proxy_requests: + description: + - Allows you to assign the check to run for multiple entities according + to their entity_attributes. + type: dict + suboptions: + entity_attributes: + description: + - List of attribute checks for determining which proxy entities this check should be scheduled against. + type: list + elements: str + splay: + description: + - Enables or disables splaying of check request scheduling. + type: bool + splay_coverage: + description: + - Percentage of the C(interval) over which to splay checks. + type: int + output_metric_format: + description: + - Enable parsing of metrics in the specified format from this check's + output. + type: str + choices: + - graphite_plaintext + - influxdb_line + - nagios_perfdata + - opentsdb_line + output_metric_handlers: + description: + - List of handlers which receive check results. I'm not sure why this exists. + type: list + elements: str + round_robin: + description: + - An array of environment variables to use with command execution. + type: bool + env_vars: + description: + - A mapping of environment variable names and values to use with command execution. + type: dict +''' + +EXAMPLES = ''' +- name: Check executing command every 30 seconds + sensu.sensu_go.check: + name: check + command: check-cpu.sh -w 75 -c 90 + subscriptions: + - checks + interval: 30 + publish: yes + +- name: Check executing command with cron scheduler + sensu.sensu_go.check: + name: check + command: check-cpu.sh -w 75 -c 90 + subscriptions: + - systems + handlers: + - slack + cron: "* * * * *" + publish: yes + +- name: Ad-hoc scheduling + sensu.sensu_go.check: + name: check + command: check-cpu.sh -w 75 -c 90 + subscriptions: + - systems + handlers: + - slack + interval: 60 + publish: no + +- name: Report events under proxy entity name instead of agent entity + sensu.sensu_go.check: + name: check + command: http_check.sh https://sensu.io + subscriptions: + - proxy + handlers: + - slack + interval: 60 + proxy_entity_name: sensu-site + round_robin: yes + publish: yes + +- name: Event that triggers hooks + sensu.sensu_go.check: + name: check + command: http_check.sh https://sensu.io + subscriptions: [ proxy ] + # The upstream JSON payload for the hooks below would look like this: + # + # "check_hooks": [ + # {"0": ["passing-hook", "always-run-this-hook"]}, + # {"critical": ["failing-hook", "always-run-this-hook"]} + # ] + # + # Ansible task simplifies this structure into a simple mapping: + check_hooks: + "0": + - passing-hook + - always-run-this-hook + critical: + - failing-hook + - always-run-this-hook + +- name: Remove check + sensu.sensu_go.check: + name: my-check + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu check. + returned: success + type: dict + sample: + metadata: + name: check_minimum + namespace: default + command: collect.sh + handlers: + - slack + interval: 10 + publish: true + subscriptions: + - system +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def validate_module_params(module): + params = module.params + proxy_requests = params['proxy_requests'] + + if (proxy_requests and proxy_requests.get('splay', False) and + proxy_requests.get('splay_coverage') is None): + module.fail_json(msg='splay is true but all of the following are missing: splay_coverage') + + if params['state'] == 'present' and not (params['interval'] or params['cron']): + module.fail_json(msg='one of the following is required: interval, cron') + + +def do_sets_differ(current, desired, key): + return set(current.get(key) or []) != set(desired.get(key) or []) + + +def do_proxy_requests_differ(current, desired): + if 'proxy_requests' not in desired: + return False + + current = current.get('proxy_requests') or {} + desired = desired['proxy_requests'] + + return ( + ( + 'entity_attributes' in desired and + do_sets_differ(current, desired, 'entity_attributes') + ) or + utils.do_differ(current, desired, 'entity_attributes') + ) + + +def do_check_hooks_differ(current, desired): + if 'check_hooks' not in desired: + return False + + current = utils.single_item_dicts_to_dict(current.get('check_hooks') or []) + current = dict((k, set(v)) for k, v in current.items()) + + desired = utils.single_item_dicts_to_dict(desired['check_hooks']) + desired = dict((k, set(v)) for k, v in desired.items()) + + return current != desired + + +def do_differ(current, desired): + return ( + utils.do_differ( + current, desired, 'proxy_requests', 'subscriptions', 'handlers', + 'runtime_assets', 'check_hooks', 'output_metric_handlers', + 'env_vars', 'secrets', + ) or + utils.do_secrets_differ(current, desired) or + do_proxy_requests_differ(current, desired) or + do_sets_differ(current, desired, 'subscriptions') or + do_sets_differ(current, desired, 'handlers') or + do_sets_differ(current, desired, 'runtime_assets') or + do_check_hooks_differ(current, desired) or + do_sets_differ(current, desired, 'output_metric_handlers') or + do_sets_differ(current, desired, 'env_vars') + ) + + +def build_api_payload(params): + payload = arguments.get_mutation_payload( + params, + 'command', + 'cron', + 'handlers', + 'high_flap_threshold', + 'interval', + 'low_flap_threshold', + 'output_metric_format', + 'output_metric_handlers', + 'proxy_entity_name', + 'publish', + 'round_robin', + 'runtime_assets', + 'secrets', + 'stdin', + 'subscriptions', + 'timeout', + 'ttl' + ) + + if params['proxy_requests']: + payload['proxy_requests'] = arguments.get_spec_payload( + params['proxy_requests'], + 'entity_attributes', 'splay', 'splay_coverage', + ) + + if params['check_hooks']: + payload['check_hooks'] = utils.dict_to_single_item_dicts(params['check_hooks']) + + if params['env_vars']: + payload['env_vars'] = utils.dict_to_key_value_strings(params['env_vars']) + + return payload + + +def main(): + required_if = [ + ('state', 'present', ['subscriptions', 'command']) + ] + mutually_exclusive = [('interval', 'cron')] + + module = AnsibleModule( + supports_check_mode=True, + required_if=required_if, + mutually_exclusive=mutually_exclusive, + argument_spec=dict( + arguments.get_spec( + "auth", "name", "state", "labels", "annotations", "namespace", + "secrets", + ), + command=dict(), + subscriptions=dict( + type='list', elements='str', + ), + handlers=dict( + type='list', elements='str', + ), + interval=dict( + type='int' + ), + cron=dict(), + publish=dict( + type='bool' + ), + timeout=dict( + type='int' + ), + ttl=dict( + type='int' + ), + stdin=dict( + type='bool' + ), + env_vars=dict( + type='dict' + ), + low_flap_threshold=dict( + type='int' + ), + high_flap_threshold=dict( + type='int' + ), + runtime_assets=dict( + type='list', elements='str', + ), + check_hooks=dict( + type='dict' + ), + proxy_entity_name=dict(), + proxy_requests=dict( + type='dict', + options=dict( + entity_attributes=dict( + type='list', elements='str', + ), + splay=dict( + type='bool' + ), + splay_coverage=dict( + type='int' + ) + ) + ), + output_metric_format=dict( + choices=['nagios_perfdata', 'graphite_plaintext', 'influxdb_line', 'opentsdb_line'] + ), + output_metric_handlers=dict( + type='list', elements='str', + ), + round_robin=dict( + type='bool' + ) + ) + ) + validate_module_params(module) + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'checks', module.params['name'], + ) + payload = build_api_payload(module.params) + + try: + changed, check = utils.sync( + module.params['state'], client, path, payload, module.check_mode, + do_differ, + ) + module.exit_json(changed=changed, object=check) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/check_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/check_info.py new file mode 100644 index 00000000..3bb207c9 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/check_info.py @@ -0,0 +1,97 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: check_info +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu checks +description: + - Retrieve information about Sensu Go checks. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/checks/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.check +''' + +EXAMPLES = ''' +- name: List all Sensu checks + sensu.sensu_go.check_info: + register: result + +- name: Obtain a specific check + sensu.sensu_go.check_info: + name: my-check + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu checks. + returned: success + type: list + elements: dict + sample: + - metadata: + name: check_minimum + namespace: default + command: collect.sh + handlers: + - slack + interval: 10 + publish: true + subscriptions: + - system +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "checks", module.params["name"], + ) + + try: + checks = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=checks) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/cluster.py b/ansible_collections/sensu/sensu_go/plugins/modules/cluster.py new file mode 100644 index 00000000..a75fbbe2 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/cluster.py @@ -0,0 +1,116 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2021, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: cluster +author: + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu Go clusters +description: + - Create, update or delete Sensu cluster. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/deploy-sensu/cluster-sensu/). +version_added: 1.9.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.state +seealso: + - module: sensu.sensu_go.cluster_info +options: + api_urls: + description: + - List of API urls that compose a single cluster. + - Required if I(state) is C(present). + type: list + elements: str +""" + +EXAMPLES = """ +- name: Create a small cluster + sensu.sensu_go.cluster: + name: small-cluster + api_urls: https://sensu.alpha.example.com:8080 + +- name: Create a larger cluster + sensu.sensu_go.cluster: + name: large-cluster + api_urls: + - https://sensu.alpha.example.com:8080 + - https://sensu.beta.example.com:8080 + - https://sensu.gamma.example.com:8080 + +- name: Delete a cluster + sensu.sensu_go.cluster: + name: small-cluster + state: absent +""" + +RETURN = """ +object: + description: Object representing Sensu cluster. + returned: success + type: dict + sample: + metadata: + name: alpha-cluster + api_urls: + - "http://10.10.0.1:8080" +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "federation/v1" + + +def main(): + required_if = [ + ("state", "present", ["api_urls"]), + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "name", "state"), + api_urls=dict(type="list", elements="str"), + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, "clusters", module.params["name"], + ) + + payload = dict( + type="Cluster", + api_version=API_VERSION, + metadata=dict(name=module.params["name"]), + spec=arguments.get_spec_payload(module.params, "api_urls"), + ) + try: + changed, cluster = utils.sync_v1( + module.params["state"], client, path, payload, module.check_mode, + ) + module.exit_json(changed=changed, object=cluster) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/cluster_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/cluster_info.py new file mode 100644 index 00000000..0fb64c27 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/cluster_info.py @@ -0,0 +1,101 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2021, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: cluster_info +author: + - Tadej Borovsak (@tadeboro) +short_description: List available Sensu Go clusters +description: + - Retrieve information about Sensu Go clusters. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/deploy-sensu/cluster-sensu/). +version_added: 1.9.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info +seealso: + - module: sensu.sensu_go.cluster +""" + +EXAMPLES = """ +- name: List all Sensu Go clusters + sensu.sensu_go.etcd_replicator_info: + register: result + +- name: Retrieve the selected Sensu Go cluster + sensu.sensu_go.etcd_replicator_info: + name: my-cluster + register: result + +- name: Do something with result + ansible.builtin.debug: + msg: "{{ result.objects.0.api_urls }}" +""" + +RETURN = """ +objects: + description: List of Sensu Go etcd clusters. + returned: success + type: list + elements: dict + sample: + - metadata: + name: alpha-cluster + api_urls: + - "http://10.10.0.1:8080" + - metadata: + name: beta-cluster + api_urls: + - "https://10.20.0.1:8080" + - "https://10.20.0.2:8080" +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "federation/v1" + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, "clusters", module.params["name"], + ) + + try: + clusters = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + # We simulate the behavior of v2 API here and only return the spec. + module.exit_json(changed=False, objects=[ + utils.convert_v1_to_v2_response(s) for s in clusters + ]) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role.py b/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role.py new file mode 100644 index 00000000..20aecb54 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role.py @@ -0,0 +1,164 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: cluster_role +author: + - Paul Arthur (@flowerysong) + - Manca Bizjak (@mancabizjak) + - Aljaz Kosir (@aljazkosir) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu cluster roles +description: + - Create, update or delete Sensu role. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#roles-and-cluster-roles). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.state +seealso: + - module: sensu.sensu_go.cluster_role_info + - module: sensu.sensu_go.cluster_role_binding + - module: sensu.sensu_go.role + - module: sensu.sensu_go.role_binding +options: + rules: + description: + - Rules that the cluster role applies. + - Must be non-empty if I(state) is C(present). + type: list + elements: dict + suboptions: + verbs: + description: + - Permissions to be applied by the rule. + type: list + elements: str + required: yes + choices: [get, list, create, update, delete] + resources: + description: + - Types of resources the rule has permission to access. + type: list + elements: str + required: yes + resource_names: + description: + - Names of specific resources the rule has permission to access. + - Note that for the C(create) verb, this argument will not be + taken into account when enforcing RBAC, even if it is provided. + type: list + elements: str +''' + +EXAMPLES = ''' +- name: Create a cluster role + sensu.sensu_go.cluster_role: + name: readonly + rules: + - verbs: + - get + - list + resources: + - checks + - entities + +- name: Delete a cluster role + sensu.sensu_go.cluster_role: + name: readonly + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu cluster role. + returned: success + type: dict + sample: + metadata: + name: cluster-role + rules: + - resource_names: + - sample-name + resources: + - assets + - checks + verbs: + - get + - list +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils, role_utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "name", "state"), + rules=dict( + type="list", + elements="dict", + options=dict( + verbs=dict( + required=True, + type="list", + elements="str", + choices=["get", "list", "create", "update", "delete"], + ), + resources=dict( + required=True, + type="list", + elements="str", + ), + resource_names=dict( + type="list", + elements="str", + ), + ) + ) + ) + ) + + msg = role_utils.validate_module_params(module.params) + if msg: + module.fail_json(msg=msg) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + None, "clusterroles", module.params["name"], + ) + payload = arguments.get_mutation_payload( + module.params, "rules" + ) + + try: + changed, cluster_role = utils.sync( + module.params['state'], client, path, + payload, module.check_mode, role_utils.do_roles_differ + ) + module.exit_json(changed=changed, object=cluster_role) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role_binding.py b/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role_binding.py new file mode 100644 index 00000000..de60d025 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role_binding.py @@ -0,0 +1,145 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: cluster_role_binding +author: + - Paul Arthur (@flowerysong) + - Manca Bizjak (@mancabizjak) + - Aljaz Kosir (@aljazkosir) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu cluster role bindings +description: + - Create, update or delete Sensu cluster role binding. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#role-bindings-and-cluster-role-bindings). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.state +options: + cluster_role: + description: + - Name of the cluster role. + - Required if I(state) is C(present). + type: str + users: + description: + - List of users to bind to the cluster role. + - Note that at least one of I(users) and I(groups) must be + specified when creating a cluster role binding. + type: list + elements: str + groups: + description: + - List of groups to bind to the cluster role. + - Note that at least one of I(users) and I(groups) must be + specified when creating a cluster role binding. + type: list + elements: str +seealso: + - module: sensu.sensu_go.cluster_role_binding_info + - module: sensu.sensu_go.cluster_role + - module: sensu.sensu_go.role_binding +''' + +EXAMPLES = ''' +- name: Create a cluster role binding + sensu.sensu_go.cluster_role_binding: + name: all-cluster-admins + cluster_role: cluster-admin + groups: + - cluster-admins + users: + - alice + +- name: Delete a cluster role binding + sensu.sensu_go.cluster_role_binding: + name: all-cluster-admins + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu cluster role binding. + returned: success + type: dict + sample: + metadata: + name: cluster-admin + role_ref: + name: cluster-admin + type: ClusterRole + subjects: + - name: cluster-admins + type: Group +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils, role_utils + + +def build_api_payload(params): + payload = arguments.get_mutation_payload(params) + payload["subjects"] = role_utils.build_subjects(params["groups"], params["users"]) + payload["role_ref"] = role_utils.type_name_dict("ClusterRole", params["cluster_role"]) + + return payload + + +def main(): + required_if = [ + ("state", "present", ["cluster_role"]) + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "name", "state"), + cluster_role=dict(), + users=dict( + type="list", elements="str", + ), + groups=dict( + type="list", elements="str", + ), + ) + ) + + msg = role_utils.validate_binding_module_params(module.params) + if msg: + module.fail_json(msg=msg) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + None, "clusterrolebindings", module.params["name"], + ) + payload = build_api_payload(module.params) + + try: + changed, cluster_role_binding = utils.sync( + module.params["state"], client, path, payload, module.check_mode, role_utils.do_role_bindings_differ + ) + module.exit_json(changed=changed, object=cluster_role_binding) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role_binding_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role_binding_info.py new file mode 100644 index 00000000..8d46fdc9 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role_binding_info.py @@ -0,0 +1,97 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: cluster_role_binding_info +author: + - Paul Arthur (@flowerysong) + - Manca Bizjak (@mancabizjak) + - Aljaz Kosir (@aljazkosir) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu cluster role bindings +description: + - Retrieve information about Sensu cluster role bindings. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#role-bindings-and-cluster-role-bindings). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info +seealso: + - module: sensu.sensu_go.cluster_role_binding +''' + +EXAMPLES = ''' +- name: List all Sensu cluster role bindings + sensu.sensu_go.cluster_role_binding_info: + register: result + +- name: Retrieve a specific Sensu cluster role binding + sensu.sensu_go.cluster_role_binding_info: + name: my-binding + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu cluster role bindings. + returned: success + type: list + elements: dict + sample: + - metadata: + name: cluster-admin + role_ref: + name: cluster-admin + type: ClusterRole + subjects: + - name: cluster-admins + type: Group +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth"), + name=dict() + ) + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + None, "clusterrolebindings", module.params["name"], + ) + + try: + cluster_role_bindings = utils.prepare_result_list( + utils.get(client, path) + ) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=cluster_role_bindings) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role_info.py new file mode 100644 index 00000000..e2374dff --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role_info.py @@ -0,0 +1,98 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: cluster_role_info +author: + - Paul Arthur (@flowerysong) + - Manca Bizjak (@mancabizjak) + - Aljaz Kosir (@aljazkosir) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu cluster roles +description: + - Retrieve information about Sensu roles. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#roles-and-cluster-roles). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info +seealso: + - module: sensu.sensu_go.cluster_role +''' + +EXAMPLES = ''' +- name: List all Sensu cluster roles + sensu.sensu_go.cluster_role_info: + register: result + +- name: Retrieve Sensu cluster role by name + sensu.sensu_go.cluster_role_info: + name: my-custer-role + register: result +''' + +RETURN = ''' +roles: + description: List of Sensu cluster roles. + returned: success + type: list + elements: dict + sample: + - metadata: + name: cluster-role + rules: + - resource_names: + - sample-name + resources: + - assets + - checks + verbs: + - get + - list +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth"), + name=dict() + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + None, "clusterroles", module.params["name"], + ) + + try: + cluster_roles = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=cluster_roles) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/datastore.py b/ansible_collections/sensu/sensu_go/plugins/modules/datastore.py new file mode 100644 index 00000000..706f3d1d --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/datastore.py @@ -0,0 +1,170 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: datastore +author: + - Manca Bizjak (@mancabizjak) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu external datastore providers +description: + - Add or remove external datastore provider. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/datastore/). +version_added: 1.1.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.state +seealso: + - module: sensu.sensu_go.datastore_info +options: + dsn: + description: + - Attribute that specifies the data source names as a URL or + PostgreSQL connection string. See the PostgreSQL docs for more + information about connection strings. + type: str + pool_size: + description: + - The maximum number of connections to hold in the PostgreSQL connection + pool. + type: int +notes: + - Currently, only one external datastore can be active at a time. The module + will fail to perform its operation if this would break that invariant. +''' + +EXAMPLES = ''' +- name: Add external datastore + sensu.sensu_go.datastore: + name: my-postgres + dsn: postgresql://user:secret@host:port/dbname + +- name: Remove external datastore + sensu.sensu_go.datastore: + name: my-postgres + state: absent +''' + +RETURN = ''' +object: + description: Object representing external datastore provider. + returned: success + type: dict + sample: + metadata: + name: my-postgres + batch_buffer: 0 + batch_size: 1 + batch_workers: 0 + dsn: "postgresql://user:secret@host:port/dbname" + max_conn_lifetime: 5m + max_idle_conns: 2 + pool_size: 20 + strict: true + enable_round_robin: true +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "store/v1" + + +def _get(client, path): + return utils.convert_v1_to_v2_response(utils.get(client, path)) + + +def sync(state, client, list_path, resource_path, payload, check_mode): + datastore = _get(client, resource_path) + + # When we are deleting stores, we do not care if there is more than one + # datastore present. We just make sure the currently manipulated store is + # gone. This makes our module useful in "let us clean up the mess" + # scenarios. + if state == "absent" and datastore is None: + return False, None + + if state == "absent": + if not check_mode: + utils.delete(client, resource_path) + return True, None + + # If the store exists, update it and ignore the fact that there might be + # more than one present. + if datastore: + if utils.do_differ(datastore, payload["spec"]): + if check_mode: + return True, payload["spec"] + utils.put(client, resource_path, payload) + return True, _get(client, resource_path) + return False, datastore + + # When adding a new datastore, we first make sure there is no other + # datastore present because we do not want to be the ones who brought + # backends into an inconsistent state. + if utils.get(client, list_path): + raise errors.Error("Some other external datastore is already active.") + + if check_mode: + return True, payload["spec"] + utils.put(client, resource_path, payload) + return True, _get(client, resource_path) + + +def main(): + required_if = [ + ("state", "present", ["dsn"]) + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "name", "state"), + dsn=dict(), + pool_size=dict( + type="int", + ) + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + list_path = utils.build_url_path(API_GROUP, API_VERSION, None, "provider") + resource_path = utils.build_url_path( + API_GROUP, API_VERSION, None, "provider", module.params["name"], + ) + payload = dict( + type="PostgresConfig", + api_version=API_VERSION, + metadata=dict(name=module.params["name"]), + spec=arguments.get_spec_payload(module.params, "dsn", "pool_size"), + ) + + try: + changed, datastore = sync( + module.params["state"], client, list_path, resource_path, payload, + module.check_mode, + ) + module.exit_json(changed=changed, object=datastore) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/datastore_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/datastore_info.py new file mode 100644 index 00000000..b31c6481 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/datastore_info.py @@ -0,0 +1,104 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: datastore_info +author: + - Manca Bizjak (@mancabizjak) + - Tadej Borovsak (@tadeboro) +short_description: List external Sensu datastore providers +description: + - Retrieve information about external Sensu datastores. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/datastore/). +version_added: 1.1.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info +seealso: + - module: sensu.sensu_go.datastore +""" + +EXAMPLES = """ +- name: List all external Sensu datastores + sensu.sensu_go.datastore_info: + register: result + +- name: Retrieve the selected external Sensu datastore + sensu.sensu_go.datastore_info: + name: my-datastore + register: result + +- name: Do something with result + ansible.builtin.debug: + msg: "{{ result.objects.0.dsn }}" +""" + +RETURN = """ +objects: + description: List of external Sensu datastore providers. + returned: success + type: list + elements: dict + sample: + - metadata: + name: my-postgres + batch_buffer: 0 + batch_size: 1 + batch_workers: 0 + dsn: "postgresql://user:secret@host:port/dbname" + max_conn_lifetime: 5m + max_idle_conns: 2 + pool_size: 20 + strict: true + enable_round_robin: true +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "store/v1" + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, "provider", module.params["name"], + ) + + try: + stores = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + # We simulate the behavior of v2 API here and only return the spec. + module.exit_json(changed=False, objects=[ + utils.convert_v1_to_v2_response(s) for s in stores + ]) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/entity.py b/ansible_collections/sensu/sensu_go/plugins/modules/entity.py new file mode 100644 index 00000000..e8d1562d --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/entity.py @@ -0,0 +1,261 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: entity +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu entities +description: + - Create, update or delete Sensu entity. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/entities/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state + - sensu.sensu_go.labels + - sensu.sensu_go.annotations +seealso: + - module: sensu.sensu_go.entity_info +options: + entity_class: + description: + - Entity class. Standard classes are C(proxy) and C(agent), but you can + use whatever you want. + - Required if I(state) is C(present). + type: str + subscriptions: + description: + - List of subscriptions for the entity. + type: list + elements: str + system: + description: + - System information about the entity, such as operating system and platform. See + U(https://docs.sensu.io/sensu-go/5.13/reference/entities/#system-attributes) for more information. + type: dict + last_seen: + description: + - Timestamp the entity was last seen, in seconds since the Unix epoch. + type: int + deregister: + description: + - If the entity should be removed when it stops sending keepalive messages. + type: bool + deregistration_handler: + description: + - The name of the handler to be called when an entity is deregistered. + type: str + redact: + description: + - List of items to redact from log messages. If a value is provided, + it overwrites the default list of items to be redacted. + type: list + elements: str + user: + description: + - Sensu RBAC username used by the entity. Agent entities require get, + list, create, update, and delete permissions for events across all namespaces. + type: str +''' + +EXAMPLES = ''' +- name: Create an entity + sensu.sensu_go.entity: + auth: + url: http://localhost:8080 + name: entity + entity_class: proxy + subscriptions: + - web + - prod + system: + hostname: playbook-entity + os: linux + platform: ubutntu + network: + interfaces: + - name: lo + addresses: + - 127.0.0.1/8 + - ::1/128 + - name: eth0 + mac: 52:54:00:20:1b:3c + addresses: + - 93.184.216.34/24 + last_seen: 1522798317 + deregister: yes + deregistration_handler: email-handler + redact: + - password + - pass + - api_key + user: agent + +- name: Delete an entity + sensu.sensu_go.entity: + name: entity + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu entity. + returned: success + type: dict + sample: + metadata: + annotations: null + labels: null + name: webserver01 + namespace: default + deregister: false + deregistration: {} + entity_class: agent + last_seen: 1542667231 + redact: + - password + - private_key + - secret + subscriptions: + - entity:webserver01 + system: + arch: amd64 + libc_type: glibc + vm_system: kvm + vm_role: host + cloud_provider: null + network: + interfaces: + - addresses: + - 127.0.0.1/8 + - ::1/128 + name: lo + - addresses: + - 172.28.128.3/24 + - fe80::a00:27ff:febc:be60/64 + mac: 08:00:27:bc:be:60 + name: enp0s8 + os: linux + platform: centos + platform_family: rhel + platform_version: 7.4.1708 + sensu_agent_version: 1.0.0 + user: agent +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def do_differ(current, desired): + system = desired.get('system') + if system and utils.do_differ(current.get('system'), system): + return True + + subs = desired.get('subscriptions') + if subs is not None and set(subs) != set(current.get('subscriptions', [])): + return True + + return utils.do_differ(current, desired, 'system', 'subscriptions') + + +def main(): + required_if = [ + ('state', 'present', ['entity_class']) + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec( + "auth", "name", "state", "labels", "annotations", "namespace", + ), + entity_class=dict(), + subscriptions=dict( + type='list', elements='str', + ), + system=dict( + type='dict' + ), + last_seen=dict( + type='int' + ), + deregister=dict( + type='bool' + ), + deregistration_handler=dict(), + redact=dict( + type='list', elements='str', + ), + user=dict() + ), + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'entities', module.params['name'], + ) + payload = arguments.get_mutation_payload( + module.params, 'entity_class', 'subscriptions', 'system', 'last_seen', 'deregister', + 'redact', 'user' + ) + if module.params['deregistration_handler']: + payload['deregistration'] = dict(handler=module.params['deregistration_handler']) + + # As per conversation with @echlebek, the only two supported entity + # classes are agent and proxy. All other classes can lead to undefined + # behavior and should not be used. + eclass = payload.get('entity_class') + if eclass and eclass not in ('agent', 'proxy'): + deprecation_msg = ( + 'The `entity_class` parameter should be set to either `agent` or ' + '`proxy`. All other values can result in undefined behavior of ' + 'the Sensu Go backend.' + ) + utils.deprecate(module, deprecation_msg, '2.0.0') + + # Agent entities always have entity:{entity_name} subscription enabled + # even if we pass an empty subscriptions. In order to prevent falsely + # reporting changed: true, we always add this subscription to the agent + # entities. + if eclass == 'agent': + entity_sub = 'entity:' + module.params['name'] + subs = payload.get('subscriptions', []) + if entity_sub not in subs: + # Copy subs in order to avoid mutating module params + payload['subscriptions'] = subs + [entity_sub] + + try: + changed, entity = utils.sync( + module.params['state'], client, path, payload, module.check_mode, + do_differ, + ) + module.exit_json(changed=changed, object=entity) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/entity_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/entity_info.py new file mode 100644 index 00000000..511dd237 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/entity_info.py @@ -0,0 +1,125 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: entity_info +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu entities +description: + - Retrieve information about Sensu entities. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/entities/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.entity +''' + +EXAMPLES = ''' +- name: List all Sensu entities + sensu.sensu_go.entity_info: + register: result + +- name: Retrieve a specific Sensu entity + sensu.sensu_go.entity_info: + name: my-entity + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu entities. + returned: success + type: list + elements: dict + sample: + - metadata: + annotations: null + labels: null + name: webserver01 + namespace: default + deregister: false + deregistration: {} + entity_class: agent + last_seen: 1542667231 + redact: + - password + - private_key + - secret + subscriptions: + - entity:webserver01 + system: + arch: amd64 + libc_type: glibc + vm_system: kvm + vm_role: host + cloud_provider: null + network: + interfaces: + - addresses: + - 127.0.0.1/8 + - ::1/128 + name: lo + - addresses: + - 172.28.128.3/24 + - fe80::a00:27ff:febc:be60/64 + mac: 08:00:27:bc:be:60 + name: enp0s8 + os: linux + platform: centos + platform_family: rhel + platform_version: 7.4.1708 + sensu_agent_version: 1.0.0 + user: agent +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "entities", module.params["name"], + ) + + try: + entities = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=entities) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/etcd_replicator.py b/ansible_collections/sensu/sensu_go/plugins/modules/etcd_replicator.py new file mode 100644 index 00000000..143aca8f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/etcd_replicator.py @@ -0,0 +1,218 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2021, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: etcd_replicator +author: + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu Go etcd replicators +description: + - Create, update or delete Sensu etcd replicator. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/deploy-sensu/etcdreplicators/). +version_added: 1.9.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.state +seealso: + - module: sensu.sensu_go.etcd_replicator_info +options: + ca_cert: + description: + - Path to an the PEM-format CA certificate to use for TLS client authentication. + - Required if I(insecure) is C(false). + type: str + cert: + description: + - Path to the PEM-format certificate to use for TLS client authentication. This + certificate is required for secure client communication. + - Required if I(insecure) is C(false). + type: str + key: + description: + - Path to the PEM-format key file associated with the cert to use for TLS client + authentication. This key and its corresponding certificate are required for + secure client communication. + - Required if I(insecure) is C(false). + type: str + insecure: + description: + - Disable transport security. + - Only set to C(true) in sandbox and experimental environments. + type: bool + default: false + url: + description: + - Destination cluster URLs. + - Required if I(state) is C(present). + type: list + elements: str + api_version: + description: + - Sensu API version of the resource to replicate. + type: str + resource: + description: + - Name of the resource to replicate. + - List of all resources is available at + U(https://docs.sensu.io/sensu-go/latest/operations/control-access/rbac/#resources). + - Required if I(state) is C(present). + type: str + namespace: + description: + - Namespace to constrain replication to. + - If you do not include namespace, all namespaces for a given resource are + replicated. + type: str + replication_interval: + description: + - Interval at which the resource will be replicated. In seconds. + type: int +""" + +EXAMPLES = """ +- name: Create a minimal replicator + sensu.sensu_go.etcd_replicator: + name: cluster_role_replicator + ca_cert: /etc/sensu/certs/ca.pem + cert: /etc/sensu/certs/cert.pem + key: /etc/sensu/certs/key.pem + url: https://sensu.alpha.example.com:2379 + resource: ClusterRole + +- name: Create an insecure minimal replicator + sensu.sensu_go.etcd_replicator: + name: role_replicator + insecure: true + url: + - https://sensu.beta.example.com:2379 + - https://sensu.gamma.example.com:2379 + resource: Role + +- name: Create a replicator with all parameters set + sensu.sensu_go.etcd_replicator: + name: role_binding_replicator + ca_cert: /etc/sensu/certs/ca.pem + cert: /etc/sensu/certs/cert.pem + key: /etc/sensu/certs/key.pem + insecure: false + url: https://127.0.0.1:2379 + api_version: core/v2 + resource: RoleBinding + namespace: default + replication_interval_seconds: 30 + +- name: Delete a replicator + sensu.sensu_go.etcd_replicator: + name: my_replicator + state: absent +""" + +RETURN = """ +object: + description: Object representing Sensu etcd replicator. + returned: success + type: dict + sample: + metadata: + created_by: admin + name: cluster-role-replicator + api_version: core/v2 + ca_cert: /etc/sensu/certs/ca.pem + cert: /etc/sensu/certs/cert.pem + insecure: false + key: /etc/sensu/certs/key.pem + namespace: "" + replication_interval_seconds: 30 + resource: ClusterRole + url: https://sensu.alpha.example.com:2379 +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "federation/v1" + + +def main(): + required_if = [ + ("state", "present", ["url", "resource"]), + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "name", "state"), + ca_cert=dict(type="str"), + cert=dict(type="str"), + key=dict(type="str", no_log=False), + insecure=dict(type="bool", default=False), + url=dict(type="list", elements="str"), + api_version=dict(type="str"), + resource=dict(type="str"), + namespace=dict(type="str"), + replication_interval=dict(type="int"), + ), + ) + + # This complex condition cannot be expressed using built-in checks. + if module.params["state"] == "present" and module.params["insecure"] is False: + missing = [] + for key in ("ca_cert", "cert", "key"): + if not module.params[key]: + missing.append(key) + if missing: + msg = "insecure is False but all of the following are missing: {0}".format( + ", ".join(missing) + ) + module.fail_json(msg=msg) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, "etcd-replicators", module.params["name"], + ) + + spec = arguments.get_spec_payload( + module.params, "ca_cert", "cert", "key", "insecure", "api_version", + "resource", "namespace", + ) + # We renamed the replication interval a bit. + if module.params["replication_interval"] is not None: + spec["replication_interval_seconds"] = module.params["replication_interval"] + # We accept a list of urls that we need to convert here + if module.params["url"]: + spec["url"] = ",".join(module.params["url"]) + + payload = dict( + type="EtcdReplicator", + api_version=API_VERSION, + metadata=dict(name=module.params["name"]), + spec=spec, + ) + try: + changed, replicator = utils.sync_v1( + module.params["state"], client, path, payload, module.check_mode, + ) + module.exit_json(changed=changed, object=replicator) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/etcd_replicator_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/etcd_replicator_info.py new file mode 100644 index 00000000..cf58d7c8 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/etcd_replicator_info.py @@ -0,0 +1,104 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: etcd_replicator_info +author: + - Tadej Borovsak (@tadeboro) +short_description: List available Sensu Go etcd replicators +description: + - Retrieve information about Sensu Go etcd replicators. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/deploy-sensu/etcdreplicators/). +version_added: 1.9.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info +seealso: + - module: sensu.sensu_go.etcd_replicator +""" + +EXAMPLES = """ +- name: List all Sensu Go etcd replicators + sensu.sensu_go.etcd_replicator_info: + register: result + +- name: Retrieve the selected Sensu Go etcd replicator + sensu.sensu_go.etcd_replicator_info: + name: role_replicator + register: result + +- name: Do something with result + ansible.builtin.debug: + msg: "{{ result.objects.0.resource }}" +""" + +RETURN = """ +objects: + description: List of Sensu Go etcd replicators. + returned: success + type: list + elements: dict + sample: + - metadata: + created_by: admin + name: cluster-role-replicator + api_version: core/v2 + ca_cert: /etc/sensu/certs/ca.pem + cert: /etc/sensu/certs/cert.pem + insecure: false + key: /etc/sensu/certs/key.pem + namespace: "" + replication_interval_seconds: 30 + resource: ClusterRole + url: https://sensu.alpha.example.com:2379 +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "federation/v1" + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, "etcd-replicators", module.params["name"], + ) + + try: + replicators = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + # We simulate the behavior of v2 API here and only return the spec. + module.exit_json(changed=False, objects=[ + utils.convert_v1_to_v2_response(s) for s in replicators + ]) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/event.py b/ansible_collections/sensu/sensu_go/plugins/modules/event.py new file mode 100644 index 00000000..ae2ae333 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/event.py @@ -0,0 +1,335 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: event +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu events +description: + - Send a synthetic event to Sensu. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/events/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.event_info +notes: + - Metric events bypass the store and are sent off to the event pipeline and corresponding event + handlers. Read more about this at + U(https://docs.sensu.io/sensu-go/latest/reference/events/#metric-only-events). +options: + timestamp: + description: + - UNIX time at which the event occurred. + type: int + entity: + description: + - Name of the entity associated with this event. It must exist before event creation. + type: str + required: true + check: + description: + - Name of the check associated with this event. It must exist before event creation. + type: str + required: true + check_attributes: + type: dict + description: + - Additional check parameters. Find out more at + U(https://docs.sensu.io/sensu-go/latest/reference/events/#check-attributes). + suboptions: + duration: + description: + - Command execution time in seconds. + type: float + executed: + description: + - Time that the check request was executed. + type: int + history: + description: + - Check status history for the last 21 check executions. + type: list + elements: dict + issued: + description: + - Time that the check request was issued in seconds since the Unix epoch. + type: int + last_ok: + description: + - The last time that the check returned an OK status (0) in seconds since the Unix epoch. + type: int + output: + description: + - The output from the execution of the check command. + type: str + state: + description: + - The state of the check. + choices: [ "passing", "failing", "flapping" ] + type: str + status: + description: + - Exit status code produced by the check. + choices: [ "ok", "warning", "critical", "unknown" ] + type: str + total_state_change: + description: + - The total state change percentage for the check's history. + type: int + metric_attributes: + type: dict + description: + - Metric attributes. Find out more at + U(https://docs.sensu.io/sensu-go/latest/reference/events/#metric-attributes). + suboptions: + handlers: + description: + - An array of Sensu handlers to use for events created by the check. + Each array item must be a string. + type: list + elements: str + points: + description: + - Metric data points including a name, timestamp, value, and tags. + type: list + elements: dict +''' + +EXAMPLES = ''' +- name: Create an event + sensu.sensu_go.event: + auth: + url: http://localhost:8080 + entity: awesome_entity + check: awesome_check + check_attributes: + duration: 1.945 + executed: 1522100915 + history: + - executed: 1552505193 + status: 1 + issued: 1552506034 + last_ok: 1552506033 + output: '10' + state: 'passing' + status: 'ok' + total_state_change: 0 + metric_attributes: + handlers: + - handler1 + - handler2 + points: + - name: "sensu-go-sandbox.curl_timings.time_total" + tags: + - name: "response_time_in_ms" + value: 101 + timestamp: 1552506033 + value: 0.005 + - name: "sensu-go-sandbox.curl_timings.time_namelookup" + tags: + - name: "namelookup_time_in_ms" + value: 57 + timestamp: 1552506033 + value: 0.004 +''' + +RETURN = ''' +object: + description: Object representing Sensu event (deprecated). + returned: success + type: dict + sample: + metadata: + namespace: default + check: + check_hooks: null + command: check-cpu.sh -w 75 -c 90 + duration: 1.07055808 + env_vars: null + executed: 1552594757 + handlers: [] + high_flap_threshold: 0 + history: + - executed: 1552594757 + status: 0 + interval: 60 + metadata: + name: check-cpu + namespace: default + occurrences: 1 + occurrences_watermark: 1 + output: CPU OK - Usage:3.96 + subscriptions: + - linux + timeout: 0 + total_state_change: 0 + ttl: 0 + entity: + deregister: false + deregistration: {} + entity_class: agent + last_seen: 1552594641 + metadata: + name: sensu-centos + namespace: default + timestamp: 1552594758 + id: 3a5948f3-6ffd-4ea2-a41e-334f4a72ca2f + sequence: 1 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +STATUS_MAP = { + 'ok': 0, + 'warning': 1, + 'critical': 2, + 'unknown': 3, +} + + +def get_check(client, namespace, check): + check_path = utils.build_core_v2_path(namespace, 'checks', check) + resp = client.get(check_path) + if resp.status != 200: + raise errors.SyncError("Check with name '{0}' does not exist on remote.".format(check)) + return resp.json + + +def get_entity(client, namespace, entity): + entity_path = utils.build_core_v2_path(namespace, 'entities', entity) + resp = client.get(entity_path) + if resp.status != 200: + raise errors.SyncError("Entity with name '{0}' does not exist on remote.".format(entity)) + return resp.json + + +def _update_payload_with_metric_attributes(payload, metric_attributes): + if not metric_attributes: + return + + payload['metrics'] = arguments.get_spec_payload(metric_attributes, *metric_attributes.keys()) + + +def _update_payload_with_check_attributes(payload, check_attributes): + if not check_attributes: + return + + if check_attributes['status']: + check_attributes['status'] = STATUS_MAP[check_attributes['status']] + + filtered_attributes = arguments.get_spec_payload(check_attributes, *check_attributes.keys()) + payload['check'].update(filtered_attributes) + + +def _build_api_payload(client, params): + payload = arguments.get_spec_payload(params, 'timestamp') + payload['metadata'] = dict( + namespace=params['namespace'] + ) + payload['entity'] = get_entity(client, params['namespace'], params['entity']) + payload['check'] = get_check(client, params['namespace'], params['check']) + + _update_payload_with_check_attributes(payload, params['check_attributes']) + _update_payload_with_metric_attributes(payload, params['metric_attributes']) + return payload + + +def send_event(client, path, payload, check_mode): + if not check_mode: + utils.put(client, path, payload) + return True, payload + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + timestamp=dict(type='int'), + entity=dict(required=True), + check=dict(required=True), + check_attributes=dict( + type='dict', + options=dict( + duration=dict( + type='float' + ), + executed=dict( + type='int' + ), + history=dict( + type='list', elements='dict', + ), + issued=dict( + type='int' + ), + last_ok=dict( + type='int' + ), + output=dict(), + state=dict( + choices=['passing', 'failing', 'flapping'] + ), + status=dict( + choices=['ok', 'warning', 'critical', 'unknown'] + ), + total_state_change=dict( + type='int' + ) + ) + ), + metric_attributes=dict( + type='dict', + options=dict( + handlers=dict( + type='list', + elements='str' + ), + points=dict( + type='list', + elements='dict' + ) + ) + ) + ) + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'events', module.params['entity'], + module.params['check'], + ) + + try: + payload = _build_api_payload(client, module.params) + changed, event = send_event(client, path, payload, module.check_mode) + module.exit_json(changed=changed, object=event) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/event_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/event_info.py new file mode 100644 index 00000000..90a4d84f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/event_info.py @@ -0,0 +1,142 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: event_info +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu events +description: + - Retrieve recent events that Sensu processed. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/events/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.event +options: + check: + description: + - Limit results to a specific check. + - I(entity) must also be specified if this parameter is used. + type: str + entity: + description: + - Limit results to a specific entity. + type: str +''' + +EXAMPLES = ''' +- name: List Sensu events + sensu.sensu_go.event_info: + register: result + +- name: List Sensu events for api.example.com + sensu.sensu_go.event_info: + entity: api.example.com + register: result + +- name: Filter events by check and entity + sensu.sensu_go.event_info: + entity: api.example.com + check: check-cpu + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu events. + returned: success + type: list + elements: dict + sample: + - metadata: + namespace: default + check: + check_hooks: null + command: check-cpu.sh -w 75 -c 90 + duration: 1.07055808 + env_vars: null + executed: 1552594757 + handlers: [] + high_flap_threshold: 0 + history: + - executed: 1552594757 + status: 0 + interval: 60 + metadata: + name: check-cpu + namespace: default + occurrences: 1 + occurrences_watermark: 1 + output: CPU OK - Usage:3.96 + subscriptions: + - linux + timeout: 0 + total_state_change: 0 + ttl: 0 + entity: + deregister: false + deregistration: {} + entity_class: agent + last_seen: 1552594641 + metadata: + name: sensu-centos + namespace: default + timestamp: 1552594758 + id: 3a5948f3-6ffd-4ea2-a41e-334f4a72ca2f + sequence: 1 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + required_by = {'check': ['entity']} + module = AnsibleModule( + supports_check_mode=True, + required_by=required_by, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + check=dict(), + entity=dict(), + ), + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'events', module.params['entity'], + module.params['check'], + ) + + try: + events = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=events) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/filter.py b/ansible_collections/sensu/sensu_go/plugins/modules/filter.py new file mode 100644 index 00000000..e8023b43 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/filter.py @@ -0,0 +1,158 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: filter +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu filters +description: + - Create, update or delete Sensu filter. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/filters/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state + - sensu.sensu_go.labels + - sensu.sensu_go.annotations +seealso: + - module: sensu.sensu_go.filter_info +options: + action: + description: + - Action to take with the event if the filter expressions match. + - Required if I(state) is C(present). + type: str + choices: [ 'allow', 'deny' ] + expressions: + description: + - Filter expressions to be compared with event data. + - Required if I(state) is C(present). + type: list + elements: str + runtime_assets: + description: + - Assets to be applied to the filter's execution context. + JavaScript files in the lib directory of the asset will be evaluated. + type: list + elements: str +''' + +EXAMPLES = ''' +- name: Create a filter + sensu.sensu_go.filter: + name: filter + action: deny + expressions: + - event.check.interval == 10 + - event.check.occurrences == 1 + runtime_assets: awesomeness + +- name: Create a production filter + sensu.sensu_go.filter: + name: filter + action: allow + expressions: + - event.entity.labels['environment'] == 'production' + +- name: Create a filter with JS expression + sensu.sensu_go.filter: + name: filter + action: deny + expressions: + - "_.reduce(event.check.history, function(memo, h) { return (memo || h.status != 0); })" + runtime_assets: + - underscore + +- name: Handling repeated events + sensu.sensu_go.filter: + name: filter_interval_60_hourly + action: allow + expressions: + - event.check.interval == 60 + - event.check.occurrences == 1 || event.check.occurrences % 60 == 0 + +- name: Delete a filter + sensu.sensu_go.filter: + name: filter_interval_60_hourly + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu filter. + returned: success + type: dict + sample: + metadata: + name: filter_minimum + namespace: default + action: allow + expressions: + - event.check.occurrences == 1 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + required_if = [ + ('state', 'present', ['action', 'expressions']) + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec( + "auth", "name", "state", "labels", "annotations", "namespace", + ), + action=dict(choices=['allow', 'deny']), + expressions=dict( + type='list', elements='str', + ), + runtime_assets=dict( + type='list', elements='str', + ), + ), + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'filters', module.params['name'], + ) + payload = arguments.get_mutation_payload( + module.params, 'action', 'expressions', 'runtime_assets' + ) + try: + changed, sensu_filter = utils.sync( + module.params['state'], client, path, payload, module.check_mode, + ) + module.exit_json(changed=changed, object=sensu_filter) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/filter_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/filter_info.py new file mode 100644 index 00000000..bdad1fa5 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/filter_info.py @@ -0,0 +1,93 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: filter_info +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu info +description: + - Retrieve information about Sensu filters. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/filters/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.filter +''' + +EXAMPLES = ''' +- name: List all Sensu filters + sensu.sensu_go.filter_info: + register: result + +- name: Retrieve a specific Sensu filter + sensu.sensu_go.filter_info: + name: my-filter + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu filters. + returned: success + type: list + elements: dict + sample: + - metadata: + name: filter_minimum + namespace: default + action: allow + expressions: + - event.check.occurrences == 1 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "filters", module.params["name"], + ) + + try: + sensu_filters = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=sensu_filters) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/handler_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/handler_info.py new file mode 100644 index 00000000..600de0a1 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/handler_info.py @@ -0,0 +1,94 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: handler_info +author: + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu handlers +description: + - Retrieve information about Sensu handlers. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/handlers/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.socket_handler + - module: sensu.sensu_go.pipe_handler + - module: sensu.sensu_go.handler_set +''' + +EXAMPLES = ''' +- name: List all Sensu handlers + sensu.sensu_go.handler_info: + register: result + +- name: Retrieve info for a specific Sensu handler + sensu.sensu_go.handler_info: + name: my-handler + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu handlers. + returned: success + type: list + elements: dict + sample: + - metadata: + name: tcp_udp_handler_minimum + namespace: default + socket: + host: 10.0.1.99 + port: 4444 + type: tcp +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "handlers", module.params["name"], + ) + + try: + handlers = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=handlers) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/handler_set.py b/ansible_collections/sensu/sensu_go/plugins/modules/handler_set.py new file mode 100644 index 00000000..aedc9856 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/handler_set.py @@ -0,0 +1,119 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: handler_set +author: + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu handler set +description: + - Create, update or delete Sensu handler set. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/handlers/#handler-sets). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state + - sensu.sensu_go.labels + - sensu.sensu_go.annotations +seealso: + - module: sensu.sensu_go.socket_handler + - module: sensu.sensu_go.pipe_handler + - module: sensu.sensu_go.handler_info +options: + handlers: + description: + - List of Sensu event handlers (names) to use for events using the handler set. + - Required if I(state) is C(present). + type: list + elements: str +''' + +EXAMPLES = ''' +- name: Create a handler set + sensu.sensu_go.handler_set: + name: notify_all_the_things + handlers: + - slack + - tcp_handler + - udp_handler + +- name: Delete a handler set + sensu.sensu_go.handler_set: + name: notify_all_the_things + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu handler set. + returned: success + type: dict + sample: + metadata: + name: tcp_udp_handler_minimum + namespace: default + handlers: + - slack + type: set +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + required_if = [ + ('state', 'present', ['handlers']) + ] + module = AnsibleModule( + supports_check_mode=True, + required_if=required_if, + argument_spec=dict( + arguments.get_spec( + "auth", "name", "state", "labels", "annotations", "namespace", + ), + handlers=dict( + type='list', elements='str', + ), + ), + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'handlers', module.params['name'], + ) + payload = arguments.get_mutation_payload( + module.params, 'handlers' + ) + payload['type'] = 'set' + + try: + changed, handler = utils.sync( + module.params['state'], client, path, payload, module.check_mode, + ) + module.exit_json(changed=changed, object=handler) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/hook.py b/ansible_collections/sensu/sensu_go/plugins/modules/hook.py new file mode 100644 index 00000000..b78f90da --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/hook.py @@ -0,0 +1,149 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: hook +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu hooks +description: + - Create, update or delete Sensu hook. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/hooks/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state + - sensu.sensu_go.labels + - sensu.sensu_go.annotations +seealso: + - module: sensu.sensu_go.hook_info +options: + command: + description: + - Command to run when the hook is triggered. + - Required if I(state) is C(present). + type: str + timeout: + description: + - The hook execution duration timeout in seconds (hard stop). + - Required if I(state) is C(present). + type: int + stdin: + description: + - Controls whether Sensu writes serialized JSON data to the process's stdin. + type: bool + runtime_assets: + description: + - List of runtime assets required to run the check. + type: list + elements: str +''' + +EXAMPLES = ''' +- name: Rudimentary auto-remediation hook + sensu.sensu_go.hook: + auth: + url: http://localhost:8080 + name: restart_nginx + command: sudo systemctl start nginx + timeout: 60 + stdin: false + +- name: Capture the process tree + sensu.sensu_go.hook: + auth: + url: http://localhost:8080 + name: process_tree + command: ps aux + timeout: 60 + stdin: false + +- name: Delete a hook + sensu.sensu_go.hook: + name: process_tree + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu hook. + returned: success + type: dict + sample: + metadata: + annotations: null + labels: null + name: restart_nginx + namespace: default + command: sudo systemctl start nginx + stdin: false + timeout: 60 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + required_if = [ + ('state', 'present', ['command', 'timeout']) + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec( + "auth", "name", "state", "labels", "annotations", "namespace", + ), + command=dict(), + timeout=dict( + type='int', + ), + stdin=dict( + type='bool' + ), + runtime_assets=dict( + type='list', elements='str', + ), + ), + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'hooks', module.params['name'], + ) + payload = arguments.get_mutation_payload( + module.params, 'command', 'timeout', 'stdin', 'runtime_assets' + ) + try: + changed, hook = utils.sync( + module.params['state'], client, path, payload, module.check_mode, + ) + module.exit_json(changed=changed, object=hook) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/hook_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/hook_info.py new file mode 100644 index 00000000..f576a5e4 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/hook_info.py @@ -0,0 +1,95 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: hook_info +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu hooks +description: + - Retrieve information about Sensu hooks. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/hooks/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.hook +''' + +EXAMPLES = ''' +- name: List all Sensu hooks + sensu.sensu_go.hook_info: + register: result + +- name: Fetch a specific Sensu hook + sensu.sensu_go.hook_info: + name: awesome-hook + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu hooks. + returned: success + type: list + elements: dict + sample: + - metadata: + annotations: null + labels: null + name: restart_nginx + namespace: default + command: sudo systemctl start nginx + stdin: false + timeout: 60 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "hooks", module.params["name"], + ) + + try: + hooks = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=hooks) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/ldap_auth_provider.py b/ansible_collections/sensu/sensu_go/plugins/modules/ldap_auth_provider.py new file mode 100644 index 00000000..e0ee4da0 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/ldap_auth_provider.py @@ -0,0 +1,378 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: ldap_auth_provider +author: + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Miha Dolinar (@mdolin) + - Tadej Borovsak (@tadeboro) + +short_description: Manage Sensu LDAP authentication provider + +description: + - Create, update or delete a Sensu Go LDAP authentication provider. + - For more information, refer to the Sensu Go documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/control-access/ldap-auth/). + +version_added: 1.10.0 + +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.state + +options: + servers: + description: + - An array of LDAP servers for your directory. + - Required if I(state) is C(present). + type: list + elements: dict + suboptions: + host: + description: + - LDAP server IP address. + required: true + type: str + port: + description: + - LDAP server port. + type: int + insecure: + description: + - Skips SSL certificate verification when set to true. + type: bool + default: false + security: + description: + - Encryption type to be used for the connection to the LDAP server. + type: str + choices: [ insecure, tls, starttls ] + default: tls + trusted_ca_file: + description: + - Path to an alternative CA bundle file. + type: str + client_cert_file: + description: + - Path to the certificate that should be sent to the server if requested. + type: str + client_key_file: + description: + - Path to the key file associated with the client_cert_file. + - Required if I(client_cert_file) is present. + type: str + binding: + description: + - The LDAP account that performs user and group lookups. + - If your sever supports anonymous binding, you can omit the user_dn or password + attributes to query the directory without credentials. + type: dict + suboptions: + user_dn: + description: + - The LDAP account that performs user and group lookups. + - If your sever supports anonymous binding, you can omit this attribute. + type: str + required: true + password: + description: + - Password for the user_dn account. + - If your sever supports anonymous binding, you can omit this attribute. + type: str + required: true + group_search: + description: + - Search configuration for groups. + type: dict + suboptions: + base_dn: + description: + - Which part of the directory tree to search. + required: true + type: str + attribute: + description: + - Used for comparing result entries. + type: str + default: member + name_attribute: + description: + - Represents the attribute to use as the entry name. + type: str + default: cn + object_class: + description: + - Identifies the class of objects returned in the search result. + type: str + default: groupOfNames + user_search: + description: + - Search configuration for users. + type: dict + suboptions: + base_dn: + description: + - Which part of the directory tree to search. + required: true + type: str + attribute: + description: + - Used for comparing result entries. + type: str + default: uid + name_attribute: + description: + - Represents the attribute to use as the entry name. + type: str + default: cn + object_class: + description: + - Identifies the class of objects returned in the search result. + type: str + default: person + groups_prefix: + description: + - The prefix added to all LDAP groups. + type: str + username_prefix: + description: + - The prefix added to all LDAP usernames. + type: str + +seealso: + - module: sensu.sensu_go.auth_provider_info + - module: sensu.sensu_go.ad_auth_provider + - module: sensu.sensu_go.oidc_auth_provider +""" + +EXAMPLES = """ +- name: Create a LDAP auth provider + sensu.sensu_go.ldap_auth_provider: + name: openldap + servers: + - host: 127.0.0.1 + group_search: + base_dn: dc=acme,dc=org + user_search: + base_dn: dc=acme,dc=org + +- name: Delete a LDAP auth provider + sensu.sensu_go.ldap_auth_provider: + name: openldap + state: absent +""" + +RETURN = """ +object: + description: Object representing Sensu LDAP authentication provider. + returned: success + type: dict + sample: + metadata: + name: 'openldap' + servers: + host: '127.0.0.1' + port: '636' + insecure: 'False' + security: 'tls' + trusted_ca_file: '/path/to/trusted-certificate-authorities.pem' + client_cert_file: '/path/to/ssl/cert.pem' + client_key_file: '/path/to/ssl/key.pem' + binding: + user_dn: 'cn=binder,dc=acme,dc=org' + group_search: + base_dn: 'dc=acme,dc=org' + attribute: 'member' + name_attribute': 'cn' + object_class: 'groupOfNames' + user_search: + base_dn: 'dc=acme,dc=org' + attribute: 'uid' + name_attribute: 'cn' + object_class: 'person' + groups_prefix: 'ldap' + username_prefix: 'ldap' +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "authentication/v2" + + +def remove_item(result): + if result: + for server in result["servers"]: + if server["binding"] and "password" in server["binding"]: + del server["binding"]["password"] + + return result + + +def _filter(payload): + # Remove keys with None values from dict + return dict((k, v) for k, v in payload.items() if v is not None) + + +def do_differ(current, desired): + if utils.do_differ_v1(current, desired, "servers"): + return True + + if len(current["spec"]["servers"]) != len(desired["spec"]["servers"]): + return True + + for c, d in zip(current["spec"]["servers"], desired["spec"]["servers"]): + if utils.do_differ(c, _filter(d)): + return True + + return False + + +def main(): + required_if = [("state", "present", ["servers"])] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec( + "auth", + "name", + "state", + ), + servers=dict( + type="list", + elements="dict", + options=dict( + host=dict( + type="str", + required=True, + ), + port=dict( + type="int", + ), + insecure=dict( + type="bool", + default=False, + ), + security=dict( + type="str", + choices=["insecure", "tls", "starttls"], + default="tls", + ), + trusted_ca_file=dict( + type="str", + ), + client_cert_file=dict( + type="str", + ), + client_key_file=dict( + type="str", + ), + binding=dict( + type="dict", + options=dict( + user_dn=dict( + type="str", + required=True, + ), + password=dict( + type="str", + no_log=True, + required=True, + ), + ), + ), + group_search=dict( + type="dict", + options=dict( + base_dn=dict( + type="str", + required=True, + ), + attribute=dict( + type="str", + default="member", + ), + name_attribute=dict( + type="str", + default="cn", + ), + object_class=dict(type="str", default="groupOfNames"), + ), + ), + user_search=dict( + type="dict", + options=dict( + base_dn=dict( + type="str", + required=True, + ), + attribute=dict( + type="str", + default="uid", + ), + name_attribute=dict( + type="str", + default="cn", + ), + object_class=dict( + type="str", + default="person", + ), + ), + ), + ), + ), + groups_prefix=dict( + type="str", + ), + username_prefix=dict( + type="str", + ), + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, "authproviders", module.params["name"] + ) + + payload = dict( + type="ldap", + api_version=API_VERSION, + metadata=dict(name=module.params["name"]), + spec=arguments.get_spec_payload( + module.params, "servers", "groups_prefix", "username_prefix" + ), + ) + + try: + changed, ldap_provider = utils.sync_v1( + module.params["state"], client, path, payload, module.check_mode, do_differ + ) + module.exit_json(changed=changed, object=remove_item(ldap_provider)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/mutator.py b/ansible_collections/sensu/sensu_go/plugins/modules/mutator.py new file mode 100644 index 00000000..0baf8ca8 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/mutator.py @@ -0,0 +1,154 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: mutator +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu mutators +description: + - Create, update or delete Sensu mutator. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/mutators/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state + - sensu.sensu_go.labels + - sensu.sensu_go.annotations + - sensu.sensu_go.secrets +seealso: + - module: sensu.sensu_go.mutator_info +options: + command: + description: + - The mutator command to be executed by the Sensu backend. + - Required if I(state) is C(present). + type: str + timeout: + description: + - The mutator execution duration timeout in seconds (hard stop). + type: int + env_vars: + description: + - A mapping of environment variable names and values to use with command execution. + type: dict + runtime_assets: + description: + - List of runtime assets, required to run the mutator I(command). + type: list + elements: str +''' + +EXAMPLES = ''' +- name: Create a mutator + sensu.sensu_go.mutator: + name: mutator + command: sensu-influxdb-mutator + timeout: 30 + env_vars: + INFLUXDB_ADDR: http://influxdb.default.svc.cluster.local:8086 + INFLUXDB_USER: sensu + runtime_assets: + - sensu-influxdb-mutator + +- name: Delete a mutator + sensu.sensu_go.mutator: + name: mutator + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu mutator. + returned: success + type: dict + sample: + metadata: + annotations: null + labels: null + name: example-mutator + namespace: default + command: example_mutator.go + env_vars: [] + runtime_assets: [] + timeout: 0 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def do_differ(current, desired): + return ( + utils.do_differ(current, desired, "secrets") or + utils.do_secrets_differ(current, desired) + ) + + +def main(): + required_if = [ + ('state', 'present', ['command']) + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec( + "auth", "name", "state", "labels", "annotations", "namespace", + "secrets", + ), + command=dict(), + timeout=dict( + type='int', + ), + env_vars=dict( + type='dict' + ), + runtime_assets=dict( + type='list', elements='str', + ), + ), + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'mutators', module.params['name'], + ) + payload = arguments.get_mutation_payload( + module.params, 'command', 'timeout', 'runtime_assets', 'secrets', + ) + if module.params['env_vars']: + payload['env_vars'] = utils.dict_to_key_value_strings(module.params['env_vars']) + try: + changed, mutator = utils.sync( + module.params['state'], client, path, payload, module.check_mode, + do_differ, + ) + module.exit_json(changed=changed, object=mutator) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/mutator_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/mutator_info.py new file mode 100644 index 00000000..1e247205 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/mutator_info.py @@ -0,0 +1,96 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: mutator_info +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu mutators +description: + - Retrieve information about Sensu mutators. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/mutators/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.mutator +''' + +EXAMPLES = ''' +- name: List all Sensu mutators + sensu.sensu_go.mutator_info: + register: result + +- name: Retrieve a single Sensu mutator + sensu.sensu_go.mutator_info: + name: my-mutator + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu mutators. + returned: success + type: list + elements: dict + sample: + - metadata: + annotations: null + labels: null + name: example-mutator + namespace: default + command: example_mutator.go + env_vars: [] + runtime_assets: [] + timeout: 0 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "mutators", module.params["name"], + ) + + try: + mutators = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=mutators) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/namespace.py b/ansible_collections/sensu/sensu_go/plugins/modules/namespace.py new file mode 100644 index 00000000..64d174cb --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/namespace.py @@ -0,0 +1,87 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: namespace +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu namespaces +description: + - Create, update or delete a Sensu namespace. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#namespaces). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.state +seealso: + - module: sensu.sensu_go.namespace_info +''' + +EXAMPLES = ''' +- name: Create a new namespace + sensu.sensu_go.namespace: + name: production + state: present + +- name: Delete a namespace + sensu.sensu_go.namespace: + name: staging + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu namespace. + returned: success + type: dict + sample: + name: default +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=arguments.get_spec("auth", "name", "state"), + ) + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + None, 'namespaces', module.params['name'], + ) + payload = arguments.get_spec_payload( + module.params, 'name' + ) + try: + changed, namespace = utils.sync( + module.params['state'], client, path, payload, module.check_mode, + ) + module.exit_json(changed=changed, object=namespace) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/namespace_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/namespace_info.py new file mode 100644 index 00000000..d5d0a269 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/namespace_info.py @@ -0,0 +1,79 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: namespace_info +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu namespaces +description: + - Retrieve information about Sensu namespaces. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#namespaces). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth +notes: + - Currently, it is not possible to retrieve information about a single + namespace because namespace is not much more than a name itself. +seealso: + - module: sensu.sensu_go.namespace +''' + +EXAMPLES = ''' +- name: List Sensu namespaces + sensu.sensu_go.namespace_info: + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu namespaces. + returned: success + type: list + elements: dict + sample: + - name: default +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth"), + ), + ) + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path(None, 'namespaces') + + try: + namespaces = utils.get(client, path) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=namespaces) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/oidc_auth_provider.py b/ansible_collections/sensu/sensu_go/plugins/modules/oidc_auth_provider.py new file mode 100644 index 00000000..bcd5d4ed --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/oidc_auth_provider.py @@ -0,0 +1,248 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: oidc_auth_provider + +author: + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Miha Dolinar (@mdolin) + - Tadej Borovsak (@tadeboro) + +short_description: Manage Sensu OIDC authentication provider + +description: + - Create, update or delete a Sensu Go OIDC authentication provider. + - For more information, refer to the Sensu Go documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/control-access/oidc-auth/). + +version_added: 1.10.0 + +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.state + +options: + additional_scopes: + description: + - Scopes to include in the claims. + type: list + elements: str + default: openid + client_id: + description: + - The OIDC provider application Client ID. + - Required if I(state) is C(present). + type: str + client_secret: + description: + - The OIDC provider application Client Secret. + - Required if I(state) is C(present). + type: str + disable_offline_access: + description: + - If C(true), the OIDC provider cannot include the offline_access scope + in the authentication request. Otherwise, C(false). + type: bool + default: false + redirect_uri: + description: + - Redirect URL to provide to the OIDC provider. + type: str + server: + description: + - The location of the OIDC server you wish to authenticate against. + - Required if I(state) is C(present). + type: str + groups_claim: + description: + - The claim to use to form the associated RBAC groups. + type: str + groups_prefix: + description: + - The prefix added to all OIDC groups. + type: str + username_claim: + description: + - The claim to use to form the final RBAC user name. + - Required if I(state) is C(present). + type: str + username_prefix: + description: + - The prefix added to all OIDC usernames. + type: str + +seealso: + - module: sensu.sensu_go.auth_provider_info + - module: sensu.sensu_go.ldap_auth_provider + - module: sensu.sensu_go.ad_auth_provider + +notes: + - Supported only on Sensu Go versions >= 6. +""" + +EXAMPLES = """ +- name: Create a OIDC auth provider + sensu.sensu_go.oidc_auth_provider: + state: present + name: oidc_name + additional_scopes: + - groups + - email + client_id: a8e43af034e7f2608780 + client_secret: b63968394be6ed2edb61c93847ee792f31bf6216 + disable_offline_access: false + redirect_uri: http://127.0.0.1:8080/api/enterprise/authentication/v2/oidc/callback + server: https://oidc.example.com:9031 + groups_claim: groups + groups_prefix: 'oidc:' + username_claim: email + username_prefix: 'oidc:' + +- name: Delete a OIDC auth provider + sensu.sensu_go.oidc_auth_provider: + name: oidc_name + state: absent +""" + +RETURN = """ +object: + description: Object representing Sensu OIDC authentication provider. + returned: success + type: dict + sample: + metadata: + name: 'oidc_name' + created_by: 'admin' + additional_scopes: + - 'groups' + - 'email' + client_id: 'a8e43af034e7f2608780' + disable_offline_access: false + redirect_uri: 'http://sensu-backend.example.com:8080/api/enterprise/authentication/v2/oidc/callback' + server: 'https://oidc.example.com:9031' + groups_claim: 'groups' + groups_prefix: 'oidc:' + username_claim: 'email' + username_prefix: 'oidc:' +""" + + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "authentication/v2" + + +def remove_item(result): + if result and 'client_secret' in result: + del result['client_secret'] + + return result + + +def main(): + required_if = [ + ("state", "present", ["client_id", "client_secret", "server", "username_claim"]) + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec( + "auth", + "name", + "state", + ), + additional_scopes=dict( + type="list", + elements="str", + default="openid", + ), + client_id=dict( + type="str", + ), + client_secret=dict( + type="str", + no_log=True, + ), + disable_offline_access=dict( + type="bool", + default=False, + ), + redirect_uri=dict( + type="str", + ), + server=dict( + type="str", + ), + groups_claim=dict( + type="str", + ), + groups_prefix=dict( + type="str", + ), + username_claim=dict( + type="str", + ), + username_prefix=dict( + type="str", + ), + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, "authproviders", module.params["name"] + ) + + payload = dict( + type="oidc", + api_version=API_VERSION, + metadata=dict(name=module.params["name"]), + spec=arguments.get_spec_payload( + module.params, + "additional_scopes", + "client_id", + "client_secret", + "disable_offline_access", + "redirect_uri", + "server", + "groups_claim", + "groups_prefix", + "username_claim", + "username_prefix", + ), + ) + + try: + changed, oidc_provider = utils.sync_v1( + module.params["state"], client, path, payload, module.check_mode + ) + module.exit_json( + changed=changed, object=remove_item(oidc_provider) + ) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/pipe_handler.py b/ansible_collections/sensu/sensu_go/plugins/modules/pipe_handler.py new file mode 100644 index 00000000..99e985f7 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/pipe_handler.py @@ -0,0 +1,167 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: pipe_handler +author: + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu pipe handler +description: + - Create, update or delete a Sensu pipe handler. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/handlers/#pipe-handlers). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state + - sensu.sensu_go.labels + - sensu.sensu_go.annotations + - sensu.sensu_go.secrets +seealso: + - module: sensu.sensu_go.socket_handler + - module: sensu.sensu_go.handler_info + - module: sensu.sensu_go.handler_set +options: + command: + description: + - The handler command to be executed. The event data is passed to the + process through STDIN. + - Required if I(state) is C(present). + type: str + filters: + description: + - List of filters to use when determining whether to pass the check result to this handler. + type: list + elements: str + mutator: + description: + - Mutator to call for transforming the check result before passing it to this handler. + type: str + timeout: + description: + - Timeout for handler execution. + type: int + env_vars: + description: + - A mapping of environment variable names and values to use with command execution. + type: dict + runtime_assets: + description: + - List of runtime assets to required to run the handler C(command). + type: list + elements: str +''' + +EXAMPLES = ''' +- name: Setup InfluxDB handler + sensu.sensu_go.pipe_handler: + name: influx-db + command: sensu-influxdb-handler -d sensu + env_vars: + INFLUXDB_ADDR: http://influxdb.default.svc.cluster.local:8086 + INFLUXDB_USER: sensu + INFLUXDB_PASS: password + runtime_assets: + - sensu-influxdb-handler + +- name: Delete handler + sensu.sensu_go.pipe_handler: + name: influx-db + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu pipe handler. + returned: success + type: dict + sample: + metadata: + name: pipe_handler_minimum + namespace: default + command: command-example + type: pipe +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def do_differ(current, desired): + return ( + utils.do_differ(current, desired, "secrets") or + utils.do_secrets_differ(current, desired) + ) + + +def main(): + required_if = [ + ('state', 'present', ['command']) + ] + module = AnsibleModule( + supports_check_mode=True, + required_if=required_if, + argument_spec=dict( + arguments.get_spec( + "auth", "name", "state", "labels", "annotations", "namespace", + "secrets", + ), + command=dict(), + filters=dict( + type='list', elements='str', + ), + mutator=dict(), + timeout=dict( + type='int' + ), + env_vars=dict( + type='dict' + ), + runtime_assets=dict( + type='list', elements='str', + ), + ), + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'handlers', module.params['name'], + ) + payload = arguments.get_mutation_payload( + module.params, 'command', 'filters', 'mutator', 'timeout', + 'runtime_assets', 'secrets', + ) + payload['type'] = 'pipe' + if module.params['env_vars']: + payload['env_vars'] = utils.dict_to_key_value_strings(module.params['env_vars']) + + try: + changed, handler = utils.sync( + module.params['state'], client, path, payload, module.check_mode, + do_differ, + ) + module.exit_json(changed=changed, object=handler) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/role.py b/ansible_collections/sensu/sensu_go/plugins/modules/role.py new file mode 100644 index 00000000..54f58d13 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/role.py @@ -0,0 +1,165 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: role +author: + - Paul Arthur (@flowerysong) + - Manca Bizjak (@mancabizjak) + - Aljaz Kosir (@aljazkosir) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu roles +description: + - Create, update or delete Sensu role. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#roles-and-cluster-roles). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state +seealso: + - module: sensu.sensu_go.role_info + - module: sensu.sensu_go.cluster_role + - module: sensu.sensu_go.role_binding +options: + rules: + description: + - Rules that the role applies. + - Must be non-empty if I(state) is C(present). + type: list + elements: dict + suboptions: + verbs: + description: + - Permissions to be applied by the rule. + type: list + elements: str + required: yes + choices: [get, list, create, update, delete] + resources: + description: + - Types of resources the rule has permission to access. + type: list + elements: str + required: yes + resource_names: + description: + - Names of specific resources the rule has permission to access. + - Note that for the C(create) verb, this argument will not be + taken into account when enforcing RBAC, even if it is provided. + type: list + elements: str +''' + +EXAMPLES = ''' +- name: Create a role + sensu.sensu_go.role: + name: readonly + rules: + - verbs: + - get + - list + resources: + - checks + - entities + +- name: Delete a role + sensu.sensu_go.role: + name: readonly + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu role. + returned: success + type: dict + sample: + metadata: + name: namespaced-resources-all-verbs + namespace: default + rules: + - resource_names: [] + resources: + - assets + - checks + verbs: + - create + - update + - delete +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils, role_utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "name", "state", "namespace"), + rules=dict( + type="list", + elements="dict", + options=dict( + verbs=dict( + required=True, + type="list", + elements="str", + choices=["get", "list", "create", "update", "delete"], + ), + resources=dict( + required=True, + type="list", + elements="str", + ), + resource_names=dict( + type="list", + elements="str", + ), + ) + ) + ) + ) + + msg = role_utils.validate_module_params(module.params) + if msg: + module.fail_json(msg=msg) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "roles", module.params["name"], + ) + payload = arguments.get_mutation_payload( + module.params, "rules" + ) + + try: + changed, role = utils.sync( + module.params['state'], client, path, + payload, module.check_mode, role_utils.do_roles_differ + ) + module.exit_json(changed=changed, object=role) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/role_binding.py b/ansible_collections/sensu/sensu_go/plugins/modules/role_binding.py new file mode 100644 index 00000000..0bab8b1b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/role_binding.py @@ -0,0 +1,176 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: role_binding +author: + - Paul Arthur (@flowerysong) + - Manca Bizjak (@mancabizjak) + - Aljaz Kosir (@aljazkosir) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu role bindings +description: + - Create, update or delete Sensu role binding. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#role-bindings-and-cluster-role-bindings). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state +seealso: + - module: sensu.sensu_go.role_binding_info + - module: sensu.sensu_go.role + - module: sensu.sensu_go.cluster_role + - module: sensu.sensu_go.cluster_role_binding +options: + role: + description: + - Name of the role. + - This parameter is mutually exclusive with I(cluster_role). + type: str + cluster_role: + description: + - Name of the cluster role. Note that the resulting role + binding grants the cluster role to the provided users and + groups in the context of I(auth.namespace) only. + - This parameter is mutually exclusive with I(role). + type: str + users: + description: + - List of users to bind to the role or cluster role. + - Note that at least one of I(users) and I(groups) must be + specified when creating a role binding. + type: list + elements: str + groups: + description: + - List of groups to bind to the role or cluster role. + - Note that at least one of I(users) and I(groups) must be + specified when creating a role binding. + type: list + elements: str +''' + +EXAMPLES = ''' +- name: Create a role binding + sensu.sensu_go.role_binding: + name: dev_and_testing + role: testers_permissive + groups: + - testers + - dev + - ops + users: + - alice + +- name: Create a role binding for admins + sensu.sensu_go.role_binding: + name: org-admins + cluster_role: admin + groups: + - team1-admins + - team2-admins + +- name: Delete a role binding + sensu.sensu_go.role_binding: + name: org-admins + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu role binding. + returned: success + type: dict + sample: + metadata: + name: event-reader-binding + namespace: default + role_ref: + name: event-reader + type: Role + subjects: + - name: bob + type: User +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils, role_utils + + +def infer_role(params): + if params["role"]: + return "Role", params["role"] + return "ClusterRole", params["cluster_role"] + + +def build_api_payload(params): + payload = arguments.get_mutation_payload(params) + payload["subjects"] = role_utils.build_subjects(params["groups"], params["users"]) + payload["role_ref"] = role_utils.type_name_dict(*infer_role(params)) + + return payload + + +def main(): + required_if = [ + ("state", "present", ["role", "cluster_role"], True) # True means any of role, cluster_role + ] + mutually_exclusive = [ + ["role", "cluster_role"] + ] + module = AnsibleModule( + required_if=required_if, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "name", "state", "namespace"), + role=dict(), + cluster_role=dict(), + users=dict( + type="list", elements="str", + ), + groups=dict( + type="list", elements="str", + ), + ) + ) + + msg = role_utils.validate_binding_module_params(module.params) + if msg: + module.fail_json(msg=msg) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "rolebindings", module.params["name"], + ) + payload = build_api_payload(module.params) + + try: + changed, role_binding = utils.sync( + module.params["state"], client, path, payload, module.check_mode, role_utils.do_role_bindings_differ + ) + module.exit_json(changed=changed, object=role_binding) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/role_binding_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/role_binding_info.py new file mode 100644 index 00000000..ef7d230f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/role_binding_info.py @@ -0,0 +1,97 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: role_binding_info +author: + - Paul Arthur (@flowerysong) + - Manca Bizjak (@mancabizjak) + - Aljaz Kosir (@aljazkosir) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu role bindings +description: + - Retrieve information about Sensu role bindings. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#role-bindings-and-cluster-role-bindings). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.role_binding +''' + +EXAMPLES = ''' +- name: List all Sensu role bindings + sensu.sensu_go.role_binding_info: + register: result + +- name: Retrieve a single Sensu role binding + sensu.sensu_go.role_binding_info: + name: my-role-binding + register: result +''' + +RETURN = ''' +role_bindings: + description: List of Sensu role bindings. + returned: success + type: list + elements: dict + sample: + - metadata: + name: event-reader-binding + namespace: default + role_ref: + name: event-reader + type: Role + subjects: + - name: bob + type: User +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + name=dict() + ) + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "rolebindings", module.params["name"], + ) + + try: + role_bindings = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=role_bindings) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/role_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/role_info.py new file mode 100644 index 00000000..cf2c7e8f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/role_info.py @@ -0,0 +1,100 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: role_info +author: + - Paul Arthur (@flowerysong) + - Manca Bizjak (@mancabizjak) + - Aljaz Kosir (@aljazkosir) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu roles +description: + - Retrieve information about Sensu roles. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#roles-and-cluster-roles). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.role +''' + +EXAMPLES = ''' +- name: List all Sensu roles + sensu.sensu_go.role_info: + register: result + +- name: Retrieve a specific Sensu role + sensu.sensu_go.role_info: + name: my-role + register: result +''' + +RETURN = ''' +roles: + description: List of Sensu roles. + returned: success + type: list + elements: dict + sample: + - metadata: + name: namespaced-resources-all-verbs + namespace: default + rules: + - resource_names: [] + resources: + - assets + - checks + verbs: + - create + - update + - delete +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + name=dict() + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "roles", module.params["name"], + ) + + try: + roles = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=roles) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/secret.py b/ansible_collections/sensu/sensu_go/plugins/modules/secret.py new file mode 100644 index 00000000..6b012557 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/secret.py @@ -0,0 +1,131 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: secret +author: + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Miha Dolinar (@mdolin) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu Go secrets +description: + - Create, update or delete Sensu secret. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/manage-secrets/secrets/). +version_added: 1.6.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state +seealso: + - module: sensu.sensu_go.secret_info + - module: sensu.sensu_go.secrets_provider_env + - module: sensu.sensu_go.secrets_provider_vault + - module: sensu.sensu_go.secrets_provider_info +options: + provider: + description: + - Name of the secrets provider that backs the secret value. + - Required if I(state) is C(present). + type: str + id: + description: + - Secret's id in the provider store. + - Required if I(state) is C(present). + type: str +""" + +EXAMPLES = """ +- name: Create an environment varibale-backed secret + sensu.sensu_go.secret: + name: env_secret + provider: env + id: MY_ENV_VARIABLE + +- name: Create a HashiCorp Vault-backed secret + sensu.sensu_go.secret: + name: hashi_valut_secret + provider: vault + id: secret/store#name + +- name: Delete a secret + sensu.sensu_go.secret: + name: my_secret + state: absent +""" + +RETURN = """ +object: + description: Object representing Sensu secret. + returned: success + type: dict + sample: + metadata: + name: sensu-ansible + namespace: default + id: 'secret/database#password' + provider: vault +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "secrets/v1" + + +def main(): + required_if = [ + ("state", "present", ["provider", "id"]) + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "name", "state", "namespace"), + provider=dict(type="str"), + id=dict(type="str"), + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, module.params["namespace"], "secrets", + module.params["name"], + ) + payload = dict( + type="Secret", + api_version=API_VERSION, + metadata=dict( + name=module.params["name"], + namespace=module.params["namespace"], + ), + spec=arguments.get_spec_payload(module.params, "provider", "id"), + ) + try: + changed, secret = utils.sync_v1( + module.params["state"], client, path, payload, module.check_mode, + ) + module.exit_json(changed=changed, object=secret) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/secret_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/secret_info.py new file mode 100644 index 00000000..a16ca82a --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/secret_info.py @@ -0,0 +1,110 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: secret_info +author: + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Miha Dolinar (@mdolin) + - Tadej Borovsak (@tadeboro) +short_description: List available Sensu Go secrets +description: + - Retrieve information about Sensu Go secrets. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/manage-secrets/secrets/). +version_added: 1.6.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.secret + - module: sensu.sensu_go.secrets_provider_env + - module: sensu.sensu_go.secrets_provider_vault + - module: sensu.sensu_go.secrets_provider_info +""" + +EXAMPLES = """ +- name: List all Sensu Go secrets + sensu.sensu_go.secret_info: + register: result + +- name: Retrieve the selected Sensu Go secret + sensu.sensu_go.secret_info: + name: my-secret + register: result + +- name: Do something with result + ansible.builtin.debug: + msg: "{{ result.objects.0.id }}" +""" + +RETURN = """ +objects: + description: List of Sensu Go secrets. + returned: success + type: list + elements: dict + sample: + - metadata: + name: sensu-ansible-token + namespace: default + id: ANSIBLE_TOKEN + provider: env + - metadata: + name: sensu-ansible + namespace: default + id: 'secret/database#password' + provider: vault +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "secrets/v1" + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, module.params["namespace"], "secrets", + module.params["name"], + ) + + try: + secrets = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + # We simulate the behavior of v2 API here and only return the spec. + module.exit_json(changed=False, objects=[ + utils.convert_v1_to_v2_response(s) for s in secrets + ]) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/secrets_provider_env.py b/ansible_collections/sensu/sensu_go/plugins/modules/secrets_provider_env.py new file mode 100644 index 00000000..258b6db0 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/secrets_provider_env.py @@ -0,0 +1,98 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: secrets_provider_env +author: + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Miha Dolinar (@mdolin) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu Env secrets provider +description: + - Create or delete a Sensu Go Env secrets provider. + - The module operates on a secrets provider named C(env). + - For more information, refer to the Sensu Go documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/manage-secrets/secrets-providers/). +version_added: 1.6.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.state +seealso: + - module: sensu.sensu_go.secrets_provider_vault + - module: sensu.sensu_go.secrets_provider_info + - module: sensu.sensu_go.secret + - module: sensu.sensu_go.secret_info +''' + +EXAMPLES = ''' +- name: Create the env secrets provider + sensu.sensu_go.secrets_provider_env: + +- name: Delete the env secrets provider + sensu.sensu_go.secrets_provider_env: + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu Env secrets provider. + returned: success + type: dict + sample: + - metadata: + name: env +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "secrets/v1" + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec( + "auth", "state", + ), + ) + ) + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, 'providers', 'env' + ) + payload = dict( + type="Env", + api_version=API_VERSION, + metadata=dict(name='env'), + spec={}, + ) + + try: + changed, env_provider = utils.sync_v1( + module.params['state'], client, path, payload, module.check_mode, + ) + module.exit_json(changed=changed, object=env_provider) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/secrets_provider_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/secrets_provider_info.py new file mode 100644 index 00000000..b3aa1b0e --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/secrets_provider_info.py @@ -0,0 +1,111 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: secrets_provider_info +author: + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Miha Dolinar (@mdolin) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu secrets providers +description: + - Retrieve information about Sensu Go secrets providers. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/manage-secrets/secrets-providers/). +version_added: 1.6.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info +seealso: + - module: sensu.sensu_go.secrets_provider_env + - module: sensu.sensu_go.secrets_provider_vault + - module: sensu.sensu_go.secret + - module: sensu.sensu_go.secret_info +""" + +EXAMPLES = """ +- name: List all Sensu secrets providers + sensu.sensu_go.secrets_provider_info: + register: result + +- name: List the selected Sensu secrets provider + sensu.sensu_go.secrets_provider_info: + name: my_provider + register: result + +- name: Do something with result + ansible.builtin.debug: + msg: "{{ result.objects.0.metadata.name }}" +""" + +RETURN = """ +objects: + description: List of Sensu secrets providers. + returned: success + type: list + elements: dict + sample: + - metadata: + name: vault + client: + address: https://vaultserver.example.com:8200 + token: VAULT_TOKEN + version: v1 + tls: + ca_cert: "/etc/ssl/certs/vault_ca_cert.pem" + max_retries: 2 + timeout: 20s + rate_limiter: + limit: 10 + burst: 100 +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "secrets/v1" + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, "providers", module.params["name"], + ) + + try: + providers = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + # We simulate the behavior of v2 API here and only return the spec. + module.exit_json(changed=False, objects=[ + utils.convert_v1_to_v2_response(p) for p in providers + ]) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/secrets_provider_vault.py b/ansible_collections/sensu/sensu_go/plugins/modules/secrets_provider_vault.py new file mode 100644 index 00000000..2df0a310 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/secrets_provider_vault.py @@ -0,0 +1,258 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: secrets_provider_vault +author: + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Miha Dolinar (@mdolin) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu VaultProvider secrets providers +description: + - Create, update or delete a Sensu Go VaultProvider secrets provider. + - For more information, refer to the Sensu Go documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/manage-secrets/secrets-providers/). +version_added: 1.6.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.state +options: + address: + description: + - Address of the Vault server. + - Required if I(state) is C(present). + type: str + token: + description: + - Authentication token to use with Vault. + - Required if I(state) is C(present). + type: str + version: + description: + - Version of the Vault key/value store. + - Please refer to U(https://www.vaultproject.io/docs/secrets/kv) for + additional information. + - Required if I(state) is C(present). + type: str + choices: [v1, v2] + timeout: + description: + - Timeout (in seconds) for connection to Vault server. + type: int + max_retries: + description: + - Maximum number of times to retry failed connections to Vault server. + type: int + rate_limit: + description: + - Maximum number of secrets requests for per second. + type: float + burst_limit: + description: + - Maximum allowed number of secrets requests in a rate interval. + type: int + tls: + description: + - TLS configuration for establishing connection with Vault server. + type: dict + suboptions: + ca_cert: + description: + - Path to the certificate file of the trusted certificate authority. + type: str + client_cert: + description: + - Path to the client certificate file. + type: str + client_key: + description: + - Path to the client key file. + type: str + cname: + description: + - Canonical name for the client. + type: str + +seealso: + - module: sensu.sensu_go.secrets_provider_env + - module: sensu.sensu_go.secrets_provider_info + - module: sensu.sensu_go.secret + - module: sensu.sensu_go.secret_info +''' + +EXAMPLES = ''' +- name: Create a vault secrets provider + sensu.sensu_go.secrets_provider_vault: + name: my-vault + address: https://my-vault.com + token: VAULT_TOKEN + version: v1 + +- name: Delete a vault secrets provider + sensu.sensu_go.secrets_provider_vault: + name: my-vault + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu vault secrets provider. + returned: success + type: dict + sample: + metadata: + name: vault + client: + address: https://vaultserver.example.com:8200 + token: VAULT_TOKEN + version: v1 + tls: + ca_cert: "/etc/ssl/certs/vault_ca_cert.pem" + max_retries: 2 + timeout: 20s + rate_limiter: + limit: 10 + burst: 100 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "secrets/v1" + + +def do_differ(current, desired): + if utils.do_differ_v1(current, desired, "client"): + return True + + current_client = current["spec"]["client"] + desired_client = desired["spec"]["client"] + # Sensu Go API returns 'agent_address' field in the client spec, + # but this field is not meant to be set via the providers API. + if utils.do_differ(current_client, desired_client, "agent_address", "tls"): + return True + # Sensu Go API returns some extra fields in the tls spec. + # We ignore them, as they are not meant to be set via the + # providers API. + return utils.do_differ(current_client["tls"], desired_client.get("tls") or {}, + "insecure", "tls_server_name", "ca_path") + + +def _format_seconds(seconds): + # Sensu API returns the configured timeout as a string, for instance + # 30 -> '30s', 60-> '1m0s', 3600 -> '1h0m0s'. + h, r = divmod(seconds, 3600) + m, s = divmod(r, 60) + if h: + return "{0}h{1}m{2}s".format(h, m, s) + if m: + return "{0}m{1}s".format(m, s) + return "{0}s".format(seconds) + + +def build_vault_provider_spec(params): + if params["state"] == "absent": + return {} + + client = arguments.get_spec_payload( + params, "address", "token", "version", "max_retries", + ) + if params.get("tls"): + client["tls"] = arguments.get_spec_payload( + params["tls"], "ca_cert", "client_cert", "client_key", "cname", + ) + if params.get("timeout"): + client["timeout"] = _format_seconds(params["timeout"]) + + if params.get("rate_limit") or params.get("burst_limit"): + client["rate_limiter"] = arguments.get_renamed_spec_payload( + params, dict( + rate_limit="limit", + burst_limit="burst", + ) + ) + + return dict(client=client) + + +def main(): + required_if = [ + ("state", "present", ["address", "token", "version"]) + ] + + module = AnsibleModule( + supports_check_mode=True, + required_if=required_if, + argument_spec=dict( + arguments.get_spec( + "auth", "name", "state", + ), + address=dict(), + token=dict(no_log=True), + version=dict( + choices=["v1", "v2"], + ), + timeout=dict( + type="int", + ), + max_retries=dict( + type="int", + ), + rate_limit=dict( + type="float", + ), + burst_limit=dict( + type="int", + ), + tls=dict( + type="dict", + options=dict( + ca_cert=dict(), + cname=dict(), + client_cert=dict(), + client_key=dict(no_log=False), + ) + ) + ) + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, 'providers', module.params['name'] + ) + + payload = dict( + type="VaultProvider", + api_version=API_VERSION, + metadata=dict(name=module.params["name"]), + spec=build_vault_provider_spec(module.params) + ) + + try: + changed, vault_provider = utils.sync_v1( + module.params['state'], client, path, payload, module.check_mode, do_differ + ) + module.exit_json(changed=changed, object=vault_provider) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/silence.py b/ansible_collections/sensu/sensu_go/plugins/modules/silence.py new file mode 100644 index 00000000..4b287911 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/silence.py @@ -0,0 +1,172 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: silence +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu silences +description: + - Create, update or delete Sensu silence. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/silencing/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.namespace + - sensu.sensu_go.state + - sensu.sensu_go.labels + - sensu.sensu_go.annotations +seealso: + - module: sensu.sensu_go.silence_info +options: + subscription: + description: + - The name of the subscription the entry should match. + - If left empty a silencing entry will contain an asterisk in the + subscription position. This indicates that any event with a matching + check name will be marked as silenced, regardless of the originating + entities subscriptions. + - Specific entity can also be targeted by taking advantage of per-entity + subscription (entity:<entity_name>). + - This parameter is required if the I(check) parameter is absent. + type: str + check: + description: + - The name of the check the entry should match. + - If left empty a silencing entry will contain an asterisk in the check + position. This indicates that any event where the originating entities + subscriptions match the subscription specified in the entry will be + marked as silenced, regardless of the check name. + - This parameter is required if the I(subscription) parameter is absent. + type: str + begin: + description: + - UNIX time at which silence entry goes into effect. + type: int + expire: + description: + - Number of seconds until the silence expires. + type: int + expire_on_resolve: + description: + - If the entry should be deleted when a check begins return OK status (resolves). + type: bool + reason: + description: + - Reason for silencing. + type: str +''' + +EXAMPLES = ''' +- name: Silence a specific check + sensu.sensu_go.silence: + subscription: proxy + check: check-disk + +- name: Silence specific check regardless of the originating entities subscription + sensu.sensu_go.silence: + check: check-cpu + +- name: Silence all checks on a specific entity + sensu.sensu_go.silence: + subscription: entity:important-entity + expire: 120 + reason: rebooting the world + +- name: Delete a silencing entry + sensu.sensu_go.silence: + subscription: entity:important-entity + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu silence. + returned: success + type: dict + sample: + metadata: + annotations: null + labels: null + name: entity:i-424242:* + namespace: default + begin: 1542671205 + check: null + creator: admin + expire: -1 + expire_on_resolve: false + reason: null + subscription: entity:i-424242 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + required_one_of = [ + ['subscription', 'check'] + ] + module = AnsibleModule( + supports_check_mode=True, + required_one_of=required_one_of, + argument_spec=dict( + arguments.get_spec( + 'auth', 'state', 'labels', 'annotations', 'namespace', + ), + subscription=dict(), + check=dict(), + begin=dict( + type='int', + ), + expire=dict( + type='int', + ), + expire_on_resolve=dict( + type='bool' + ), + reason=dict() + ), + ) + name = '{0}:{1}'.format(module.params['subscription'] or '*', module.params['check'] or '*') + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'silenced', name, + ) + # We add name parameter because it is actually required and must match the name that is + # autogenerated on the API + module.params['name'] = name + payload = arguments.get_mutation_payload( + module.params, 'subscription', 'check', 'begin', 'expire', 'expire_on_resolve', 'reason' + ) + try: + changed, silence = utils.sync( + module.params['state'], client, path, payload, module.check_mode, + ) + module.exit_json(changed=changed, object=silence) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/silence_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/silence_info.py new file mode 100644 index 00000000..68769902 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/silence_info.py @@ -0,0 +1,112 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: silence_info +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu silence entries +description: + - Retrieve information about Sensu silences. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/silencing/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.silence +options: + subscription: + description: + - The name of the subscription the entry should match. If left empty a silencing entry will + contain an asterisk in the subscription position. + type: str + check: + description: + - The name of the check the entry should match. If left empty a silencing entry will contain an + asterisk in the check position. + type: str +''' + +EXAMPLES = ''' +- name: List all Sensu silence entries + sensu.sensu_go.silence_info: + register: result + +- name: Fetch a specific silence with name proxy:awesome_check + sensu.sensu_go.silence_info: + subscription: proxy + check: awesome_check + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu silence entries. + returned: success + type: list + elements: dict + sample: + - metadata: + annotations: null + labels: null + name: entity:i-424242:* + namespace: default + begin: 1542671205 + check: null + creator: admin + expire: -1 + expire_on_resolve: false + reason: null + subscription: entity:i-424242 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec('auth', 'namespace'), + subscription=dict(), + check=dict(), + ), + ) + + name = '{0}:{1}'.format(module.params['subscription'] or '*', module.params['check'] or '*') + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "silenced", None if name == "*:*" else name, + ) + + try: + silences = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=silences) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/socket_handler.py b/ansible_collections/sensu/sensu_go/plugins/modules/socket_handler.py new file mode 100644 index 00000000..ac42d27c --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/socket_handler.py @@ -0,0 +1,160 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: socket_handler +author: + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu TCP/UDP handler +description: + - Create, update or delete Sensu socket handler. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/handlers/#tcp-udp-handlers). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state + - sensu.sensu_go.labels + - sensu.sensu_go.annotations +seealso: + - module: sensu.sensu_go.handler_info + - module: sensu.sensu_go.pipe_handler + - module: sensu.sensu_go.handler_set +options: + type: + description: + - The handler type. + - Required if I(state) is C(present). + choices: + - tcp + - udp + type: str + filters: + description: + - List of filters to use when determining whether to pass the check result to this handler. + type: list + elements: str + mutator: + description: + - Mutator to call for transforming the check result before passing it to this handler. + type: str + timeout: + description: + - Timeout for handler execution. + type: int + host: + description: + - The socket host address (IP or hostname) to connect to. + - Required if I(state) is C(present). + type: str + port: + description: + - The socket port to connect to. + - Required if I(state) is C(present). + type: int +''' + +EXAMPLES = ''' +- name: TCP handler + sensu.sensu_go.socket_handler: + name: tcp_handler + type: tcp + host: 10.0.1.99 + port: 4444 + +- name: UDP handler + sensu.sensu_go.socket_handler: + name: udp_handler + type: udp + host: 10.0.1.99 + port: 4444 + +- name: Delete a handler + sensu.sensu_go.socket_handler: + name: udp_handler + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu socket handler. + returned: success + type: dict + sample: + - metadata: + name: udp_handler + namespace: default + socket: + host: 10.0.1.99 + port: 4444 + type: udp +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + required_if = [ + ('state', 'present', ['type', 'host', 'port']) + ] + module = AnsibleModule( + supports_check_mode=True, + required_if=required_if, + argument_spec=dict( + arguments.get_spec( + "auth", "name", "state", "labels", "annotations", "namespace", + ), + type=dict(choices=['tcp', 'udp']), + filters=dict( + type='list', elements='str', + ), + mutator=dict(), + timeout=dict( + type='int' + ), + host=dict(), + port=dict( + type='int' + ) + ), + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'handlers', module.params['name'], + ) + payload = arguments.get_mutation_payload( + module.params, 'type', 'filters', 'mutator', 'timeout' + ) + payload['socket'] = dict(host=module.params['host'], port=module.params['port']) + + try: + changed, handler = utils.sync( + module.params['state'], client, path, payload, module.check_mode, + ) + module.exit_json(changed=changed, object=handler) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/tessen.py b/ansible_collections/sensu/sensu_go/plugins/modules/tessen.py new file mode 100644 index 00000000..ba18f518 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/tessen.py @@ -0,0 +1,108 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: tessen +author: + - Paul Arthur (@flowerysong) + - Manca Bizjak (@mancabizjak) + - Aljaz Kosir (@aljazkosir) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu's Tessen configuration +description: + - Enable or disable Tessen service. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/tessen/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth +options: + state: + description: + - Enable or disable sending anonymized data to Sensu Inc. + choices: [ enabled, disabled ] + type: str + required: True +''' + +EXAMPLES = ''' +- name: Disable Tessen + sensu.sensu_go.tessen: + state: disabled + register: result +''' + +RETURN = ''' +object: + description: Object representing Sensu tessen. + returned: success + type: dict + sample: + opt_out: false +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def get(client, path): + resp = client.get(path) + if resp.status != 200: + raise errors.SyncError( + "GET {0} failed with status {1}: {2}".format(path, resp.status, resp.data)) + return resp.json + + +def sync(client, path, payload, check_mode): + remote_object = get(client, path) + + if utils.do_differ(remote_object, payload): + if check_mode: + return True, payload + utils.put(client, path, payload) + return True, get(client, path) + + return False, remote_object + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec('auth'), + state=dict( + choices=['enabled', 'disabled'], + required=True, + ) + ) + ) + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path(None, 'tessen') + payload = dict( + opt_out=module.params['state'] == 'disabled' + ) + + try: + changed, tessen = sync(client, path, payload, module.check_mode) + module.exit_json(changed=changed, object=tessen) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/user.py b/ansible_collections/sensu/sensu_go/plugins/modules/user.py new file mode 100644 index 00000000..4ef15586 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/user.py @@ -0,0 +1,326 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: user +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu users +description: + - Create, update, activate or deactivate Sensu user. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#users). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name +requirements: + - bcrypt (when managing Sensu Go 5.21.0 or newer) +seealso: + - module: sensu.sensu_go.user_info +options: + state: + description: + - Desired state of the user. + - Users cannot actually be deleted, only deactivated. + type: str + choices: [ enabled, disabled ] + default: enabled + password: + description: + - Password for the user. + - Required if user with a desired name does not exist yet on the backend + and I(password_hash) is not set. + - If both I(password) and I(password_hash) are set, I(password_hash) is + ignored and calculated from the I(password) if required. + type: str + password_hash: + description: + - Bcrypt password hash for the user. + - Use C(sensuctl user hash-password PASSWORD) to generate a hash. + - Required if user with a desired name does not exist yet on the backend + and I(password) is not set. + - If both I(password) and I(password_hash) are set, I(password_hash) is + ignored and calculated from the I(password) if required. + - Sensu Go < 5.21.0 does not support creating/updating users using + hashed passwords. Use I(password) parameter if you need to manage such + Sensu Go installations. + - At the moment, change detection does not work properly when using + password hashes because the Sensu Go backend does not expose enough + information via its API. + type: str + version_added: 1.8.0 + groups: + description: + - List of groups user belongs to. + type: list + elements: str +''' + +EXAMPLES = ''' +- name: Create a user + sensu.sensu_go.user: + auth: + url: http://localhost:8080 + name: awesome_username + password: hidden_password? + groups: + - dev + - prod + +- name: Use pre-hashed password + sensu.sensu_go.user: + auth: + url: http://localhost:8080 + name: awesome_username + password_hash: $5f$14$.brXRviMZpbaleSq9kjoUuwm67V/s4IziOLGHjEqxJbzPsreQAyNm + +- name: Deactivate a user + sensu.sensu_go.user: + name: awesome_username + state: disabled +''' + +RETURN = ''' +object: + description: Object representing Sensu user. + returned: success + type: dict + sample: + disabled: false + groups: + - ops + - dev + password: USER_PASSWORD + password_hash: $5f$14$.brXRviMZpbaleSq9kjoUuwm67V/s4IziOLGHjEqxJbzPsreQAyNm + username: alice +''' + +import traceback + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib + +from ..module_utils import arguments, errors, utils + +try: + import bcrypt + HAS_BCRYPT = True + BCRYPT_IMPORT_ERROR = None +except ImportError: + HAS_BCRYPT = False + BCRYPT_IMPORT_ERROR = traceback.format_exc() + + +def _simulate_backend_response(payload): + # Backend does not return back any password-related information for now. + masked_keys = ('password', 'password_hash') + return dict( + (k, v) for k, v in payload.items() if k not in masked_keys + ) + + +def update_password(client, path, username, password, check_mode): + # Hit the auth testing API and try to validate the credentials. If the API + # says they are invalid, we need to update them. + if client.validate_auth_data(username, password): + return False + + if not check_mode: + if client.version < "5.21.0": + utils.put(client, path + '/password', dict( + username=username, password=password, + )) + else: + hash = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()) + utils.put(client, path + '/reset_password', dict( + username=username, password_hash=hash.decode('ascii'), + )) + + return True + + +def update_password_hash(client, path, username, password_hash, check_mode): + # Some older Sensu Go versions do not have support for password hashes. + if client.version < "5.21.0": + raise errors.SensuError( + "Sensu Go < 5.21.0 does not support password hashes" + ) + + # Insert change detection here once we can receive password hash from the + # backend. Up until then, we always update passwords. + + if not check_mode: + utils.put(client, path + '/reset_password', dict( + username=username, password_hash=password_hash, + )) + + return True + + +def update_groups(client, path, old_groups, new_groups, check_mode): + to_delete = set(old_groups).difference(new_groups) + to_add = set(new_groups).difference(old_groups) + + if not check_mode: + # Next few lines are far from atomic, which means that we can leave a + # user in any of the intermediate states, but this is the best we can + # do given the API limitations. + for g in to_add: + utils.put(client, path + '/groups/' + g, None) + for g in to_delete: + utils.delete(client, path + '/groups/' + g) + + return len(to_delete) + len(to_add) > 0 + + +def update_state(client, path, old_disabled, new_disabled, check_mode): + changed = old_disabled != new_disabled + + if not check_mode and changed: + if new_disabled: # `state: disabled` input parameter + utils.delete(client, path) + else: # `state: enabled` input parameter + utils.put(client, path + '/reinstate', None) + + return changed + + +def sync(remote_object, client, path, payload, check_mode): + # Create new user (either enabled or disabled) + if remote_object is None: + if check_mode: + return True, _simulate_backend_response(payload) + utils.put(client, path, payload) + return True, utils.get(client, path) + + # Update existing user. We do this on a field-by-field basis because the + # upsteam API for updating users requires a password field to be set. Of + # course, we do not want to force users to specify an existing password + # just for the sake of updating the group membership, so this is why we + # use field-specific API endpoints to update the user data. + + changed = False + + # We only use password hash if we do not have a password. In practice, + # this means that users should not set both password and password_hash. We + # do not enforce this by making those two parameters mutually exclusive + # because in the future (2.0.0 version of collection), we intend to move + # password hashing into action plugin and supply both the password and its + # hash. Why? Because installing bcrypt on control node is way friendlier + # compared to installing bcrypt on every host that runs our user module. + # + # It is true that most of the time, control node == target node in our + # cases, but not always. + if 'password' in payload: + changed = update_password( + client, path, payload['username'], payload['password'], + check_mode, + ) or changed + elif 'password_hash' in payload: + changed = update_password_hash( + client, path, payload['username'], payload['password_hash'], + check_mode, + ) or changed + + if 'groups' in payload: + changed = update_groups( + client, path, remote_object.get('groups') or [], + payload['groups'], check_mode, + ) or changed + + if 'disabled' in payload: + changed = update_state( + client, path, remote_object['disabled'], payload['disabled'], + check_mode, + ) or changed + + if check_mode: + # Backend does not return back passwords, so we should follow the + # example set by the backend API. + return changed, dict( + remote_object, **_simulate_backend_response(payload) + ) + + return changed, utils.get(client, path) + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "name"), + state=dict( + default='enabled', + choices=['enabled', 'disabled'], + ), + password=dict( + no_log=True + ), + password_hash=dict( + no_log=False, # Showing hashes is perfectly OK + ), + groups=dict( + type='list', elements='str', + ) + ), + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path(None, 'users', module.params['name']) + + try: + if not HAS_BCRYPT and client.version >= "5.21.0": + module.fail_json( + msg=missing_required_lib('bcrypt'), + exception=BCRYPT_IMPORT_ERROR, + ) + except errors.SensuError as e: + module.fail_json(msg=str(e)) + + try: + remote_object = utils.get(client, path) + except errors.Error as e: + module.fail_json(msg=str(e)) + + if ( + remote_object is None + and module.params['password'] is None + and module.params['password_hash'] is None + ): + module.fail_json( + msg='Cannot create new user without a password or a hash' + ) + + payload = arguments.get_spec_payload( + module.params, 'password', 'password_hash', 'groups', + ) + payload['username'] = module.params['name'] + payload['disabled'] = module.params['state'] == 'disabled' + + try: + changed, user = sync( + remote_object, client, path, payload, module.check_mode + ) + module.exit_json(changed=changed, object=user) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/user_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/user_info.py new file mode 100644 index 00000000..ad9eda8f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/user_info.py @@ -0,0 +1,90 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur <paul.arthur@flowerysong.com> +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: user_info +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu users +description: + - Retrieve information about Sensu users. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#users). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info +seealso: + - module: sensu.sensu_go.user +''' + +EXAMPLES = ''' +- name: List Sensu users + sensu.sensu_go.user_info: + register: result + +- name: Retrieve a single Sensu user + sensu.sensu_go.user_info: + name: my-user + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu users. + returned: success + type: list + elements: dict + sample: + - disabled: false + groups: + - ops + - dev + password: USER_PASSWORD + password_hash: $5f$14$.brXRviMZpbaleSq9kjoUuwm67V/s4IziOLGHjEqxJbzPsreQAyNm + username: alice +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth"), + name=dict(), # Name is not required in info modules. + ), + ) + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path(None, "users", module.params["name"]) + + try: + users = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=users) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/pytest.ini b/ansible_collections/sensu/sensu_go/pytest.ini new file mode 100644 index 00000000..36542343 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +junit_family = xunit1 diff --git a/ansible_collections/sensu/sensu_go/roles/agent/README.md b/ansible_collections/sensu/sensu_go/roles/agent/README.md new file mode 100644 index 00000000..24d817ab --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/agent/README.md @@ -0,0 +1,5 @@ +# sensu.sensu_go.agent role + +Visit [the official documentation site][docs] for role documentation. + + [docs]: https://sensu.github.io/sensu-go-ansible/roles/agent.html diff --git a/ansible_collections/sensu/sensu_go/roles/agent/defaults/main.yml b/ansible_collections/sensu/sensu_go/roles/agent/defaults/main.yml new file mode 100644 index 00000000..a6238675 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/agent/defaults/main.yml @@ -0,0 +1,5 @@ +--- +# Related to /etc/sensu/backend.yml, see +# https://docs.sensu.io/sensu-go/latest/reference/agent/#configuration-summary +agent_backend_urls: "{{ hostvars | sensu.sensu_go.backends(groups) }}" +agent_config: diff --git a/ansible_collections/sensu/sensu_go/roles/agent/handlers/main.yml b/ansible_collections/sensu/sensu_go/roles/agent/handlers/main.yml new file mode 100644 index 00000000..1921322c --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/agent/handlers/main.yml @@ -0,0 +1,35 @@ +--- +- name: Restart Linux agent + service: + name: sensu-agent + state: restarted + when: manage_sensu_agent_service | default(False) + +- name: Restart Windows agent + action: + module: win_service + name: SensuAgent + state: restarted + when: manage_sensu_agent_service | default(False) + +# You probably noticed that we use some black magic in the previous handler. +# Let us explain what it does and why did we bring it into the daylight. +# +# Under normal circumstances, we would write the previous handler as +# +# - name: Restart Windows agent +# win_service: +# name: SensuAgent +# state: restarted +# +# When Ansible loads this handler, it makes sure it can find the win_service +# module. And this is where things start to go downhill for us. Because we do +# not have a guarantee that win_service module will be available (win_service +# is not part of a certified collection), this eagerness prevents operating in +# certain situations where win_service module is not even needed. +# +# And this is why we use the alternative form that forces Ansible to lazy-load +# the module at the task/handler execution time. +# +# Of course, it would be much easier if we could declare ansible.windows as our +# dependency, but this is not possible at the moment. diff --git a/ansible_collections/sensu/sensu_go/roles/agent/meta/argument_specs.yml b/ansible_collections/sensu/sensu_go/roles/agent/meta/argument_specs.yml new file mode 100644 index 00000000..57e23a60 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/agent/meta/argument_specs.yml @@ -0,0 +1,58 @@ +argument_specs: + + configure: + short_description: Configure Sensu Go agent + description: + - Write the Sensu Go agent configuration file. + + options: + agent_config: &agent_config + description: + - Any option that is valid for the Sensu Go agent version we are + installing. + - All valid options are listed at + U(https://docs.sensu.io/sensu-go/latest/reference/agent/#configuration). + - Role copies the key-value pairs from the I(agent_config) variable + verbatim to the configuration file. This means that we must copy + the key names B(exactly) as they appear in the configuration + reference. In a way, the I(agent_config) variable should contain a + properly indented copy of the C(/etc/sensu/agent.yml) file. + type: dict + + start: + short_description: Start Sensu Go agent + description: + - Start the Sensu Go agent service. + + main: + short_description: Install, configure, and start Sensu Go agent + description: + - Install, configure, and start the Sensu Go agent service. + + options: + channel: + description: + - Repository channel that serves as a source of packages. + - Visit the packagecloud site to find all available channels. + type: str + default: stable + + version: &version + description: + - Package version to install. + - Can be any valid version string such as C(6.2.5) or special value + C(latest). + type: str + default: latest + + build: + description: + - Package build to install. + - Can be any valid build string such as C(8290) or a special value + latest. + - If the I(version) variable is set to latest, this variable is + ignored and the latest available build is installed. + type: str + default: latest + + agent_config: *agent_config diff --git a/ansible_collections/sensu/sensu_go/roles/agent/meta/main.yml b/ansible_collections/sensu/sensu_go/roles/agent/meta/main.yml new file mode 100644 index 00000000..11d106c6 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/agent/meta/main.yml @@ -0,0 +1,24 @@ +galaxy_info: + author: XLAB Steampunk <steampunk@xlab.si> + description: Configure Sensu Go agent + license: GPL-3.0-or-later + min_ansible_version: 2.8 + + platforms: + - name: EL + versions: + - "7" + - "8" + - name: Ubuntu + versions: + - trusty + - xenial + - bionic + - disco + - name: Debian + versions: + - stretch + - buster + + galaxy_tags: + - sensu diff --git a/ansible_collections/sensu/sensu_go/roles/agent/tasks/configure.yml b/ansible_collections/sensu/sensu_go/roles/agent/tasks/configure.yml new file mode 100644 index 00000000..5c1e35ea --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/agent/tasks/configure.yml @@ -0,0 +1,8 @@ +--- +- name: Configure agent (Linux) + include_tasks: linux/configure.yml + when: ansible_facts.os_family != "Windows" + +- name: Configure agent (Windows) + include_tasks: windows/configure.yml + when: ansible_facts.os_family == "Windows" diff --git a/ansible_collections/sensu/sensu_go/roles/agent/tasks/linux/configure.yml b/ansible_collections/sensu/sensu_go/roles/agent/tasks/linux/configure.yml new file mode 100644 index 00000000..4a3db7d9 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/agent/tasks/linux/configure.yml @@ -0,0 +1,22 @@ +--- +- name: Load Linux locations + include_vars: linux.yml + +- name: Install agent communication PKI + copy: + src: "{{ agent_trusted_ca_file }}" + dest: "{{ trusted_ca_file_path }}" + # Keep this in sync with what the backend service is running as from packager + owner: &sensu_user sensu + group: &sensu_group sensu + mode: "0644" + when: agent_trusted_ca_file is defined + +- name: Configure sensu-agent ({{ agent_config_path }}) + template: + src: agent.yml.j2 + dest: "{{ agent_config_path }}" + owner: *sensu_user + group: *sensu_group + mode: '0600' + notify: Restart Linux agent diff --git a/ansible_collections/sensu/sensu_go/roles/agent/tasks/main.yml b/ansible_collections/sensu/sensu_go/roles/agent/tasks/main.yml new file mode 100644 index 00000000..44135ccd --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/agent/tasks/main.yml @@ -0,0 +1,16 @@ +--- +- name: Install sensu-go-agent binary + include_role: + name: install + vars: + components: [sensu-go-agent] + +- name: Inform restart handler that we are in charge of the agent service + set_fact: + manage_sensu_agent_service: true + +- name: Configure the agent + include_tasks: configure.yml + +- name: Start the agent + include_tasks: start.yml diff --git a/ansible_collections/sensu/sensu_go/roles/agent/tasks/start.yml b/ansible_collections/sensu/sensu_go/roles/agent/tasks/start.yml new file mode 100644 index 00000000..7c5970a1 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/agent/tasks/start.yml @@ -0,0 +1,15 @@ +--- +- name: Start sensu-agent (Linux) + service: + name: sensu-agent + state: started + enabled: true + when: ansible_facts.os_family != "Windows" + +- name: Start sensu-agent (Windows) + action: + module: win_service + name: SensuAgent + path: C:\Program Files\sensu\sensu-agent\bin\sensu-agent.exe service run + state: started + when: ansible_facts.os_family == "Windows" diff --git a/ansible_collections/sensu/sensu_go/roles/agent/tasks/windows/configure.yml b/ansible_collections/sensu/sensu_go/roles/agent/tasks/windows/configure.yml new file mode 100644 index 00000000..e6b5f33c --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/agent/tasks/windows/configure.yml @@ -0,0 +1,15 @@ +--- +- name: Load Windows locations + include_vars: windows.yml + +- name: Install agent communication PKI + win_copy: + src: "{{ agent_trusted_ca_file }}" + dest: "{{ trusted_ca_file_path }}" + when: agent_trusted_ca_file is defined + +- name: Configure sensu-agent ({{ agent_config_path }}) + win_template: + src: agent.yml.j2 + dest: "{{ agent_config_path }}" + notify: Restart Windows agent diff --git a/ansible_collections/sensu/sensu_go/roles/agent/templates/agent.yml.j2 b/ansible_collections/sensu/sensu_go/roles/agent/templates/agent.yml.j2 new file mode 100644 index 00000000..ecd55b33 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/agent/templates/agent.yml.j2 @@ -0,0 +1,27 @@ +--- +# +# {{ managed }} +# + +## +# Sensu agent configuration +## +{% if not agent_config or "backend-url" not in agent_config %} +backend-url: +{{ agent_backend_urls | to_nice_yaml }} +{% endif -%} + +{% if agent_trusted_ca_file is defined and + not (agent_config and "trusted-ca-file" in agent_config) %} +trusted-ca-file: {{ trusted_ca_file_path }} +{% endif -%} + +{% if agent_trusted_ca_file is defined or agent_config and + ("trusted-ca-file" in agent_config and + not "insecure-skip-tls-verify" in agent_config) %} +insecure-skip-tls-verify: false +{% endif -%} + +{% if agent_config %} +{{ agent_config | to_nice_yaml }} +{% endif %} diff --git a/ansible_collections/sensu/sensu_go/roles/agent/vars/linux.yml b/ansible_collections/sensu/sensu_go/roles/agent/vars/linux.yml new file mode 100644 index 00000000..66232239 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/agent/vars/linux.yml @@ -0,0 +1,3 @@ +--- +agent_config_path: /etc/sensu/agent.yml +trusted_ca_file_path: /etc/sensu/sensu-agent-trusted-ca.crt diff --git a/ansible_collections/sensu/sensu_go/roles/agent/vars/main.yml b/ansible_collections/sensu/sensu_go/roles/agent/vars/main.yml new file mode 100644 index 00000000..28afcd19 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/agent/vars/main.yml @@ -0,0 +1,2 @@ +--- +managed: Managed by Ansible - do NOT edit this file manually! diff --git a/ansible_collections/sensu/sensu_go/roles/agent/vars/windows.yml b/ansible_collections/sensu/sensu_go/roles/agent/vars/windows.yml new file mode 100644 index 00000000..6f422f9a --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/agent/vars/windows.yml @@ -0,0 +1,3 @@ +--- +agent_config_path: C:\ProgramData\sensu\config\agent.yml +trusted_ca_file_path: C:\ProgramData\sensu\config\sensu-agent-trusted-ca.crt diff --git a/ansible_collections/sensu/sensu_go/roles/backend/README.md b/ansible_collections/sensu/sensu_go/roles/backend/README.md new file mode 100644 index 00000000..66702076 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/backend/README.md @@ -0,0 +1,5 @@ +# sensu.sensu_go.backend role + +Visit [the official documentation site][docs] for role documentation. + + [docs]: https://sensu.github.io/sensu-go-ansible/roles/backend.html diff --git a/ansible_collections/sensu/sensu_go/roles/backend/defaults/main.yml b/ansible_collections/sensu/sensu_go/roles/backend/defaults/main.yml new file mode 100644 index 00000000..488a8d87 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/backend/defaults/main.yml @@ -0,0 +1,7 @@ +--- +cluster_admin_username: admin +cluster_admin_password: P@ssw0rd! + +# Related to /etc/sensu/backend.yml, see +# https://docs.sensu.io/sensu-go/latest/reference/backend/#configuration-summary +backend_config: diff --git a/ansible_collections/sensu/sensu_go/roles/backend/handlers/main.yml b/ansible_collections/sensu/sensu_go/roles/backend/handlers/main.yml new file mode 100644 index 00000000..f327f3f1 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/backend/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: Restart backend + service: + name: sensu-backend + state: restarted + when: manage_sensu_backend_service | default(False) diff --git a/ansible_collections/sensu/sensu_go/roles/backend/meta/argument_specs.yml b/ansible_collections/sensu/sensu_go/roles/backend/meta/argument_specs.yml new file mode 100644 index 00000000..7f243eaf --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/backend/meta/argument_specs.yml @@ -0,0 +1,153 @@ +argument_specs: + + configure: + short_description: Configure Sensu Go backend + description: + - Write the Sensu Go backend configuration file and optionally copy the + keys and certificates over. + + options: + backend_config: &backend_config + description: + - Any option that is valid for the Sensu Go backend version we are + installing. + - All valid options are listed at + U(https://docs.sensu.io/sensu-go/latest/reference/backend/#configuration). + - Role copies the key-value pairs from the I(backend_config) variable + verbatim to the configuration file. This means that we must copy + the key names B(exactly) as they appear in the configuration + reference. In a way, the I(backend_config) variable should contain a + properly indented copy of the C(/etc/sensu/backend.yml) file. + type: dict + + etcd_cert_file: &etcd_cert_file + description: + - Path to the certificate used for SSL/TLS connections B(to) etcd. + This is a client certificate. + type: str + + etcd_key_file: &etcd_key_file + description: + - Path to the private key for the etcd client certificate file. Must + be unencrypted. + type: str + + etcd_trusted_ca_file: &etcd_trusted_ca_file + description: + - Path to the trusted certificate authority for the etcd client + certificates. + type: str + + etcd_peer_cert_file: &etcd_peer_cert_file + description: + - Path to the certificate used for SSL/TLS connections between peers. + This will be used both for listening on the peer address as well as + sending requests to other peers. + type: str + + etcd_peer_key_file: &etcd_peer_key_file + description: + - Path to the peer certificate's key. Must be unencrypted. + type: str + + etcd_peer_trusted_ca_file: &etcd_peer_trusted_ca_file + description: + - Path to the trusted certificate authority for the peer + certificates. + type: str + + api_cert_file: &api_cert_file + description: + - Path to the certificate used to secure the Sensu Go API. + type: str + + api_key_file: &api_key_file + description: + - Path to the private key corresponding to the Sensu Go API + certificate. Must be unencrypted. + type: str + + api_trusted_ca_file: &api_trusted_ca_file + description: + - Path to the trusted certificate authority for the Sensu Go API + certificates. + type: str + + dashboard_cert_file: &dashboard_cert_file + description: + - Path to the certificate used for SSL/TLS connections to the + dashboard. + type: str + + dashboard_key_file: &dashboard_key_file + description: + - Path to the private key corresponding to the dashboard certificate. + Must be unencrypted. + type: str + + start: + short_description: Start Sensu Go backend + description: + - Start the Sensu Go backend service and initialize it on the first run. + + options: + cluster_admin_username: &cluster_admin_username + description: + - Initial admin user to create when initializing backend for the + first time. + type: str + default: admin + + cluster_admin_password: &cluster_admin_password + description: + - Initial admin password to create when initializing backend for the + first time. + type: str + default: P@ssw0rd! + + main: + short_description: Install, configure, and start Sensu Go backend + description: + - Install, configure, and start the Sensu Go backend service and + initialize it on the first run. + + options: + channel: + description: + - Repository channel that serves as a source of packages. + - Visit the packagecloud site to find all available channels. + type: str + default: stable + + version: &version + description: + - Package version to install. + - Can be any valid version string such as C(6.2.5) or special value + C(latest). + type: str + default: latest + + build: + description: + - Package build to install. + - Can be any valid build string such as C(8290) or a special value + latest. + - If the I(version) variable is set to latest, this variable is + ignored and the latest available build is installed. + type: str + default: latest + + backend_config: *backend_config + etcd_cert_file: *etcd_cert_file + etcd_key_file: *etcd_key_file + etcd_trusted_ca_file: *etcd_trusted_ca_file + etcd_peer_cert_file: *etcd_peer_cert_file + etcd_peer_key_file: *etcd_peer_key_file + etcd_peer_trusted_ca_file: *etcd_peer_trusted_ca_file + api_cert_file: *api_cert_file + api_key_file: *api_key_file + api_trusted_ca_file: *api_trusted_ca_file + dashboard_cert_file: *dashboard_cert_file + dashboard_key_file: *dashboard_key_file + cluster_admin_username: *cluster_admin_username + cluster_admin_password: *cluster_admin_password diff --git a/ansible_collections/sensu/sensu_go/roles/backend/meta/main.yml b/ansible_collections/sensu/sensu_go/roles/backend/meta/main.yml new file mode 100644 index 00000000..da85fa57 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/backend/meta/main.yml @@ -0,0 +1,24 @@ +galaxy_info: + author: XLAB Steampunk <steampunk@xlab.si> + description: Configure Sensu Go backend + license: GPL-3.0-or-later + min_ansible_version: 2.8 + + platforms: + - name: EL + versions: + - "7" + - "8" + - name: Ubuntu + versions: + - trusty + - xenial + - bionic + - disco + - name: Debian + versions: + - stretch + - buster + + galaxy_tags: + - sensu diff --git a/ansible_collections/sensu/sensu_go/roles/backend/tasks/configure.yml b/ansible_collections/sensu/sensu_go/roles/backend/tasks/configure.yml new file mode 100644 index 00000000..56102681 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/backend/tasks/configure.yml @@ -0,0 +1,71 @@ +--- +- name: Install etcd communication PKI + copy: + src: "{{ item.source }}" + dest: "/etc/sensu/{{ item.filename }}" + # Keep this in sync with what the backend service is running as from packager + owner: &sensu_user sensu + group: &sensu_group sensu + mode: "{{ item.mode | default('0644') }}" + loop: + - source: "{{ etcd_cert_file }}" + filename: etcd-client.crt + - source: "{{ etcd_key_file }}" + filename: etcd-client.key + mode: '0400' + - source: "{{ etcd_trusted_ca_file }}" + filename: etcd-client-ca.crt + - source: "{{ etcd_peer_cert_file }}" + filename: etcd-peer.crt + - source: "{{ etcd_peer_key_file }}" + filename: etcd-peer.key + mode: '0400' + - source: "{{ etcd_peer_trusted_ca_file }}" + filename: etcd-peer-ca.crt + when: etcd_trusted_ca_file is defined or etcd_cert_file is defined or + etcd_key_file is defined or etcd_peer_cert_file is defined or + etcd_peer_key_file is defined + +- name: Install API communication PKI + copy: + src: "{{ item.source }}" + dest: "/etc/sensu/{{ item.filename }}" + owner: *sensu_user + group: *sensu_group + mode: "{{ item.mode | default('0644') }}" + loop: + - source: "{{ api_cert_file }}" + filename: api.crt + - source: "{{ api_key_file }}" + filename: api.key + mode: '0400' + - source: "{{ api_trusted_ca_file }}" + filename: api-ca.crt + when: api_cert_file is defined or api_key_file is defined or + api_trusted_ca_file is defined + +- name: Install dashboard communication PKI + copy: + src: "{{ item.source }}" + dest: "/etc/sensu/{{ item.filename }}" + owner: *sensu_user + group: *sensu_group + mode: "{{ item.mode }}" + loop: + - source: "{{ dashboard_cert_file }}" + filename: dashboard.crt + mode: '0644' + - source: "{{ dashboard_key_file }}" + filename: dashboard.key + mode: '0400' + when: dashboard_cert_file is defined or dashboard_key_file is defined + +- name: Configure sensu-backend (/etc/sensu/backend.yml) + template: + src: backend.yml.j2 + dest: /etc/sensu/backend.yml + owner: *sensu_user + group: *sensu_group + mode: '0600' + notify: Restart backend + register: configure_result diff --git a/ansible_collections/sensu/sensu_go/roles/backend/tasks/main.yml b/ansible_collections/sensu/sensu_go/roles/backend/tasks/main.yml new file mode 100644 index 00000000..85de418d --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/backend/tasks/main.yml @@ -0,0 +1,16 @@ +--- +- name: Install sensu-go-backend binary + include_role: + name: install + vars: + components: [sensu-go-backend] + +- name: Inform restart handler that we are in charge of the backend service + set_fact: + manage_sensu_backend_service: true + +- name: Configure the backend + include_tasks: configure.yml + +- name: Start the backend + include_tasks: start.yml diff --git a/ansible_collections/sensu/sensu_go/roles/backend/tasks/start.yml b/ansible_collections/sensu/sensu_go/roles/backend/tasks/start.yml new file mode 100644 index 00000000..90a8a419 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/backend/tasks/start.yml @@ -0,0 +1,25 @@ +--- +- name: Start sensu-backend + service: + name: sensu-backend + state: started + enabled: true + +- name: Check for sensu-backend init command + command: + cmd: sensu-backend init -h + register: init_command_test + failed_when: false # Never fail, we just want to know if init exists. + changed_when: false # Displaying help is read-only operation. + check_mode: false # We do not modify the system, so we can always run + +- name: Initialize backend + command: + cmd: sensu-backend init + environment: + SENSU_BACKEND_CLUSTER_ADMIN_USERNAME: "{{ cluster_admin_username }}" + SENSU_BACKEND_CLUSTER_ADMIN_PASSWORD: "{{ cluster_admin_password }}" + when: init_command_test.rc == 0 + register: init_command + failed_when: init_command.rc not in (0, 3) # 0 - OK, 3 - already initialized + changed_when: init_command.rc == 0 diff --git a/ansible_collections/sensu/sensu_go/roles/backend/templates/backend.yml.j2 b/ansible_collections/sensu/sensu_go/roles/backend/templates/backend.yml.j2 new file mode 100644 index 00000000..2bda11eb --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/backend/templates/backend.yml.j2 @@ -0,0 +1,124 @@ +--- +# +# {{ managed }} +# + +{% if not backend_config or "state-dir" not in backend_config %} +state-dir: "/var/lib/sensu/sensu-backend" +{% endif -%} + +{% set secure_etcd = etcd_trusted_ca_file is defined or etcd_cert_file is defined or + etcd_key_file is defined or etcd_peer_cert_file is defined or + etcd_peer_key_file is defined %} +{% if secure_etcd and + (not backend_config or "etcd-listen-client-urls" not in backend_config) %} +etcd-listen-client-urls: "https://localhost:2379" +{% endif -%} + +{% if secure_etcd and + (not backend_config or "etcd-listen-peer-urls" not in backend_config) %} +etcd-listen-peer-urls: "https://localhost:2380" +{% endif -%} + +{% if secure_etcd and + (not backend_config or "etcd-initial-cluster" not in backend_config) %} +etcd-initial-cluster: "default=https://localhost:2380" +{% endif -%} + +{% if secure_etcd and + (not backend_config or + "etcd-initial-advertise-peer-urls" not in backend_config) %} +etcd-initial-advertise-peer-urls: "https://localhost:2380" +{% endif -%} + +{% if secure_etcd and + (not backend_config or "etcd-cert-file" not in backend_config) %} +etcd-cert-file: "/etc/sensu/etcd-client.crt" +{% endif -%} + +{% if secure_etcd and + (not backend_config or "etcd-key-file" not in backend_config) %} +etcd-key-file: "/etc/sensu/etcd-client.key" +{% endif -%} + +{% if secure_etcd and + (not backend_config or "etcd-client-cert-auth" not in backend_config) %} +etcd-client-cert-auth: true +{% endif -%} + +{% if secure_etcd and + (not backend_config or "etcd-trusted-ca-file" not in backend_config) %} +etcd-trusted-ca-file: "/etc/sensu/etcd-client-ca.crt" +{% endif -%} + +{% if secure_etcd and + (not backend_config or "etcd-peer-cert-file" not in backend_config) %} +etcd-peer-cert-file: "/etc/sensu/etcd-peer.crt" +{% endif -%} + +{% if secure_etcd and + (not backend_config or "etcd-peer-key-file" not in backend_config) %} +etcd-peer-key-file: "/etc/sensu/etcd-peer.key" +{% endif -%} + +{% if secure_etcd and + (not backend_config or + "etcd-peer-client-cert-auth" not in backend_config) %} +etcd-peer-client-cert-auth: true +{% endif -%} + +{% if secure_etcd and + (not backend_config or + "etcd-peer-trusted-ca-file" not in backend_config) %} +etcd-peer-trusted-ca-file: "/etc/sensu/etcd-peer-ca.crt" +{% endif -%} + +{% set secure_api = api_cert_file is defined or api_key_file is defined or + api_trusted_ca_file is defined %} +{% if secure_api and + (not backend_config or "cert-file" not in backend_config) %} +cert-file: "/etc/sensu/api.crt" +{% endif -%} + +{% if secure_api and + (not backend_config or "key-file" not in backend_config) %} +key-file: "/etc/sensu/api.key" +{% endif -%} + +{% if secure_api and + (not backend_config or + "insecure-skip-tls-verify" not in backend_config) %} +insecure-skip-tls-verify: false +{% endif -%} + +{% if secure_api and + (not backend_config or "trusted-ca-file" not in backend_config) %} +trusted-ca-file: "/etc/sensu/api-ca.crt" +{% endif -%} + +{% if secure_api and + (not backend_config or + "insecure-skip-tls-verify" not in backend_config) %} +insecure-skip-tls-verify: false +{% endif -%} + +{% if secure_api and + (not backend_config or "api-url" not in backend_config) %} +api-url: "https://localhost:8080" +{% endif -%} + +{% set secure_dashboard = dashboard_cert_file is defined or + dashboard_key_file is defined %} +{% if secure_dashboard and + (not backend_config or "dashboard-cert-file" not in backend_config) %} +dashboard-cert-file: "/etc/sensu/dashboard.crt" +{% endif -%} + +{% if secure_dashboard and + (not backend_config or "dashboard-key-file" not in backend_config) %} +dashboard-key-file: "/etc/sensu/dashboard.key" +{% endif -%} + +{% if backend_config %} +{{ backend_config | to_nice_yaml }} +{% endif %} diff --git a/ansible_collections/sensu/sensu_go/roles/backend/vars/main.yml b/ansible_collections/sensu/sensu_go/roles/backend/vars/main.yml new file mode 100644 index 00000000..28afcd19 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/backend/vars/main.yml @@ -0,0 +1,2 @@ +--- +managed: Managed by Ansible - do NOT edit this file manually! diff --git a/ansible_collections/sensu/sensu_go/roles/install/README.md b/ansible_collections/sensu/sensu_go/roles/install/README.md new file mode 100644 index 00000000..8c76a8ce --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/README.md @@ -0,0 +1,5 @@ +# sensu.sensu_go.install role + +Visit [the official documentation site][docs] for role documentation. + + [docs]: https://sensu.github.io/sensu-go-ansible/roles/install.html diff --git a/ansible_collections/sensu/sensu_go/roles/install/defaults/main.yml b/ansible_collections/sensu/sensu_go/roles/install/defaults/main.yml new file mode 100644 index 00000000..9d2b4c1a --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/defaults/main.yml @@ -0,0 +1,12 @@ +--- +os: unknown +dist: unknown + +packagecloud_auth: "" +channel: stable +version: latest +build: latest +components: + - sensu-go-backend + - sensu-go-agent + - sensu-go-cli diff --git a/ansible_collections/sensu/sensu_go/roles/install/meta/argument_specs.yml b/ansible_collections/sensu/sensu_go/roles/install/meta/argument_specs.yml new file mode 100644 index 00000000..0ccae514 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/meta/argument_specs.yml @@ -0,0 +1,69 @@ +argument_specs: + + repositories: + short_description: Enable Sensu Go repos + description: + - Install required repository files on supported distributions. + - This entry point does not work on Windows because there is no + concept of repository there. + + options: + channel: &channel + description: + - Repository channel that serves as a source of packages. + - Visit the packagecloud site to find all available channels. + type: str + default: stable + + packages: + short_description: Install selected Sensu Go packages + description: + - Make sure selected packages are installed. + - By default, the role will install latest available package version. + This will change in the next major version of the collection where the + I(version) will become a required variable. + + options: + components: &components + description: + - List of components to install. + type: list + elements: str + choices: + - sensu-go-backend + - sensu-go-agent + - sensu-go-cli + default: + - sensu-go-backend + - sensu-go-agent + - sensu-go-cli + + version: &version + description: + - Package version to install. + - Can be any valid version string such as C(6.2.5) or special value + C(latest). + type: str + default: latest + + build: &build + description: + - Package build to install. + - Can be any valid build string such as C(8290) or a special value + latest. + - If the I(version) variable is set to latest, this variable is + ignored and the latest available build is installed. + type: str + default: latest + + main: + short_description: Enable Sensu Go repos and install selected packages + description: + - The main entry point just combines the repositories and packages entry + points. + + options: + components: *components + channel: *channel + version: *version + build: *build diff --git a/ansible_collections/sensu/sensu_go/roles/install/meta/main.yml b/ansible_collections/sensu/sensu_go/roles/install/meta/main.yml new file mode 100644 index 00000000..fbbf7b5f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/meta/main.yml @@ -0,0 +1,24 @@ +galaxy_info: + author: XLAB Steampunk <steampunk@xlab.si> + description: Install Sensu Go components + license: GPL-3.0-or-later + min_ansible_version: 2.8 + + platforms: + - name: EL + versions: + - "7" + - "8" + - name: Ubuntu + versions: + - trusty + - xenial + - bionic + - disco + - name: Debian + versions: + - stretch + - buster + + galaxy_tags: + - sensu diff --git a/ansible_collections/sensu/sensu_go/roles/install/tasks/apt/install.yml b/ansible_collections/sensu/sensu_go/roles/install/tasks/apt/install.yml new file mode 100644 index 00000000..da7f2027 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/tasks/apt/install.yml @@ -0,0 +1,9 @@ +--- +- name: Install component + apt: + name: "{{ 'apt' | sensu.sensu_go.package_name(item, version, build) }}" + state: "{{ (version == 'latest') | ternary('latest', 'present') }}" + # FIXME(@tadeboro): This is a temporary "fix" for + # https://github.com/ansible/ansible/issues/29451. + force: true + loop: "{{ components }}" diff --git a/ansible_collections/sensu/sensu_go/roles/install/tasks/apt/prepare.yml b/ansible_collections/sensu/sensu_go/roles/install/tasks/apt/prepare.yml new file mode 100644 index 00000000..21daf60a --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/tasks/apt/prepare.yml @@ -0,0 +1,48 @@ +--- +- name: Include distro-specific vars ({{ ansible_distribution }}) + include_vars: file='{{ ansible_distribution }}.yml' + +- name: Update apt cache (ensure we have package index) + apt: + update_cache: true + # Updating the APT cache does not change the system so we never report a + # change here (helps keep the role idempotent). + changed_when: false + +- name: Install utility packages + apt: + name: + - gnupg + - debian-archive-keyring + - apt-transport-https + state: present + +- name: Fetch the apt repository key + uri: + url: https://{{ packagecloud_auth }}packagecloud.io/sensu/{{ channel }}/gpgkey + force_basic_auth: true + return_content: true + register: apt_key_download + # Fetching resource into memory does not change the system at all, so we + # never report a change here (helps keep the role idempotent). And by the + # same line of reasoning, we are also safe to run in check mode (the uri + # module does not support check mode and would cause us grief when it would + # be skipped). + changed_when: false + check_mode: false + +- name: Add apt key + apt_key: + data: "{{ apt_key_download.content }}" + +- name: Add apt repository + apt_repository: + repo: deb https://{{ packagecloud_auth }}packagecloud.io/sensu/{{ channel }}/{{ os }}/ {{ dist }} main + filename: /etc/apt/sources.list.d/sensu_{{ channel }} + validate_certs: true + +- name: Add apt source repository + apt_repository: + repo: deb-src https://{{ packagecloud_auth }}packagecloud.io/sensu/{{ channel }}/{{ os }}/ {{ dist }} main + filename: /etc/apt/sources.list.d/sensu_{{ channel }} + validate_certs: true diff --git a/ansible_collections/sensu/sensu_go/roles/install/tasks/dnf/install.yml b/ansible_collections/sensu/sensu_go/roles/install/tasks/dnf/install.yml new file mode 100644 index 00000000..5e1f02b7 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/tasks/dnf/install.yml @@ -0,0 +1,9 @@ +--- +# Why did we kill the package-latest check? Because we really do want to be +# able to upgrade the packages to the latest stable version. +- name: Install component + dnf: + name: "{{ 'yum' | sensu.sensu_go.package_name(item, version, build) }}" + state: latest # noqa package-latest + allow_downgrade: true + loop: "{{ components }}" diff --git a/ansible_collections/sensu/sensu_go/roles/install/tasks/dnf/prepare.yml b/ansible_collections/sensu/sensu_go/roles/install/tasks/dnf/prepare.yml new file mode 100644 index 00000000..51cda371 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/tasks/dnf/prepare.yml @@ -0,0 +1,31 @@ +--- +- name: Include distro-specific vars ({{ ansible_distribution }}) + include_vars: file='{{ ansible_distribution }}.yml' + +- name: Add yum repository + yum_repository: + name: sensu_{{ channel }} + description: sensu_{{ channel }} + file: sensu + baseurl: https://{{ packagecloud_auth }}packagecloud.io/sensu/{{ channel }}/{{ os }}/{{ dist }}/$basearch + gpgkey: https://{{ packagecloud_auth }}packagecloud.io/sensu/{{ channel }}/gpgkey + gpgcheck: false + repo_gpgcheck: true + enabled: true + sslverify: true + sslcacert: /etc/pki/tls/certs/ca-bundle.crt + metadata_expire: '300' + +- name: Add yum source repository + yum_repository: + name: sensu_{{ channel }}-source + description: sensu_{{ channel }}-source + file: sensu + baseurl: https://{{ packagecloud_auth }}packagecloud.io/sensu/{{ channel }}/{{ os }}/{{ dist }}/SRPMS + gpgkey: https://{{ packagecloud_auth }}packagecloud.io/sensu/{{ channel }}/gpgkey + gpgcheck: false + repo_gpgcheck: true + enabled: true + sslverify: true + sslcacert: /etc/pki/tls/certs/ca-bundle.crt + metadata_expire: '300' diff --git a/ansible_collections/sensu/sensu_go/roles/install/tasks/main.yml b/ansible_collections/sensu/sensu_go/roles/install/tasks/main.yml new file mode 100644 index 00000000..26d8e3b1 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/tasks/main.yml @@ -0,0 +1,7 @@ +--- +- name: Prepare package repositories + include_tasks: repositories.yml + when: ansible_facts.os_family != "Windows" # No repo concept on Windows + +- name: Install selected packages + include_tasks: packages.yml diff --git a/ansible_collections/sensu/sensu_go/roles/install/tasks/msi/install.yml b/ansible_collections/sensu/sensu_go/roles/install/tasks/msi/install.yml new file mode 100644 index 00000000..5b357554 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/tasks/msi/install.yml @@ -0,0 +1,35 @@ +--- +- name: Make sure we are installing agent + assert: + that: + - components | length == 1 + - components.0 == "sensu-go-agent" + fail_msg: Windows hosts only support agent installation + quiet: true + +- name: Load supported agent versions on Windows + include_vars: + file: Windows.yml + +- name: Check if version is supported + assert: + that: + - version in _msi_lookup + fail_msg: Version {{ version }} is not supported + quiet: true + +- name: Set version, build, and arch + set_fact: + _version: "{{ _msi_lookup[version].version }}" + _build: "{{ _msi_lookup[version].build }}" + _arch: "{{ (ansible_facts.architecture == '64-bit') | ternary('x64', 'x86') }}" + +- name: Fetch product code + set_fact: + _product_code: "{{ _msi_lookup[_version].product_codes[_arch] }}" + +- name: Install component + win_package: + path: "https://s3-us-west-2.amazonaws.com/sensu.io/sensu-go/{{ _version }}\ + /sensu-go-agent_{{ _version }}.{{ _build }}_en-US.{{ _arch }}.msi" + product_id: "{{ _product_code }}" diff --git a/ansible_collections/sensu/sensu_go/roles/install/tasks/packages.yml b/ansible_collections/sensu/sensu_go/roles/install/tasks/packages.yml new file mode 100644 index 00000000..73eb5153 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/tasks/packages.yml @@ -0,0 +1,8 @@ +--- +- name: Install selected components (Linux) + include_tasks: "{{ ansible_pkg_mgr }}/install.yml" + when: ansible_facts.os_family != "Windows" + +- name: Install selected components (Windows) + include_tasks: "msi/install.yml" + when: ansible_facts.os_family == "Windows" diff --git a/ansible_collections/sensu/sensu_go/roles/install/tasks/repositories.yml b/ansible_collections/sensu/sensu_go/roles/install/tasks/repositories.yml new file mode 100644 index 00000000..5c32d80c --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/tasks/repositories.yml @@ -0,0 +1,3 @@ +--- +- name: Prepare package repositories + include_tasks: "{{ ansible_pkg_mgr }}/prepare.yml" diff --git a/ansible_collections/sensu/sensu_go/roles/install/tasks/yum/install.yml b/ansible_collections/sensu/sensu_go/roles/install/tasks/yum/install.yml new file mode 100644 index 00000000..e919c9f5 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/tasks/yum/install.yml @@ -0,0 +1,9 @@ +--- +# Why did we kill the package-latest check? Because we really do want to be +# able to upgrade the packages to the latest stable version. +- name: Install component + yum: + name: "{{ 'yum' | sensu.sensu_go.package_name(item, version, build) }}" + state: "{{ (version == 'latest') | ternary('latest', 'present') }}" + allow_downgrade: true + loop: "{{ components }}" diff --git a/ansible_collections/sensu/sensu_go/roles/install/tasks/yum/prepare.yml b/ansible_collections/sensu/sensu_go/roles/install/tasks/yum/prepare.yml new file mode 100644 index 00000000..51cda371 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/tasks/yum/prepare.yml @@ -0,0 +1,31 @@ +--- +- name: Include distro-specific vars ({{ ansible_distribution }}) + include_vars: file='{{ ansible_distribution }}.yml' + +- name: Add yum repository + yum_repository: + name: sensu_{{ channel }} + description: sensu_{{ channel }} + file: sensu + baseurl: https://{{ packagecloud_auth }}packagecloud.io/sensu/{{ channel }}/{{ os }}/{{ dist }}/$basearch + gpgkey: https://{{ packagecloud_auth }}packagecloud.io/sensu/{{ channel }}/gpgkey + gpgcheck: false + repo_gpgcheck: true + enabled: true + sslverify: true + sslcacert: /etc/pki/tls/certs/ca-bundle.crt + metadata_expire: '300' + +- name: Add yum source repository + yum_repository: + name: sensu_{{ channel }}-source + description: sensu_{{ channel }}-source + file: sensu + baseurl: https://{{ packagecloud_auth }}packagecloud.io/sensu/{{ channel }}/{{ os }}/{{ dist }}/SRPMS + gpgkey: https://{{ packagecloud_auth }}packagecloud.io/sensu/{{ channel }}/gpgkey + gpgcheck: false + repo_gpgcheck: true + enabled: true + sslverify: true + sslcacert: /etc/pki/tls/certs/ca-bundle.crt + metadata_expire: '300' diff --git a/ansible_collections/sensu/sensu_go/roles/install/vars/Alma.yml b/ansible_collections/sensu/sensu_go/roles/install/vars/Alma.yml new file mode 100644 index 00000000..cb2da76e --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/vars/Alma.yml @@ -0,0 +1,3 @@ +--- +os: el +dist: '{{ ansible_distribution_version.split(".")[0] }}' diff --git a/ansible_collections/sensu/sensu_go/roles/install/vars/Amazon.yml b/ansible_collections/sensu/sensu_go/roles/install/vars/Amazon.yml new file mode 100644 index 00000000..f760918d --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/vars/Amazon.yml @@ -0,0 +1,3 @@ +--- +os: el +dist: 6 diff --git a/ansible_collections/sensu/sensu_go/roles/install/vars/CentOS.yml b/ansible_collections/sensu/sensu_go/roles/install/vars/CentOS.yml new file mode 100644 index 00000000..cb2da76e --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/vars/CentOS.yml @@ -0,0 +1,3 @@ +--- +os: el +dist: '{{ ansible_distribution_version.split(".")[0] }}' diff --git a/ansible_collections/sensu/sensu_go/roles/install/vars/Debian.yml b/ansible_collections/sensu/sensu_go/roles/install/vars/Debian.yml new file mode 100644 index 00000000..e1dc2720 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/vars/Debian.yml @@ -0,0 +1,3 @@ +--- +os: debian +dist: '{{ ansible_distribution_release }}' diff --git a/ansible_collections/sensu/sensu_go/roles/install/vars/OracleLinux.yml b/ansible_collections/sensu/sensu_go/roles/install/vars/OracleLinux.yml new file mode 100644 index 00000000..cb2da76e --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/vars/OracleLinux.yml @@ -0,0 +1,3 @@ +--- +os: el +dist: '{{ ansible_distribution_version.split(".")[0] }}' diff --git a/ansible_collections/sensu/sensu_go/roles/install/vars/RedHat.yml b/ansible_collections/sensu/sensu_go/roles/install/vars/RedHat.yml new file mode 100644 index 00000000..cb2da76e --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/vars/RedHat.yml @@ -0,0 +1,3 @@ +--- +os: el +dist: '{{ ansible_distribution_version.split(".")[0] }}' diff --git a/ansible_collections/sensu/sensu_go/roles/install/vars/Rocky.yml b/ansible_collections/sensu/sensu_go/roles/install/vars/Rocky.yml new file mode 100644 index 00000000..cb2da76e --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/vars/Rocky.yml @@ -0,0 +1,3 @@ +--- +os: el +dist: '{{ ansible_distribution_version.split(".")[0] }}' diff --git a/ansible_collections/sensu/sensu_go/roles/install/vars/Ubuntu.yml b/ansible_collections/sensu/sensu_go/roles/install/vars/Ubuntu.yml new file mode 100644 index 00000000..07acbc09 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/vars/Ubuntu.yml @@ -0,0 +1,3 @@ +--- +os: ubuntu +dist: '{{ ansible_distribution_release }}' diff --git a/ansible_collections/sensu/sensu_go/roles/install/vars/Windows.yml b/ansible_collections/sensu/sensu_go/roles/install/vars/Windows.yml new file mode 100644 index 00000000..9b7101bd --- /dev/null +++ b/ansible_collections/sensu/sensu_go/roles/install/vars/Windows.yml @@ -0,0 +1,314 @@ +_msi_lookup: + 5.20.0: + build: 12118 + product_codes: + x64: '{1289F1F1-771F-4554-98CE-64C75EE483A0}' + x86: '{050A9321-E95E-4D93-8C4E-2ADE630ED6BA}' + version: 5.20.0 + 5.20.1: + build: 12427 + product_codes: + x64: '{24283517-F637-4BD7-BA6C-4E4C8A0ABC91}' + x86: '{CF53D755-A675-4C93-B099-850BBD9E6FFE}' + version: 5.20.1 + 5.20.2: + build: 12959 + product_codes: + x64: '{8A6B1636-A850-4F6B-B6B7-8B7BF44ABFFE}' + x86: '{88E35B3B-0B23-4DAC-826F-7B16B4284C89}' + version: 5.20.2 + 5.21.0: + build: 14262 + product_codes: + x64: '{072C534E-BEDF-41F3-BA24-7F57F8932E3E}' + x86: '{9458C181-9512-492B-8FF3-B33165D69920}' + version: 5.21.0 + 5.21.1: + build: 25872 + product_codes: + x64: '{4B391527-0345-42F7-B298-878ACA618A6C}' + x86: '{2F1B32C5-C6DC-4EB9-959E-B225570115DF}' + version: 5.21.1 + 5.21.2: + build: 32982 + product_codes: + x64: '{0BBB6501-40C6-4C65-A456-FBA41710B2E9}' + x86: '{FBE03C70-4015-48D4-8680-938E1BD03551}' + version: 5.21.2 + 5.21.3: + build: 42094 + product_codes: + x64: '{FA45624F-0296-444B-8864-BC26EC5775F1}' + x86: '{57F5C867-44FE-465A-8C76-AF516BBF7A83}' + version: 5.21.3 + 5.21.4: + build: 71057 + product_codes: + x64: '{63311D12-2A46-4971-80DA-3847459F6DB8}' + x86: '{120691DA-EB4D-4AF3-B908-192AAEBFAA26}' + version: 5.21.4 + 5.21.5: + build: 75085 + product_codes: + x64: '{04E27BF3-96B0-4DEE-A346-5EEE550422B2}' + x86: '{AA378A80-E188-4F7E-90B7-F98395DCCE4A}' + version: 5.21.5 + 6.0.0: + build: 3003 + product_codes: + x64: '{E0C22D5F-C434-4AEB-B44B-8972D699DEB0}' + x86: '{81F1520D-6FA4-4663-AA7A-599C7BC00D2D}' + version: 6.0.0 + 6.1.0: + build: 3465 + product_codes: + x64: '{D07CF7A8-741D-41D2-A4ED-DD12005F5AF6}' + x86: '{1C03DD78-C86E-484B-A20B-F31A81D7F7AB}' + version: 6.1.0 + 6.1.1: + build: 3555 + product_codes: + x64: '{5205A350-92F1-4643-8EAA-1B88EFCBD013}' + x86: '{BEB96AA7-F29B-4AF6-8C51-27CF3DA35F0B}' + version: 6.1.1 + 6.1.2: + build: 3565 + product_codes: + x64: '{FF9C0C8A-C5A0-4DC9-BE2C-BC032490E763}' + x86: '{4C61E49D-BE7C-4795-B00B-B400A2DEC310}' + version: 6.1.2 + 6.1.3: + build: 3642 + product_codes: + x64: '{82789B56-72CF-4AB7-81E5-C136DEF9B71A}' + x86: '{39658556-334B-4D48-ABC9-64B2EA7AFFEC}' + version: 6.1.3 + 6.1.4: + build: 3866 + product_codes: + x64: '{88C67A03-5C68-4BD9-9B63-EF953A3E7C3B}' + x86: '{D496DB94-29CB-403C-A6B3-479474DA966B}' + version: 6.1.4 + 6.2.0: + build: 3888 + product_codes: + x64: '{388D860C-287B-43D5-97F4-BF3786F9BBEE}' + x86: '{A85FAC85-8CB7-4F35-A2E9-0C411CC0AA87}' + version: 6.2.0 + 6.2.1: + build: 3945 + product_codes: + x64: '{58442272-4509-4F29-B010-68827FBB8AF9}' + x86: '{6E079E5F-5F87-4A2B-AC78-3D7A7CDEF873}' + version: 6.2.1 + 6.2.2: + build: 3967 + product_codes: + x64: '{C0EB65CB-ED89-4657-B61C-FC9B74A205D6}' + x86: '{94375831-C0E6-4CED-8E4A-1BCC9308A087}' + version: 6.2.2 + 6.2.3: + build: 3986 + product_codes: + x64: '{E2578FD0-6413-4368-8E50-53AFDEDB17C4}' + x86: '{774892E8-B9EB-4798-B833-4E28F81783CC}' + version: 6.2.3 + 6.2.4: + build: 4013 + product_codes: + x64: '{149E48FC-10C0-4D6B-8E44-3528BE7C0E71}' + x86: '{574B7572-FCFD-4C8F-9260-495DBB54137F}' + version: 6.2.4 + 6.2.5: + build: 4040 + product_codes: + x64: '{DC2D98AB-9490-4EC1-A002-908833DAD0A9}' + x86: '{0A3BB524-06DF-4EF3-815C-08899AE2BFDF}' + version: 6.2.5 + 6.2.6: + build: 4389 + product_codes: + x64: '{42807FFC-B700-481D-B4C5-3D8395C8391B}' + x86: '{7B1A7EA6-EB55-46B9-AB0B-A8E6C69E1EF0}' + version: 6.2.6 + 6.2.7: + build: 4449 + product_codes: + x64: '{696360AB-D47F-4426-8621-4787B70F7046}' + x86: '{6527ACB7-AE24-48D1-9706-15BA0022176E}' + version: 6.2.7 + 6.3.0: + build: 4680 + product_codes: + x64: '{726B92DE-0142-4C57-9A81-DDAA2B43D374}' + x86: '{9244B94A-9CA6-44BB-B4D9-2567A377F6B7}' + version: 6.3.0 + 6.4.0: + build: 4826 + product_codes: + x64: '{5BA3DA84-94DB-4291-A133-1C2357E18E86}' + x86: '{DEEF9927-C506-4340-A85B-C3D08F50C32F}' + version: 6.4.0 + 6.4.1: + build: 4969 + product_codes: + x64: '{F38A8417-D9F1-4EAE-B98E-14C7B33AB219}' + x86: '{8B1C0EC7-E777-4627-BDFE-27698AB21C99}' + version: 6.4.1 + 6.4.2: + build: 5005 + product_codes: + x64: '{B7C0E062-27D8-47D8-96F8-8BDD2578C5A0}' + x86: '{DA9B4B1C-F34D-4CD6-83BB-DA8548557746}' + version: 6.4.2 + 6.4.3: + build: 5016 + product_codes: + x64: '{D7382F5C-386B-45CB-9BFB-078E4BA8D8D4}' + x86: '{77A43C9E-1687-4079-BD98-AF429593762A}' + version: 6.4.3 + 6.5.0: + build: 5266 + product_codes: + x64: '{2E3DC5F9-1ADB-48CF-B1CF-1E3489E5FCF8}' + x86: '{B3618CC4-EBF2-416E-9A81-5E0B63454DD6}' + version: 6.5.0 + 6.5.1: + build: 5325 + product_codes: + x64: '{07149975-D964-4627-9E8B-539AF7364027}' + x86: '{D0AF74F6-2F34-4F71-8AF8-85A4BBFB167B}' + version: 6.5.1 + 6.5.2: + build: 5376 + product_codes: + x64: '{7F8FBFFA-D5CB-4A6A-8F9E-30BE77A96D68}' + x86: '{4EB2E813-73E6-4C6E-B6E5-6E6F54B3D407}' + version: 6.5.2 + 6.5.3: + build: 5384 + product_codes: + x64: '{BC81192F-CB3F-40AF-B40B-176BF3BF14FE}' + x86: '{92F6EBAE-ED6B-4C5A-BABF-8EC7B1EC05A5}' + version: 6.5.3 + 6.5.4: + build: 5391 + product_codes: + x64: '{F3191296-456C-4841-BD00-9C738B858435}' + x86: '{4FD7E30E-F441-42F1-A3BB-3C98C73DBDA4}' + version: 6.5.4 + 6.5.5: + build: 5456 + product_codes: + x64: '{6B117FCD-C3A4-43C3-94B2-19AD71D2DA16}' + x86: '{81EEF28C-C5BA-4F67-A24C-04B5AD534459}' + version: 6.5.5 + 6.6.0: + build: 5502 + product_codes: + x64: '{18C14F3B-A115-4CDD-A571-EFDA8ED6BFB5}' + x86: '{18D7558F-A977-4ECC-BA48-88E901B8ABB9}' + version: 6.6.0 + 6.6.1: + build: 5514 + product_codes: + x64: '{F9FEE5BE-91EE-45BC-AED4-B647A8E4AD08}' + x86: '{C4BDBBA0-4EBE-471F-BF7E-704C24C33197}' + version: 6.6.1 + 6.6.2: + build: 5538 + product_codes: + x64: '{C9AB16C4-34E5-4237-A577-8941A96AFDF7}' + x86: '{662D43FF-8EE5-48CB-A2FD-9F2FE1C18CBB}' + version: 6.6.2 + 6.6.3: + build: 5588 + product_codes: + x64: '{88B71CB7-6A41-4E69-BA91-D347238E21E5}' + x86: '{EDE79B95-0050-4F5A-97E7-9D2061D87834}' + version: 6.6.3 + 6.6.4: + build: 5671 + product_codes: + x64: '{91005E61-CA07-48F8-8CC1-01C26DDDF149}' + x86: '{800EDA49-0544-42D7-BD70-47CCE3E51DDB}' + version: 6.6.4 + 6.6.5: + build: 5744 + product_codes: + x64: '{C5551A82-3FC5-4657-8B3D-220DB7C3702C}' + x86: '{F0522F39-FA16-480F-8970-0C019020101B}' + version: 6.6.5 + 6.6.6: + build: 5787 + product_codes: + x64: '{69A99B8F-8B18-4E47-928C-BA0BDD66CEE0}' + x86: '{B1FEDB39-4529-44F6-BACA-2B5AEC3B663E}' + version: 6.6.6 + 6.7.0: + build: 6196 + product_codes: + x64: '{B0F70691-6B51-4DE9-91DE-F0823A4D6974}' + x86: '{5927FE59-DBD8-42B0-8EFA-DC9CDDED9A6E}' + version: 6.7.0 + 6.7.1: + build: 6231 + product_codes: + x64: '{02828A2A-386F-4A03-B177-F19951F17ED9}' + x86: '{6BF93E93-592C-4570-B160-D59BB795B093}' + version: 6.7.1 + 6.7.2: + build: 6283 + product_codes: + x64: '{03708BA6-187F-4FCF-9B3C-29F6380CACB5}' + x86: '{F3524177-40D4-494B-A70D-28D7B56D9341}' + version: 6.7.2 + 6.7.3: + build: 6428 + product_codes: + x64: '{69DF3F6E-15BE-43B7-A93C-A144810DDCCD}' + x86: '{078E09A5-9812-4554-96A3-6CE4B102F42A}' + version: 6.7.3 + 6.7.4: + build: 6494 + product_codes: + x64: '{FB58C77E-1309-4B73-9EFD-F93FBD9E8E4B}' + x86: '{89EB2F14-1D6F-4B8B-965C-FEFE68ADAC8B}' + version: 6.7.4 + 6.7.5: + build: 6587 + product_codes: + x64: '{1E0F61D7-7B12-4A40-935E-7275EBC90DC4}' + x86: '{60DF07C3-4A9A-4AE9-A3FB-00E5D5C6D88E}' + version: 6.7.5 + 6.8.0: + build: 6692 + product_codes: + x64: '{254A257F-4B06-47F1-82DF-C2291F9F4CAF}' + x86: '{3249DC61-714C-4330-9F8B-8051BEB230A9}' + version: 6.8.0 + 6.8.1: + build: 6707 + product_codes: + x64: '{FDB49575-1A6F-47CE-9BE5-6546ED2F496E}' + x86: '{8BB46F78-460D-4CD9-A0F3-FE9872D4C1EE}' + version: 6.8.1 + 6.8.2: + build: 6788 + product_codes: + x64: '{E77E67DF-46EA-4EEA-8510-BFA0C547D671}' + x86: '{5C066D3A-53D7-476A-B93C-588ADC38106A}' + version: 6.8.2 + 6.9.0: + build: 6863 + product_codes: + x64: '{31CC4B1A-1BF5-443C-B719-88E23D2605E3}' + x86: '{C2E7439E-3F4D-46AB-9275-5F313F40F30F}' + version: 6.9.0 + 6.9.1: &id001 + build: 6928 + product_codes: + x64: '{1A371BB5-43CC-42E3-9294-7B5A9FED5594}' + x86: '{DFE77283-A52D-43AE-A156-3DAA47E72F69}' + version: 6.9.1 + latest: *id001 diff --git a/ansible_collections/sensu/sensu_go/sanity.requirements b/ansible_collections/sensu/sensu_go/sanity.requirements new file mode 100644 index 00000000..a99e2d2c --- /dev/null +++ b/ansible_collections/sensu/sensu_go/sanity.requirements @@ -0,0 +1,6 @@ +# ansible-lint >= 4.3.0 does not support python < 3.6 anymore +ansible-lint==5.4.0; python_version >= "3.6" +flake8 +yamllint +pyyaml +requests
\ No newline at end of file diff --git a/ansible_collections/sensu/sensu_go/tests/config.yml b/ansible_collections/sensu/sensu_go/tests/config.yml new file mode 100644 index 00000000..59769d37 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/config.yml @@ -0,0 +1,3 @@ +--- +modules: + python_requires: ">= 2.7" diff --git a/ansible_collections/sensu/sensu_go/tests/integration/base.yml b/ansible_collections/sensu/sensu_go/tests/integration/base.yml new file mode 100644 index 00000000..396c4ebf --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/base.yml @@ -0,0 +1,54 @@ +--- +scenario: + test_sequence: + - destroy + - create + - converge + - destroy +dependency: + name: galaxy +driver: + name: docker +provisioner: + name: ansible + config_options: + defaults: + interpreter_python: auto_silent + lint: + enabled: false +platforms: + - name: v6.9.0 + image: quay.io/xlab-steampunk/sensu-go-tests-sensu:6.9.0 + pre_build_image: true + pull: true + override_command: false + - name: v6.8.2 + image: quay.io/xlab-steampunk/sensu-go-tests-sensu:6.8.2 + pre_build_image: true + pull: true + override_command: false + - name: v6.7.5 + image: quay.io/xlab-steampunk/sensu-go-tests-sensu:6.7.5 + pre_build_image: true + pull: true + override_command: false + - name: v6.6.2 + image: quay.io/xlab-steampunk/sensu-go-tests-sensu:6.6.2 + pre_build_image: true + pull: true + override_command: false + - name: v6.5.5 + image: quay.io/xlab-steampunk/sensu-go-tests-sensu:6.5.5 + pre_build_image: true + pull: true + override_command: false + - name: v6.4.3 + image: quay.io/xlab-steampunk/sensu-go-tests-sensu:6.4.3 + pre_build_image: true + pull: true + override_command: false + - name: v6.3.0 + image: quay.io/xlab-steampunk/sensu-go-tests-sensu:6.3.0 + pre_build_image: true + pull: true + override_command: false diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/action_bonsai_asset/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/action_bonsai_asset/converge.yml new file mode 100644 index 00000000..ceeb3bdc --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/action_bonsai_asset/converge.yml @@ -0,0 +1,102 @@ +--- +- name: Converge + collections: + - sensu.sensu_go + hosts: all + gather_facts: no + tasks: + - name: Create a Bonsai asset + bonsai_asset: &idempotence + auth: + url: http://localhost:8080 + name: sensu/monitoring-plugins + version: 2.2.0-1 + on_remote: true + register: result + + - assert: + that: + # https://bonsai.sensu.io/api/v1/assets/sensu/monitoring-plugins/2.2.0-1/release_asset_builds + - result is changed + - result.object.metadata.name == 'sensu/monitoring-plugins' + - result.object.builds | length == 3 + - result.object.metadata.annotations | dict2items | length == 7 + + - name: Test asset creation idempotence + bonsai_asset: *idempotence + register: result + + - assert: + that: result is not changed + + - name: Modify an asset + bonsai_asset: + auth: + url: http://localhost:8080 + name: sensu/monitoring-plugins + version: 2.2.0-2 + labels: + my: label + annotations: + anot: here + register: result + + - assert: + that: + # https://bonsai.sensu.io/api/v1/assets/sensu/monitoring-plugins/2.2.0-2/release_asset_builds + - result is changed + - result.object.metadata.name == 'sensu/monitoring-plugins' + - result.object.builds | length == 4 + - result.object.metadata.annotations | dict2items | length == 8 + - result.object.metadata.annotations.anot == 'here' + - result.object.metadata.labels.my == 'label' + - result is changed + + - name: Fetch a specific asset + asset_info: + auth: + url: http://localhost:8080 + name: sensu/monitoring-plugins + register: result + + - assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'sensu/monitoring-plugins' + + - name: Add same asset under different name + bonsai_asset: + auth: + url: http://localhost:8080 + name: sensu/monitoring-plugins + version: 2.2.0-2 + rename: renamed-asset + + - name: Fetch renamed asset + asset_info: + auth: + url: http://localhost:8080 + name: renamed-asset + register: result + + - assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'renamed-asset' + + - name: Delete an asset + asset: + auth: + url: http://localhost:8080 + name: sensu/monitoring-plugins + state: absent + + - name: Fetch all assets again after deletion + asset_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == 1 diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/action_bonsai_asset/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/action_bonsai_asset/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/action_bonsai_asset/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_authentication/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_authentication/converge.yml new file mode 100644 index 00000000..5cc1fb19 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_authentication/converge.yml @@ -0,0 +1,313 @@ +--- +- name: Prepare new backend + hosts: has_api_key_support + gather_facts: no + + tasks: + - name: Create a regular user + sensu.sensu_go.user: + name: test_user + password: test_pass + + - name: Create a regular user role + sensu.sensu_go.role: + name: test_role + rules: + - verbs: [ list ] + resources: [ assets ] + + - name: Allow user to list entities + sensu.sensu_go.role_binding: + name: test_binding + role: test_role + users: [ test_user ] + + - name: Configure sensuctl + command: + cmd: > + sensuctl configure + --non-interactive + --url http://localhost:8080 + --username admin + --password P@ssw0rd! + --namespace default + + - name: Create API key for admin + command: + cmd: sensuctl api-key grant admin + register: api_key_admin_result + + - name: Create API key for test user + command: + cmd: sensuctl api-key grant test_user + register: api_key_test_user_result + + - name: Store API keys + set_fact: + api_key_admin: "{{ api_key_admin_result.stdout | regex_search('[^/]+$') }}" + api_key_test_user: "{{ api_key_test_user_result.stdout | regex_search('[^/]+$') }}" + + - name: Change default admin password for tests + command: + cmd: > + sensuctl user change-password admin + --current-password 'P@ssw0rd!' + --new-password insecure + + +- name: Test against a new backend + hosts: has_api_key_support + collections: + - sensu.sensu_go + gather_facts: no + + tasks: + - name: Fail with invalid password + mutator: + auth: + user: admin + password: not-a-password + url: http://localhost:8080 + name: my_mutator + command: sensu-influxdb-mutator + timeout: 30 + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "'Authentication' in result.msg" + + - name: Fail with bad username + asset_info: + auth: + user: not-a-valid-user + password: insecure + url: http://localhost:8080 + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "'Authentication' in result.msg" + + - name: Fail with bad API token + hook: + auth: + api_key: not-a-valid-api-key + url: http://localhost:8080 + name: restart_nginx + command: sudo systemctl start nginx + timeout: 60 + stdin: false + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "'Authentication' in result.msg" + + - name: Fail with bad API token even if username/password would be OK + hook: + auth: + api_key: not-a-valid-api-key + user: admin + password: insecure + url: http://localhost:8080 + name: restart_nginx + command: sudo systemctl start nginx + timeout: 60 + stdin: false + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "'Authentication' in result.msg" + + - name: Authenticate with a valid user/pass combination + asset: + auth: + user: admin + password: insecure + url: http://localhost:8080 + name: my_asset + builds: + - url: https://assets.bonsai.sensu.io/68546e739d96fd695655b77b35b5aabfbabeb056/sensu-plugins-cpu-checks_4.0.0_centos_linux_amd64.tar.gz + sha512: 518e7c17cf670393045bff4af318e1d35955bfde166e9ceec2b469109252f79043ed133241c4dc96501b6636a1ec5e008ea9ce055d1609865635d4f004d7187b + register: result + + - assert: + that: + - result is success + + - name: Authenticate with a valid API key + asset_info: + auth: + api_key: "{{ api_key_admin }}" + url: http://localhost:8080 + register: result + + - assert: + that: + - result is success + + - name: Authenticate with a valid API key and ignore user/password + asset_info: + auth: + api_key: "{{ api_key_admin }}" + user: not-a-valid-user + password: not-a-password + url: http://localhost:8080 + register: result + + - assert: + that: + - result is success + + - name: List assets using test user API key + asset_info: + auth: + api_key: "{{ api_key_test_user }}" + + - name: Fail to create an asset because test_user can only list them + bonsai_asset: + auth: + api_key: "{{ api_key_test_user }}" + name: sensu/monitoring-plugins + version: 2.2.0-1 + ignore_errors: true + register: result + + - assert: + that: + - result is failed + + - name: Fail to list entities because test_user has no access to entities + entity_info: + auth: + api_key: "{{ api_key_test_user }}" + ignore_errors: true + register: result + + - assert: + that: + - result is failed + + +- name: Prepare old backend + hosts: no_api_key_support + gather_facts: no + + tasks: + - name: Configure sensuctl + command: + cmd: > + sensuctl configure + --non-interactive + --url http://localhost:8080 + --username admin + --password P@ssw0rd! + --namespace default + + - name: Change default admin password for tests + command: + cmd: > + sensuctl user change-password admin + --current-password 'P@ssw0rd!' + --new-password insecure + + +- name: Test against an old backend + hosts: no_api_key_support + collections: + - sensu.sensu_go + gather_facts: no + + tasks: + - name: Fail with invalid password + mutator: + auth: + user: admin + password: not-a-password + url: http://localhost:8080 + name: my_mutator + command: sensu-influxdb-mutator + timeout: 30 + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "'Authentication' in result.msg" + + - name: Fail with bad username + asset_info: + auth: + user: not-a-valid-user + password: insecure + url: http://localhost:8080 + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "'Authentication' in result.msg" + + - name: Fail with API token + hook: + auth: + api_key: not-a-valid-api-key + url: http://localhost:8080 + name: restart_nginx + command: sudo systemctl start nginx + timeout: 60 + stdin: false + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "'Authentication' in result.msg" + + - name: Fail with bad API token even if username/password would be OK + hook: + auth: + api_key: not-a-valid-api-key + user: admin + password: insecure + url: http://localhost:8080 + name: restart_nginx + command: sudo systemctl start nginx + timeout: 60 + stdin: false + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "'Authentication' in result.msg" + + - name: Authenticate with a valid user/pass combination + asset: + auth: + user: admin + password: insecure + url: http://localhost:8080 + name: my_asset + builds: + - url: https://assets.bonsai.sensu.io/68546e739d96fd695655b77b35b5aabfbabeb056/sensu-plugins-cpu-checks_4.0.0_centos_linux_amd64.tar.gz + sha512: 518e7c17cf670393045bff4af318e1d35955bfde166e9ceec2b469109252f79043ed133241c4dc96501b6636a1ec5e008ea9ce055d1609865635d4f004d7187b + register: result + + - assert: + that: + - result is success diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_authentication/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_authentication/molecule.yml new file mode 100644 index 00000000..1648fc45 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_authentication/molecule.yml @@ -0,0 +1,13 @@ +platforms: + - name: v6.4.1 + image: quay.io/xlab-steampunk/sensu-go-tests-sensu:6.4.1 + groups: [ has_api_key_support ] + pre_build_image: true + pull: true + override_command: false + - name: v5.14.2 + image: quay.io/xlab-steampunk/sensu-go-tests-sensu:5.14.2 + groups: [ no_api_key_support ] + pre_build_image: true + pull: true + override_command: false diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/converge.yml new file mode 100644 index 00000000..a0867bb3 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/converge.yml @@ -0,0 +1,67 @@ +--- +- name: Converge + hosts: all + gather_facts: no + + tasks: + - name: Test connection using no verification + sensu.sensu_go.asset_info: + auth: + url: https://localhost:8080 + verify: false + + - name: Test connection using no verification (env var) + sensu.sensu_go.asset_info: + auth: + url: https://localhost:8080 + environment: + SENSU_VERIFY: "false" + + - name: Test connection using custom CA + sensu.sensu_go.user_info: + auth: + url: https://sensu-api:8080 + ca_path: /etc/sensu/api-ca.crt + + - name: Test connection using custom CA (env var) + sensu.sensu_go.user_info: + auth: + url: https://sensu-api:8080 + environment: + SENSU_CA_PATH: /etc/sensu/api-ca.crt + + - name: Test connection using default verification + sensu.sensu_go.asset_info: + auth: + url: https://localhost:8080 + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - '"certificate" in result.msg' + + - name: Test connection using custom CA not matching name + sensu.sensu_go.user_info: + auth: + url: https://localhost:8080 + ca_path: /etc/sensu/api-ca.crt + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - result.msg is search("localhost.+sensu-api") + + - name: Test connection using the wrong protocol + sensu.sensu_go.asset_info: + auth: + url: http://localhost:8080 + ignore_errors: true + register: result + + - assert: + that: + - result is failed diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/files/regenerate_cert.sh b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/files/regenerate_cert.sh new file mode 100755 index 00000000..eb550932 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/files/regenerate_cert.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +# Generate root CA certificate +openssl genrsa -out sensu-api-ca.key 2048 +openssl req -x509 -sha256 -new -nodes -key sensu-api-ca.key -subj '/CN=Sensu-test CA' -days 1095 -out sensu-api-ca.crt + +# Generate certificate +openssl genrsa -out sensu-api.key 2048 +openssl req -new -key "sensu-api.key" -out "sensu-api.csr" -sha256 -subj '/CN=sensu-api' +openssl x509 -req -days 1095 -in "sensu-api.csr" -sha256 -CA "sensu-api-ca.crt" -CAkey "sensu-api-ca.key" -CAcreateserial -out "sensu-api.crt" -extfile "sensu-api.cnf" -extensions server + +# print content of certificate +openssl x509 -in sensu-api-ca.crt -text +openssl x509 -in sensu-api.crt -text
\ No newline at end of file diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/files/sensu-api-ca.crt b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/files/sensu-api-ca.crt new file mode 100644 index 00000000..a2301e4b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/files/sensu-api-ca.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDETCCAfmgAwIBAgIUBSnmUh6PCEF3/6zTJ1DqH9rcNuswDQYJKoZIhvcNAQEL +BQAwGDEWMBQGA1UEAwwNU2Vuc3UtdGVzdCBDQTAeFw0yMjEyMTIwNjQ3NDdaFw0y +NTEyMTEwNjQ3NDdaMBgxFjAUBgNVBAMMDVNlbnN1LXRlc3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCoAXA7t1aC4zNgZNCasPHxcyCfmC4J5zoh +oTstyiFxhNv131EEQtpIABcWsgDXtQozJuAgHTVuGHEbHlFu7BFrh9Ik6FEznuZW +pZ53V//LRtQZt7DLQrJP2ZJhGedSm+exZj8f0Uk8do0xI46BtwT/u5pzkLN8NLCk +z+xu2mnFN+pdTqJLHP59JpRV1E0Al2E1m9rCfR8grGh4lTFDE/SwUueaHmg5Z0TN +QuROuRii1HcK8g7QZ0HyOWRGfpNGzXq2kYUaGTvONrSwGe4bPjT/Jp4TElUV9mSA +YoBE6JaDi1v28sXD9Cz6JgK63Ci0lmz6ny3H7kNfMQECgpj3KWL7AgMBAAGjUzBR +MB0GA1UdDgQWBBQrUp514z1VOH+uWKjmtkvomGULLjAfBgNVHSMEGDAWgBQrUp51 +4z1VOH+uWKjmtkvomGULLjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA +A4IBAQCg/Po64hN09w4QxwLvFRo050NBpvTi4JHrC/xQjFWl60UlQ+tCICtKDRRA +MCpOt+PqJlivm8sDrSn86vqQKMJfQHGUDExNpcEWFG+QLUJ0aX5jFUj6+V+2hAbO +g3QR9OvqeewuIfpHQJ8CTEbSamhc03LC2eX0AqdQUwSMuNrNgrZ0BBVTMkEfx8fM +vfhWPjbO7WDjj0NJrh/FRXxIplvog1vGVY5RNRNtGgs3hGwh+roNq0kxOTO+Ye5D +rtFSQ9CfjZI9qsT+XA8R6WzvFqSZSuxQpPkh70Pl6TPlK/W4Pqec2izZSlJxKFp1 +/rHU0nyXHJ2d7X5iV/sd3/WTs8kV +-----END CERTIFICATE----- diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/files/sensu-api.cnf b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/files/sensu-api.cnf new file mode 100644 index 00000000..1aec86d3 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/files/sensu-api.cnf @@ -0,0 +1,9 @@ +[server] +basicConstraints=CA:FALSE +subjectKeyIdentifier=hash +authorityKeyIdentifier=issuer + +extendedKeyUsage=serverAuth +keyUsage=digitalSignature, keyEncipherment +subjectAltName=IP:127.0.0.1 + diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/files/sensu-api.crt b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/files/sensu-api.crt new file mode 100644 index 00000000..f2fdc951 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/files/sensu-api.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIUBvuM6lXj9IVDxOUs7Szg6+vzEu8wDQYJKoZIhvcNAQEL +BQAwGDEWMBQGA1UEAwwNU2Vuc3UtdGVzdCBDQTAeFw0yMjEyMTIwNzE1MzRaFw0y +NTEyMTEwNzE1MzRaMBQxEjAQBgNVBAMMCXNlbnN1LWFwaTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAOHOCPfinziVMh0dJljYnnJ7/H6+Tj6fbf+I01U8 +hRQLhhKoA6n51uuThS6ncBiWhyC6t09i/SbXqvybdIVrn1Gcj/yyDpeTDxKs3GHY +i7q3xeDkJ/qWwsBK7sGtN3F6T/qQUvnhMhG8DzsNH+G11G3PnOJD6CMwmsCzFqip +GmagDFUOleHaMDaTZOiT6C8Bv+w08KakGuRoKtJUlIJCps9Za1ZnpcRKv5nY7Mxo +K1/K1M9dDmznuh0nHkSVjBig5j3I3k4Fto4ApIN/eGHoyCc9uXHCHoB8K7xwGeNQ +eNuxzYUmryemA51p/AjtIIXe7Hn9XxkTmWolCiv14Prp/ZUCAwEAAaOBnzCBnDAJ +BgNVHRMEAjAAMB0GA1UdDgQWBBQ+DDirz38IyldwTg9p38Ti0wAytzA9BgNVHSME +NjA0oRykGjAYMRYwFAYDVQQDDA1TZW5zdS10ZXN0IENBghQFKeZSHo8IQXf/rNMn +UOof2tw26zATBgNVHSUEDDAKBggrBgEFBQcDATALBgNVHQ8EBAMCBaAwDwYDVR0R +BAgwBocEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAkXWRt3m4IGp/6+/icT5PCZ3M +S7FCzx6f+2BxxvGdFX0JI1HTjqO7cUHQ57lC/eRHyx15W6CxgDkUCZJ/tJot0Q+k +ZZPHUsO6mG+d/DdMyRbfzOtH0xSrIFZTXdG9gSfEGG+r5IX+TBcZ3BOHtIgRYJsK +p3elglhsSgChxZ6SlcSp52KK9gaFcBywLtIt13BpRm/F9JX2IKmX4yZIFwvMg5W9 +MmDdR1GSBDe/uS9yRhst3hVOxBBLX9J2P1xBf4TYgmIAPqLhSCKIdr0ciRwwnV0D +dOk8q2eCVLqgu+Hl/NJWqLvI99eRtya20uVV458ZMjVa1ugw4kcpU7UmOEZajg== +-----END CERTIFICATE----- diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/files/sensu-api.key b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/files/sensu-api.key new file mode 100644 index 00000000..efea0f67 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/files/sensu-api.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA4c4I9+KfOJUyHR0mWNiecnv8fr5OPp9t/4jTVTyFFAuGEqgD +qfnW65OFLqdwGJaHILq3T2L9Jteq/Jt0hWufUZyP/LIOl5MPEqzcYdiLurfF4OQn ++pbCwEruwa03cXpP+pBS+eEyEbwPOw0f4bXUbc+c4kPoIzCawLMWqKkaZqAMVQ6V +4dowNpNk6JPoLwG/7DTwpqQa5Ggq0lSUgkKmz1lrVmelxEq/mdjszGgrX8rUz10O +bOe6HSceRJWMGKDmPcjeTgW2jgCkg394YejIJz25ccIegHwrvHAZ41B427HNhSav +J6YDnWn8CO0ghd7sef1fGROZaiUKK/Xg+un9lQIDAQABAoIBAQCEUbwnpoEvIx6O +uPozrhyLceRwUQyA3eQTjhZpKGHDcU1LuXEMNf+fZH7y6+NgRTVCFKg+uP8nt9HW +3THWzU47AMfPiHfMkryOcQVjwQWAkRg/xPM4gQf2rvJiRCLtOIONjO1SyIgSpGU4 +cWRxW5/0CWkhnjF2DZFhwpBQnWd/IsiiCrqCrEgKm4yjgu63LVEkBRmSwzkms2qO +aWOBnut/DJNkMdV3Zaxb+MYkaYmrus6viPGk3brbt70qkuFzJh4l/KSZcCWHofUW +3Rs1nrgNmrzaDEscULNZTE/4WSL32tyXyTo8ujIqVQCgMXbWc1QujV1AyHL1EZep +KRSYpn6BAoGBAPkTc35qlOZSOBE/GeIddqFlNbNK0XaJhjH0lNc1L/aoxNLQCNqX +cUCu1CB2YVoERhghvuLUOULkeVt9LeJywpLYW7l3lLbqJoBWedRqEHAkVxGxARqO +KWwgMyFgphTrkgfmSlOYWrZ1hW8BPL84pwQbLgyRU6jzkmHi0Re/GbjZAoGBAOgU ++YgO47vqCfQhrinWYESsqH3OZGoAaIlRqEMv4vPIq3UHtWne1je9vGRCTCgdA+GJ +6tkIbx04m61FvPXW/HlWlSYvA4zafWZFKjWkNtsrOZBIknqtQ9h5ca1CsQjEfWLJ +WdMN5DMTNQeK6irNHFHXFJHejlig/dCOdwF7oFUdAoGAXsgbHBEc5mSFN3Lmu5fw +q8wi2j5vZQdCTMJA3YA850Uj2QEXTW9xxmaBDHVf6GxV7BrzU8fknmLpF3qUOmbn +ShARH4u0yMJjslS1+bH+3V3G0FGmFN/iPWYnbt0jdjSKlnz47cS9SE+CXlJ/Nlkt +nS9mn5ux1UoS+zLf1ISBGkkCgYAZ3XyR9VbcMpE5bCeE8id4f+WnX8FBLxp3c2pV +UpjwooS5XkqRqgwl2jeM1Pa34cP09vH9jjsT/qMbBJKys2sf+s5UmxjCMfeX1k2F +/O9ALekUc4Ifuf+9uIs6zBv+5iczQ02HgBzWtGVMmebPLlzhoh7gwvUKW6bt/3Kz +75tG4QKBgFz2h/AVPz52Fraztx7QUc2J9sgM4yERvZqVnVCYVINrczi7iV+LS+yE +x4CenPWPRgnoh4P2jmqybmw+3rGsk4UyTPYZhvM7FUUI5DsD/Quk9KYgDIQBQTxV +0ryPzC6j8uYweF9xLKwfv+fIj8j+iqcOwhXWd5+E2YOXOIdKS3Oa +-----END RSA PRIVATE KEY----- diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/files/sensu-api.pem b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/files/sensu-api.pem new file mode 100644 index 00000000..beff18dc --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/files/sensu-api.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIUAOVga11hbTPIxN+jFbDcB6YX/PwwDQYJKoZIhvcNAQEL +BQAwGDEWMBQGA1UEAwwNU2Vuc3UtdGVzdCBDQTAeFw0yMjEyMDkxMzM5NThaFw0y +NTEyMDgxMzM5NThaMBQxEjAQBgNVBAMMCXNlbnN1LWFwaTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAJGoCboPEiiKj9E9YUUdVR3oIS1mHYQLqqd0T0Y5 +BTp4XbeLdvv/oFF5nol5QUWUJm7PHa8CUY6LO9k+fW2VkhQcBnrrv0H1e23DgyWm +J2pOjp54RYHciGErRnAkwpNt3Z6Q6oMnnK77hp5YiPsk3AvwgQaHFgOpI1L6Z2VI +difGuckMUruwVKT0q+y2zHDvewzf55J+ykBKVYjVEi8Kx/gaAIe12VybAyg5P0QM +ZUiKfcb7f/sae4KEZOWSXKfh8HaK/YXQN0JxwFujPt+MHxtUlzEyOakWgBjhP7nQ +BB8FgQmyE+m9xuq2bHei/qzx1183Pwd+zPrVoAWY+Rlv9x8CAwEAAaOBnzCBnDAJ +BgNVHRMEAjAAMB0GA1UdDgQWBBTqiIfWrKiwVZTFchGktuW8WWtFRDA9BgNVHSME +NjA0oRykGjAYMRYwFAYDVQQDDA1TZW5zdS10ZXN0IENBghQxv8cd6BODLyXX7rdB +udWKli0pBjATBgNVHSUEDDAKBggrBgEFBQcDATALBgNVHQ8EBAMCBaAwDwYDVR0R +BAgwBocEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEANptW8pIOrmulcqZ78n+b95zu +zcxoD0n1QjhouiEqlCgEaVoOpWbm12tds6pALDVuBNF1lYuTe2Kdffxoz/q7lpxb +Z07fU5lXF3mGa8CtsbXxlnpRxu1HIQwChp3EGxjxhOiGmfxfULicOl/z3nfWR9zu ++/7jpKRXAJ+O5JvxdU754cmXXftk+XncUz19ZbizX7trWDuqzkyTxD/0lNRA6OZN +hVF8FVE/Mju3FXbY4atvptV4e8MOsz1vfS6piPtU9HzD2miWuoY4di+OgC3DDTQs +4eDkOLvyoJ04JQMst928Oka6NPHnfUVxBeQg9JtzCDbFT/km4bQ4snybQ4vPnQ== +-----END CERTIFICATE----- diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/molecule.yml new file mode 100644 index 00000000..1785c689 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/molecule.yml @@ -0,0 +1,20 @@ +scenario: + test_sequence: + - destroy + - create + - prepare + - converge + - destroy + +platforms: + - name: backend + image: quay.io/xlab-steampunk/sensu-go-tests-centos:7 + pre_build_image: true + pull: true + override_command: false + privileged: true + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:ro + etc_hosts: + # sensu-api is the hostname used in the test certificate + sensu-api: 127.0.0.1 diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/prepare.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/prepare.yml new file mode 100644 index 00000000..1f146325 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/misc_api_cert/prepare.yml @@ -0,0 +1,13 @@ +--- +- name: Prepare + hosts: all + + tasks: + - name: Install backend with secured API + include_role: + name: sensu.sensu_go.backend + vars: + version: 5.21.0 + api_cert_file: files/sensu-api.crt + api_key_file: files/sensu-api.key + api_trusted_ca_file: files/sensu-api-ca.crt diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_ad_auth_provider/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_ad_auth_provider/converge.yml new file mode 100644 index 00000000..0112074b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_ad_auth_provider/converge.yml @@ -0,0 +1,360 @@ +--- +- name: Converge + hosts: all + gather_facts: no + tasks: + - name: Fetch all AD auth providers and verify the presence + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 0 + + - name: Fail when trying to create a AD auth provider with missing required params + sensu.sensu_go.ad_auth_provider: + auth: + url: http://localhost:8080 + name: activedirectory + register: result + ignore_errors: true + + - ansible.builtin.assert: + that: + - result is failed + - "'state is present but all of the following are missing' in result.msg" + + - name: Create AD auth provider with minimal params (check mode) + sensu.sensu_go.ad_auth_provider: &create-provider + state: present + name: activedirectory + servers: + - host: 127.0.0.1 + group_search: + base_dn: dc=acme,dc=org + user_search: + base_dn: dc=acme,dc=org + check_mode: true + register: result + + - ansible.builtin.assert: &create-provider-assertions + that: + - result is changed + - result.object.metadata.name == 'activedirectory' + - result.object.servers.0.host == '127.0.0.1' + - result.object.servers.0.group_search.base_dn == 'dc=acme,dc=org' + - result.object.servers.0.user_search.base_dn == 'dc=acme,dc=org' + + - name: Make sure AD auth provider was not created when running in check mode + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 0 + + - name: Create AD auth provider with minimal params + sensu.sensu_go.ad_auth_provider: *create-provider + register: result + + - ansible.builtin.assert: *create-provider-assertions + + - name: Make sure AD auth provider was created + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + name: activedirectory + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'activedirectory' + + - name: Idempotence check for AD auth provider creation with minimal params + sensu.sensu_go.ad_auth_provider: *create-provider + register: result + + - ansible.builtin.assert: + that: + - result is not changed + + - name: Update AD auth provider (check mode) + sensu.sensu_go.ad_auth_provider: &update-provider + state: present + name: activedirectory + servers: + - host: 127.0.0.1 + group_search: + base_dn: dc=acme,dc=org + user_search: + base_dn: dc=acme,dc=org + groups_prefix: dev + check_mode: true + register: result + + - ansible.builtin.assert: &update-provider-assertions + that: + - result is changed + - result.object.metadata.name == 'activedirectory' + - result.object.servers.0.host == '127.0.0.1' + - result.object.servers.0.group_search.base_dn == 'dc=acme,dc=org' + - result.object.servers.0.user_search.base_dn == 'dc=acme,dc=org' + - result.object.groups_prefix == 'dev' + + - name: Make sure AD auth provider was not updated in check mode + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + name: activedirectory + register: result + + - ansible.builtin.assert: + that: + - result.objects.0.metadata.name == 'activedirectory' + - result.objects.0.servers.0.host == '127.0.0.1' + - result.objects.0.servers.0.group_search.base_dn == 'dc=acme,dc=org' + - result.objects.0.servers.0.user_search.base_dn == 'dc=acme,dc=org' + - result.objects.0.groups_prefix == '' + + - name: Update AD auth provider + sensu.sensu_go.ad_auth_provider: *update-provider + register: result + + - ansible.builtin.assert: *update-provider-assertions + + - name: Make sure AD auth provider was updated + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + name: activedirectory + register: result + + - ansible.builtin.assert: + that: + - result.objects.0.metadata.name == 'activedirectory' + - result.objects.0.servers.0.host == '127.0.0.1' + - result.objects.0.servers.0.group_search.base_dn == 'dc=acme,dc=org' + - result.objects.0.servers.0.user_search.base_dn == 'dc=acme,dc=org' + - result.objects.0.groups_prefix == 'dev' + + - name: Idempotence check for AD auth provider modification + sensu.sensu_go.ad_auth_provider: *update-provider + register: result + + - ansible.builtin.assert: + that: + - result is not changed + + - name: Add AD auth provider server to existing one (check mode) + sensu.sensu_go.ad_auth_provider: &create-extra-provider + state: present + name: activedirectory + servers: + - host: 127.0.0.1 + group_search: + base_dn: dc=acme,dc=org + user_search: + base_dn: dc=acme,dc=org + - host: 127.0.0.2 + group_search: + base_dn: dc=acme,dc=org + user_search: + base_dn: dc=acme,dc=org + groups_prefix: dev + check_mode: true + register: result + + - ansible.builtin.assert: &create-extra-provider-assertions + that: + - result is changed + - result.object.metadata.name == 'activedirectory' + - result.object.servers.0.host == '127.0.0.1' + - result.object.servers.1.host == '127.0.0.2' + - result.object.servers.1.group_search.base_dn == 'dc=acme,dc=org' + - result.object.servers.1.user_search.base_dn == 'dc=acme,dc=org' + + - name: Make sure extra AD auth provider was not created when running in check mode + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.servers | length == 1 + + - name: Create an extra AD auth provider + sensu.sensu_go.ad_auth_provider: *create-extra-provider + register: result + + - ansible.builtin.assert: *create-extra-provider-assertions + + - name: Make sure extra AD auth provider was created + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + name: activedirectory + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.servers | length == 2 + - result.objects.0.metadata.name == 'activedirectory' + + - name: Idempotence check for extra AD auth provider creation + sensu.sensu_go.ad_auth_provider: *create-extra-provider + register: result + + - ansible.builtin.assert: + that: + - result is not changed + + - name: Create a AD auth provider with all params + sensu.sensu_go.ad_auth_provider: &create-provider-all-params + auth: + url: http://localhost:8080 + state: present + name: other-activedirectory + servers: + - host: 127.0.0.1 + port: 636 + insecure: false + security: tls + trusted_ca_file: /path/to/trusted-certificate-authorities.pem + client_cert_file: /path/to/ssl/cert.pem + client_key_file: /path/to/ssl/key.pem + default_upn_domain: example.org + binding: + user_dn: cn=binder,dc=acme,dc=org + password: ad_password + group_search: + base_dn: dc=acme,dc=org + attribute: member + name_attribute: cn + object_class: groupOfNames + user_search: + base_dn: dc=acme,dc=org + attribute: uid + name_attribute: cn + object_class: person + groups_prefix: dev + username_prefix: ad + register: result + + - ansible.builtin.assert: + that: + - result is changed + - result.object.metadata.name == 'other-activedirectory' + - result.object.servers.0.host == '127.0.0.1' + - result.object.servers.0.port == 636 + - result.object.servers.0.insecure == false + - result.object.servers.0.security == 'tls' + - result.object.servers.0.trusted_ca_file == '/path/to/trusted-certificate-authorities.pem' + - result.object.servers.0.client_cert_file == '/path/to/ssl/cert.pem' + - result.object.servers.0.client_key_file == '/path/to/ssl/key.pem' + - result.object.servers.0.default_upn_domain == 'example.org' + - result.object.servers.0.binding.user_dn == 'cn=binder,dc=acme,dc=org' + - "'password' not in result.object.servers.0.binding" + - result.object.servers.0.group_search.base_dn == 'dc=acme,dc=org' + - result.object.servers.0.group_search.attribute == 'member' + - result.object.servers.0.group_search.name_attribute == 'cn' + - result.object.servers.0.group_search.object_class == 'groupOfNames' + - result.object.servers.0.user_search.base_dn == 'dc=acme,dc=org' + - result.object.servers.0.user_search.attribute == 'uid' + - result.object.servers.0.user_search.name_attribute == 'cn' + - result.object.servers.0.user_search.object_class == 'person' + - result.object.groups_prefix == 'dev' + - result.object.username_prefix == 'ad' + + - name: Idempotence check for AD auth provider creation with all params + sensu.sensu_go.ad_auth_provider: *create-provider-all-params + register: result + + - ansible.builtin.assert: + that: + - result is not changed + + - name: Delete AD auth provider (check mode) + sensu.sensu_go.ad_auth_provider: &delete-provider + auth: + url: http://localhost:8080 + name: activedirectory + state: absent + check_mode: true + register: result + + - ansible.builtin.assert: + that: + - result is changed + + - name: Make sure AD auth provider was not deleted when running in check mode + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + name: activedirectory + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 1 + + - name: Delete AD auth provider + sensu.sensu_go.ad_auth_provider: *delete-provider + register: result + + - ansible.builtin.assert: + that: + - result is changed + + - name: Make sure AD auth provider was deleted + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + name: activedirectory + register: result + + - ansible.builtin.assert: + that: + - result.objects == [] + + - name: Check if still any AD auth providers exist + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 1 + + - name: Delete AD auth provider + sensu.sensu_go.ad_auth_provider: + auth: + url: http://localhost:8080 + name: other-activedirectory + state: absent + check_mode: true + register: result + + - ansible.builtin.assert: + that: + - result is changed + + - name: Delete a non-existent AD auth provider + sensu.sensu_go.ad_auth_provider: + auth: + url: http://localhost:8080 + name: i-dont-exist + state: absent + register: result + + - ansible.builtin.assert: + that: + - result is not changed diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_ad_auth_provider/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_ad_auth_provider/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_ad_auth_provider/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_asset/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_asset/converge.yml new file mode 100644 index 00000000..062ab7ee --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_asset/converge.yml @@ -0,0 +1,260 @@ +--- +- name: Setup sensuctl + hosts: all + gather_facts: no + + tasks: + - name: Configure sensuctl + command: + cmd: > + sensuctl configure + --non-interactive + --url http://localhost:8080 + --username admin + --password P@ssw0rd! + --namespace default + + +- name: Converge + collections: + - sensu.sensu_go + hosts: all + gather_facts: no + tasks: + - name: Create an asset with missing required parameters + asset: + auth: + url: http://localhost:8080 + name: asset + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "result.msg == 'state is present but all of the following are missing: builds'" + + - name: Create an asset with empty builds + asset: + auth: + url: http://localhost:8080 + name: asset + builds: + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "result.msg == 'builds must include at least one element'" + + - name: Create an asset with missing parameters for build + asset: + auth: + url: http://localhost:8080 + name: asset + builds: + - url: http://assets.bonsai.sensu.io/asset + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "result.msg == 'missing required arguments: sha512 found in builds'" + + - name: Create an asset with minimal parameters + asset: + auth: + url: http://localhost:8080 + name: minimal_asset + builds: + - url: https://assets.bonsai.sensu.io/68546e739d96fd695655b77b35b5aabfbabeb056/sensu-plugins-cpu-checks_4.0.0_centos_linux_amd64.tar.gz + sha512: 518e7c17cf670393045bff4af318e1d35955bfde166e9ceec2b469109252f79043ed133241c4dc96501b6636a1ec5e008ea9ce055d1609865635d4f004d7187b + register: result + + - assert: + that: + - result is changed + - result.object.builds | length == 1 + - result.object.builds.0.url == 'https://assets.bonsai.sensu.io/68546e739d96fd695655b77b35b5aabfbabeb056/sensu-plugins-cpu-checks_4.0.0_centos_linux_amd64.tar.gz' + - result.object.builds.0.sha512 == '518e7c17cf670393045bff4af318e1d35955bfde166e9ceec2b469109252f79043ed133241c4dc96501b6636a1ec5e008ea9ce055d1609865635d4f004d7187b' + - result.object.builds.0.filters == None + - result.object.builds.0.headers == None + + - name: Create an asset + asset: + auth: + url: http://localhost:8080 + name: asset + builds: + - url: https://assets.bonsai.sensu.io/68546e739d96fd695655b77b35b5aabfbabeb056/sensu-plugins-cpu-checks_4.0.0_centos_linux_amd64.tar.gz + sha512: 518e7c17cf670393045bff4af318e1d35955bfde166e9ceec2b469109252f79043ed133241c4dc96501b6636a1ec5e008ea9ce055d1609865635d4f004d7187b + filters: + - entity.system.os == 'linux' + - entity.system.arch == 'amd64' + - entity.system.platform == 'rhel' + headers: + Sensu-Blivet: foo + - url: https://assets.bonsai.sensu.io/68546e739d96fd695655b77b35b5aabfbabeb056/sensu-plugins-cpu-checks_4.0.0_alpine_linux_amd64.tar.gz + sha512: b2da25ecd7642e6de41fde37d674fe19dcb6ee3d680e145e32289f7cfc352e6b5f9413ee9b701d61faeaa47b399aa30b25885dbc1ca432c4061c8823774c28f3 + filters: + - entity.system.os == 'linux' + - entity.system.arch == 'amd64' + - entity.system.platform == 'alpine' + headers: + Sensu-Blivet: bar + annotations: + sensio.io.bonsai.url: https://assets.bonsai.sensu.io/68546e739d96fd695655b77b35b5aabfbabeb056/sensu-plugins-cpu-checks_4.0.0_centos_linux_amd64.tar.gz + sensio.io.bonsai.tier: Community + sensio.io.bonsai.version: 4.0.0 + sensio.io.bonsai.tags: ruby-runtime-2.4.4 + register: result + + - assert: + that: + - result is changed + - result.object.metadata.name == 'asset' + - result.object.builds | length == 2 + - result.object.metadata.annotations | dict2items | length == 4 + + - name: Test asset creation idempotence + asset: + auth: + url: http://localhost:8080 + name: asset + builds: + - url: https://assets.bonsai.sensu.io/68546e739d96fd695655b77b35b5aabfbabeb056/sensu-plugins-cpu-checks_4.0.0_alpine_linux_amd64.tar.gz + sha512: b2da25ecd7642e6de41fde37d674fe19dcb6ee3d680e145e32289f7cfc352e6b5f9413ee9b701d61faeaa47b399aa30b25885dbc1ca432c4061c8823774c28f3 + filters: + - entity.system.platform == 'alpine' + - entity.system.os == 'linux' + - entity.system.arch == 'amd64' + headers: + Sensu-Blivet: bar + - url: https://assets.bonsai.sensu.io/68546e739d96fd695655b77b35b5aabfbabeb056/sensu-plugins-cpu-checks_4.0.0_centos_linux_amd64.tar.gz + sha512: 518e7c17cf670393045bff4af318e1d35955bfde166e9ceec2b469109252f79043ed133241c4dc96501b6636a1ec5e008ea9ce055d1609865635d4f004d7187b + filters: + - entity.system.arch == 'amd64' + - entity.system.platform == 'rhel' + - entity.system.os == 'linux' + headers: + Sensu-Blivet: foo + annotations: + sensio.io.bonsai.url: https://assets.bonsai.sensu.io/68546e739d96fd695655b77b35b5aabfbabeb056/sensu-plugins-cpu-checks_4.0.0_centos_linux_amd64.tar.gz + sensio.io.bonsai.tier: Community + sensio.io.bonsai.version: 4.0.0 + sensio.io.bonsai.tags: ruby-runtime-2.4.4 + register: result + + - assert: + that: result is not changed + + - name: Modify an asset + asset: + auth: + url: http://localhost:8080 + name: asset + builds: + - url: https://assets.bonsai.sensu.io/73a6f8b6f56672630d83ec21676f9a6251094475/sensu-plugins-disk-checks_5.0.0_centos_linux_amd64.tar.gz + sha512: 0ce9d52b270b77f4cab754e55732ae002228201d0bd01a89b954a0655b88c1ee6546e2f82cfd1eec04689af90ad940cde128e8867912d9e415f4a58d7fdcdadf + annotations: + sensio.io.bonsai.url: https://assets.bonsai.sensu.io/68546e739d96fd695655b77b35b5aabfbabeb056/sensu-plugins-cpu-checks_4.0.0_centos_linux_amd64.tar.gz + sensio.io.bonsai.tier: Community + sensio.io.bonsai.version: 4.0.0 + register: result + + - assert: + that: + - result is changed + - result.object.builds | length == 1 + - result.object.builds.0.sha512 == '0ce9d52b270b77f4cab754e55732ae002228201d0bd01a89b954a0655b88c1ee6546e2f82cfd1eec04689af90ad940cde128e8867912d9e415f4a58d7fdcdadf' + - not result.object.builds.0.headers + - not result.object.builds.0.filters + - result.object.metadata.annotations | dict2items | length == 3 + - "'sensu.io.bonsai.tags' not in result.object.metadata.annotations" + + - name: Fetch a specific asset + asset_info: + auth: + url: http://localhost:8080 + name: asset + register: result + + - assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'asset' + + - name: Fetch all assets + asset_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == 2 + - result.objects.0.metadata.name == 'asset' + + - name: Delete an asset + asset: + auth: + url: http://localhost:8080 + name: asset2 + state: absent + + - name: Fetch all assets again after deletion + asset_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == 2 + - result.objects.0.metadata.name == 'asset' + + - name: Try to fetch non-existing asset + asset_info: + auth: + url: http://localhost:8080 + name: bad-bad-asset + register: result + + - assert: + that: + - result.objects == [] + + - name: Create an asset with a deprecated definition + shell: + cmd: | + cat <<EOF | sensuctl create + type: Asset + api_version: core/v2 + metadata: + name: old_asset + namespace: default + spec: + url: https://example.com/sensu-cpu-check_0.0.3_linux_amd64.tar.gz + sha512: 0ce9d52b270b77f4cab754e55732ae002228201d0bd01a89b954a0655b88c1ee6546e2f82cfd1eec04689af90ad940cde128e8867912d9e415f4a58d7fdcdadf + filters: + - entity.system.os == 'linux' + - entity.system.arch == 'amd64' + EOF + + - name: Update deprecated asset + asset: + auth: + url: http://localhost:8080 + name: old_asset + builds: + - url: https://example.com/sensu-cpu-check_0.0.3_linux_amd64.tar.gz + sha512: 0ce9d52b270b77f4cab754e55732ae002228201d0bd01a89b954a0655b88c1ee6546e2f82cfd1eec04689af90ad940cde128e8867912d9e415f4a58d7fdcdadf + filters: + - entity.system.os == 'linux' + - entity.system.arch == 'amd64' + register: result + + - assert: + that: result is changed diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_asset/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_asset/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_asset/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_check/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_check/converge.yml new file mode 100644 index 00000000..645ebe64 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_check/converge.yml @@ -0,0 +1,230 @@ +--- +- name: Converge + collections: + - sensu.sensu_go + hosts: all + gather_facts: no + tasks: + - name: Create a check with missing required parameters + check: + auth: + url: http://localhost:8080 + name: minimal_check + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "result.msg == 'state is present but all of the following are missing: subscriptions, command'" + + + - name: Create a check with minimal parameters + check: + auth: + url: http://localhost:8080 + name: minimal_check + command: /bin/true + subscriptions: + - checks + - also_checks + interval: 30 + register: result + + - assert: + that: + - result is changed + - result.object.metadata.name == 'minimal_check' + + - name: Create a check with minimal parameters idempotence + check: + auth: + url: http://localhost:8080 + name: minimal_check + command: /bin/true + subscriptions: + - checks + - also_checks + interval: 30 + register: result + + - assert: + that: result is not changed + + - name: Create a check + check: &check + auth: + url: http://localhost:8080 + name: check + command: /bin/true + subscriptions: + - checks + - also_checks + handlers: + - default + - not_default + interval: 30 + publish: True + timeout: 30 + ttl: 100 + stdin: False + low_flap_threshold: 20 + high_flap_threshold: 60 + proxy_entity_name: switch-dc-01 + proxy_requests: + entity_attributes: ['entity.entity_class == "proxy"'] + splay: True + splay_coverage: 90 + output_metric_format: nagios_perfdata + output_metric_handlers: ['influx-db'] + round_robin: True + env_vars: + foo: bar + runtime_assets: awesomeness + secrets: + - name: test + secret: value + register: result + + - assert: + that: + - result is changed + - result.object.metadata.name == 'check' + - "result.object.secrets == [{'name': 'test', 'secret': 'value'}]" + + - name: Test check creation idempotence + check: *check + register: result + + - assert: + that: result is not changed + + - name: Modify a check + check: + auth: + url: http://localhost:8080 + name: check + interval: 30 + command: /bin/true + subscriptions: + - checks + register: result + + - assert: + that: + - result is changed + - not result.object.handlers + - not result.object.env_vars + - not result.object.runtime_assets + - "'also_checks' not in result.object.subscriptions" + + - name: Create a second check + check: + auth: + url: http://localhost:8080 + name: check2 + interval: 30 + command: /usr/bin/true + subscriptions: checks + handlers: default + + - name: Fetch all checks + check_info: + auth: + url: http://localhost:8080 + + - name: Fetch a specific check + check_info: + auth: + url: http://localhost:8080 + name: check + register: result + + - assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'check' + + - name: Delete a check + check: + auth: + url: http://localhost:8080 + name: check + state: absent + + - name: Get all checks + check_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == 2 + - result.objects.0.metadata.name == 'check2' + + - name: Try to fetch non-existing check + check_info: + auth: + url: http://localhost:8080 + name: bad-bad-check + register: result + + - assert: + that: + - result.objects == [] + + - name: Create check for idempotency test of complex fields + check: + name: complex + command: sleep 10 + interval: 10 + subscriptions: + - sub1 + - sub2 + handlers: [] + proxy_requests: + entity_attributes: + - "entity.entity_class == 'proxy'" + - "entity.entity_class == 'demo'" + check_hooks: + warning: + - h1 + - h2 + - h3 + error: + - h4 + - h2 + env_vars: + var1: val1 + var2: val2 + + - name: Test for idempotency test of complex fields + check: + name: complex + command: sleep 10 + interval: 10 + subscriptions: + - sub2 + - sub1 + runtime_assets: [] + proxy_requests: + entity_attributes: + - "entity.entity_class == 'demo'" + - "entity.entity_class == 'proxy'" + check_hooks: + warning: + - h1 + - h3 + - h2 + error: + - h2 + - h4 + env_vars: + var2: val2 + var1: val1 + register: result + + - assert: + that: + - result is not changed diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_check/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_check/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_check/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_cluster/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_cluster/converge.yml new file mode 100644 index 00000000..d92d56f8 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_cluster/converge.yml @@ -0,0 +1,171 @@ +--- +- name: Converge + hosts: all + gather_facts: false + environment: + SENSU_ANSIBLE_DEBUG: "true" + + tasks: + - name: Make sure we start clean + sensu.sensu_go.cluster_info: + register: result + - ansible.builtin.assert: + that: + - result is success + - result.objects | length == 0 + + - name: Create new cluster (check mode) + sensu.sensu_go.cluster: &cluster + name: alpha-cluster + api_urls: http://10.10.0.1:8080 + check_mode: true + register: result + - ansible.builtin.assert: &cluster-assertions + that: + - result is changed + - result.object.metadata.name == "alpha-cluster" + - result.object.api_urls == ["http://10.10.0.1:8080"] + + - name: Make sure things are still clean + sensu.sensu_go.cluster_info: + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 0 + + - name: Create new cluster + sensu.sensu_go.cluster: *cluster + register: result + - ansible.builtin.debug: + var: result + - ansible.builtin.assert: *cluster-assertions + + - name: Make sure cluster is present on the backend + sensu.sensu_go.cluster_info: + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == "alpha-cluster" + - result.objects.0.api_urls == ["http://10.10.0.1:8080"] + + - name: Create new cluster (idempotence) + sensu.sensu_go.cluster: *cluster + register: result + - ansible.builtin.assert: + that: + - result is not changed + + - name: Update existing cluster (check mode) + sensu.sensu_go.cluster: &update + name: alpha-cluster + api_urls: + - http://10.10.0.2:8080 + check_mode: true + register: result + - ansible.builtin.assert: &update-assertions + that: + - result is changed + - result.object.metadata.name == "alpha-cluster" + - result.object.api_urls == ["http://10.10.0.2:8080"] + + - name: Make sure cluster did not change + sensu.sensu_go.cluster_info: + name: alpha-cluster + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.api_urls == ["http://10.10.0.1:8080"] + + - name: Update existing cluster + sensu.sensu_go.cluster: *update + register: result + - ansible.builtin.assert: *update-assertions + + - name: Make sure cluster is updated + sensu.sensu_go.cluster_info: + name: alpha-cluster + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == "alpha-cluster" + - result.objects.0.api_urls == ["http://10.10.0.2:8080"] + + - name: Update existing cluster (idempotence) + sensu.sensu_go.cluster: *update + register: result + - ansible.builtin.assert: + that: + - result is not changed + + - name: Create additional cluster + sensu.sensu_go.cluster: + name: beta-cluster + api_urls: + - https://10.20.0.1:8080 + - https://10.20.0.2:8080 + register: result + - ansible.builtin.assert: + that: + - result is changed + - result.object.metadata.name == "beta-cluster" + - result.object.api_urls == [ + "https://10.20.0.1:8080", + "https://10.20.0.2:8080", + ] + + - name: Make sure we have two replicators now + sensu.sensu_go.cluster_info: + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 2 + + - name: Delete cluster (check mode) + sensu.sensu_go.cluster: &delete + name: alpha-cluster + state: absent + check_mode: true + register: result + - ansible.builtin.assert: + that: + - result is changed + + - name: Make sure we still have two replicators + sensu.sensu_go.cluster_info: + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 2 + + - name: Delete cluster + sensu.sensu_go.cluster: *delete + register: result + - ansible.builtin.assert: + that: + - result is changed + + - name: Now we have only one cluster + sensu.sensu_go.cluster_info: + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == "beta-cluster" + + - name: And the first cluster is no more + sensu.sensu_go.cluster_info: + name: alpha-cluster + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 0 + + - name: Delete cluster (idempotency) + sensu.sensu_go.cluster: *delete + register: result + - ansible.builtin.assert: + that: + - result is not changed diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_cluster/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_cluster/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_cluster/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_cluster_role/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_cluster_role/converge.yml new file mode 100644 index 00000000..b771c76d --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_cluster_role/converge.yml @@ -0,0 +1,219 @@ +--- +- name: Converge + collections: + - sensu.sensu_go + hosts: all + gather_facts: no + tasks: + - name: Create a cluster role with missing required parameters + cluster_role: + auth: + url: http://localhost:8080 + name: test_cluster_role + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "result.msg == 'state is present but all of the following are missing: rules'" + + - name: Create a cluster role with empty rules + cluster_role: + auth: + url: http://localhost:8080 + name: test_cluster_role + rules: [] + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "result.msg == 'state is present but all of the following are missing: rules'" + + - name: Create a cluster role with invalid rule verbs + cluster_role: + auth: + url: http://localhost:8080 + name: test_cluster_role + rules: + - verbs: + - list + - do_something + resources: + - entities + ignore_errors: true + register: result + + - assert: + that: + - result is failed + + - name: Create a cluster role with minimal parameters + cluster_role: + auth: + url: http://localhost:8080 + name: minimal_test_cluster_role + rules: + - verbs: + - get + - list + resources: + - entities + register: result + + - assert: + that: + - result is changed + - result.object.metadata.name == 'minimal_test_cluster_role' + - result.object.rules | length == 1 + - result.object.rules.0.verbs | length == 2 + - result.object.rules.0.verbs == ['get', 'list'] + - result.object.rules.0.resources == ['entities'] + + - name: Check idempotence of cluster role creation with minimal parameters + cluster_role: + auth: + url: http://localhost:8080 + name: minimal_test_cluster_role + rules: + - verbs: + - list + - get + resources: + - entities + register: result + + - assert: + that: result is not changed + + - name: Create a cluster role + cluster_role: + auth: + url: http://localhost:8080 + name: test_cluster_role + rules: + - verbs: + - list + resources: + - assets + - checks + resource_names: + - some_resource_1 + - some_resource_2 + - verbs: + - list + - get + resources: + - checks + register: result + + - assert: + that: + - result is changed + - result.object.metadata.name == 'test_cluster_role' + - result.object.rules | length == 2 + - result.object.rules.0.verbs | length == 1 + - result.object.rules.0.verbs == ['list'] + - result.object.rules.0.resources == ['assets', 'checks'] + - result.object.rules.0.resource_names == ['some_resource_1', 'some_resource_2'] + - result.object.rules.1.verbs == ['list', 'get'] + - result.object.rules.1.resources == ['checks'] + - not result.object.rules.1.resource_names + + - name: Check idempotence of cluster role creation + cluster_role: + auth: + url: http://localhost:8080 + name: test_cluster_role + rules: + - verbs: + - list + resources: + - checks + - assets + resource_names: + - some_resource_2 + - some_resource_1 + - verbs: + - get + - list + resources: + - checks + register: result + + - assert: + that: + - result is not changed + + - name: Modify a cluster role + cluster_role: + auth: + url: http://localhost:8080 + name: test_cluster_role + rules: + - verbs: + - list + resources: + - assets + register: result + + - assert: + that: + - result is changed + - result.object.rules | length == 1 + - result.object.rules.0.verbs == ['list'] + - result.object.rules.0.resources == ['assets'] + - not result.object.rules.0.resource_names + + - name: Fetch all cluster roles + cluster_role_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == 8 # There are 6 default cluster roles + + - name: Fetch a specific cluster role + cluster_role_info: + auth: + url: http://localhost:8080 + name: test_cluster_role + register: result + + - assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'test_cluster_role' + + - name: Delete a cluster role + cluster_role: + auth: + url: http://localhost:8080 + state: absent + name: minimal_test_cluster_role + register: result + + - name: Fetch all cluster roles after deletion of a cluster role + cluster_role_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == 7 # There are 6 default cluster roles + + - name: Try to fetch non-existing role + cluster_role_info: + auth: + url: http://localhost:8080 + name: bad-bad-role + register: result + + - assert: + that: + - result.objects == [] diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_cluster_role/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_cluster_role/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_cluster_role/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_cluster_role_binding/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_cluster_role_binding/converge.yml new file mode 100644 index 00000000..5fbb40de --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_cluster_role_binding/converge.yml @@ -0,0 +1,194 @@ +--- +- name: Converge + collections: + - sensu.sensu_go + hosts: all + gather_facts: no + tasks: + - name: Create a cluster role binding with missing required parameters + cluster_role_binding: + auth: + url: http://localhost:8080 + name: test_cluster_role_binding + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "result.msg == 'state is present but all of the following are missing: cluster_role'" + + - name: Create a cluster role binding without providing users or groups + cluster_role_binding: + auth: + url: http://localhost:8080 + name: test_cluster_role_binding + cluster_role: test_cluster_role + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "result.msg == 'missing required parameters: users or groups'" + + - name: Create a cluster role binding with minimal parameters (users only) + cluster_role_binding: + auth: + url: http://localhost:8080 + name: minimal_test_cluster_role_binding_users + cluster_role: test_cluster_role + users: + - test_user + register: result + + - assert: + that: + - result is changed + - result.object.metadata.name == 'minimal_test_cluster_role_binding_users' + - result.object.role_ref.name == 'test_cluster_role' + - result.object.subjects | length == 1 + - result.object.subjects.0.name == 'test_user' + - result.object.subjects.0.type == 'User' + + - name: Create a cluster role binding with minimal parameters (groups only) + cluster_role_binding: + auth: + url: http://localhost:8080 + name: minimal_test_cluster_role_binding_groups + cluster_role: test_cluster_role + groups: + - test_group_1 + - test_group_2 + register: result + + - assert: + that: + - result is changed + - result.object.metadata.name == 'minimal_test_cluster_role_binding_groups' + - result.object.role_ref.name == 'test_cluster_role' + - result.object.subjects | length == 2 + + - name: Check idempotence of cluster role binding creation with minimal parameters + cluster_role_binding: + auth: + url: http://localhost:8080 + name: minimal_test_cluster_role_binding_groups + cluster_role: test_cluster_role + groups: + - test_group_2 + - test_group_1 + register: result + + - assert: + that: result is not changed + + - name: Create a cluster role binding + cluster_role_binding: + auth: + url: http://localhost:8080 + name: test_cluster_role_binding + cluster_role: test_cluster_role + users: + - test_user_1 + - test_user_2 + groups: + - test_group_1 + - test_group_2 + register: result + + - assert: + that: + - result is changed + - result.object.metadata.name == 'test_cluster_role_binding' + - result.object.role_ref.name == 'test_cluster_role' + - result.object.subjects | length == 4 + + - name: Check idempotence of cluster role binding creation + cluster_role_binding: + auth: + url: http://localhost:8080 + name: test_cluster_role_binding + cluster_role: test_cluster_role + users: + - test_user_2 + - test_user_1 + groups: + - test_group_2 + - test_group_1 + register: result + + - assert: + that: + - result is not changed + + - name: Modify a cluster role binding + cluster_role_binding: + auth: + url: http://localhost:8080 + name: test_cluster_role_binding + cluster_role: another_cluster_role + users: + groups: + - group_1 + register: result + + - assert: + that: + - result is changed + - result.object.metadata.name == 'test_cluster_role_binding' + - result.object.role_ref.name == 'another_cluster_role' + - result.object.subjects | length == 1 + - result.object.subjects.0.name == 'group_1' + - result.object.subjects.0.type == 'Group' + + - name: Fetch all cluster role bindings + cluster_role_binding_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == 6 # There are 3 pre-existing cluster role bindings by default + + - name: Fetch a specific cluster role binding + cluster_role_binding_info: + auth: + url: http://localhost:8080 + name: test_cluster_role_binding + register: result + + - assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'test_cluster_role_binding' + + - name: Delete a cluster role binding + cluster_role_binding: + auth: + url: http://localhost:8080 + state: absent + name: test_cluster_role_binding + register: result + + - name: Fetch all cluster roles bindings after deletion + cluster_role_binding_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == 5 # There are 3 pre-existing cluster role bindings by default + + - name: Try to fetch non-existing binding + cluster_role_binding_info: + auth: + url: http://localhost:8080 + name: bad-bad-binding + register: result + + - assert: + that: + - result.objects == [] diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_cluster_role_binding/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_cluster_role_binding/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_cluster_role_binding/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_datastore/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_datastore/converge.yml new file mode 100644 index 00000000..a6963f97 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_datastore/converge.yml @@ -0,0 +1,133 @@ +--- +- name: Converge + collections: + - sensu.sensu_go + hosts: all + gather_facts: no + tasks: + - name: Retrieve empty list of external datastores + datastore_info: + auth: &auth + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects == [] + + - name: Make sure we fail creation if dsn parameter is missing + datastore: + auth: *auth + name: my-incomplete-datastore + register: result + ignore_errors: true + + - assert: + that: + - result is failed + + - name: Enable external datastore with minimal parameters + datastore: &idempotence + auth: *auth + name: my-datastore + dsn: postgresql://user:secret@host:port/dbname + register: result + + - assert: + that: + - result is changed + - result.object.metadata.name == "my-datastore" + - result.object.dsn == "postgresql://user:secret@host:port/dbname" + + - name: Check for idempotence + datastore: *idempotence + register: result + + - assert: + that: + - result is not changed + + - name: Try to add another external storage + datastore: + auth: *auth + name: my-second-datastore + dsn: postgresql://user:secret@host:port/db + pool_size: 123 + register: result + ignore_errors: true + + - assert: + that: + - result is failed + + - name: Update external datastore + datastore: + auth: *auth + name: my-datastore + dsn: postgresql://user:secret@host:port/new + pool_size: 321 + register: result + + - assert: + that: + - result is changed + - result.object.metadata.name == "my-datastore" + - result.object.dsn == "postgresql://user:secret@host:port/new" + - result.object.pool_size == 321 + + - name: Fetch all datastores + datastore_info: + auth: *auth + register: result + + - assert: + that: + - result.objects | length == 1 + + - name: Fetch a specific datastore + datastore_info: + auth: *auth + name: my-datastore + register: result + + - assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == "my-datastore" + - result.objects.0.dsn == "postgresql://user:secret@host:port/new" + - result.objects.0.pool_size == 321 + + - name: Remove external datastore + datastore: + auth: *auth + name: my-datastore + state: absent + + - name: Re-fetch all datastores + datastore_info: + auth: *auth + register: result + + - assert: + that: + - result.objects | length == 0 + + - name: Try to fetch non-existing datastore + datastore_info: + auth: *auth + name: my-fictional-datastore + register: result + + - assert: + that: + - result.objects == [] + + - name: Try to remove non-existing external datastore + datastore: + auth: *auth + name: my-fictional-datastore + state: absent + + - assert: + that: + - result is not changed diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_datastore/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_datastore/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_datastore/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_entity/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_entity/converge.yml new file mode 100644 index 00000000..4cca6849 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_entity/converge.yml @@ -0,0 +1,191 @@ +--- +- name: Converge + collections: + - sensu.sensu_go + hosts: all + gather_facts: no + tasks: + - name: Create entity with minimal parameters + entity: + auth: + url: http://localhost:8080 + name: minimal_entity + entity_class: proxy + register: result + + - assert: + that: + - result is changed + - result.object.entity_class == 'proxy' + - result.object.metadata.name == 'minimal_entity' + + - name: Create entity with minimal parameters idempotence + entity: + auth: + url: http://localhost:8080 + name: minimal_entity + entity_class: proxy + register: result + + - assert: + that: + - result is not changed + + - name: Create entity + entity: + auth: + url: http://localhost:8080 + name: entity + entity_class: proxy + subscriptions: + - web + - prod + system: + hostname: playbook-entity + os: linux + platform: ubutntu + network: + interfaces: + - name: lo + addresses: + - 127.0.0.1/8 + - ::1/128 + - name: eth0 + mac: 52:54:00:20:1b:3c + addresses: + - 93.184.216.34/24 + last_seen: 1522798317 + deregister: yes + deregistration_handler: email-handler + redact: + - password + - pass + - api_key + user: agent + register: result + + - assert: + that: + - result is changed + - result.object.entity_class == 'proxy' + - result.object.subscriptions == ['web', 'prod'] + - result.object.system.network.interfaces.0.name == 'lo' + - result.object.system.network.interfaces.1.mac == '52:54:00:20:1b:3c' + - result.object.last_seen == 1522798317 + - result.object.deregister == True + - result.object.deregistration.handler == 'email-handler' + - result.object.redact == ['password', 'pass', 'api_key'] + - result.object.user == 'agent' + - result.object.metadata.name == 'entity' + + - name: Test entity creation idempotence + entity: + auth: + url: http://localhost:8080 + name: entity + entity_class: proxy + subscriptions: + - web + - prod + system: + hostname: playbook-entity + os: linux + platform: ubutntu + network: + interfaces: + - name: lo + addresses: + - 127.0.0.1/8 + - ::1/128 + - name: eth0 + mac: 52:54:00:20:1b:3c + addresses: + - 93.184.216.34/24 + last_seen: 1522798317 + deregister: yes + deregistration_handler: email-handler + redact: + - password + - pass + - api_key + user: agent + register: result + + - assert: + that: result is not changed + + - name: Modify entity + entity: + auth: + url: http://localhost:8080 + name: entity + entity_class: some_class + register: result + + - assert: + that: + - result is changed + - result.object.entity_class == 'some_class' + - "'deprecations' in result" + + - name: Create an agent entity + entity: &agent_entity + auth: + url: http://localhost:8080 + name: entity2 + entity_class: agent + + - name: Create an agent entity (idempotence) + entity: *agent_entity + register: result + + - assert: + that: + - result is not changed + + - name: Fetch all entities + entity_info: + auth: + url: http://localhost:8080 + + - name: Fetch a specific entity + entity_info: + auth: + url: http://localhost:8080 + name: entity + register: result + + - assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'entity' + - result.objects.0.entity_class == 'some_class' + + - name: Delete entity + entity: + auth: + url: http://localhost:8080 + name: entity + state: absent + + - name: Fetch all entities + entity_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == 2 + - result.objects.0.metadata.name == 'entity2' + + - name: Try to fetch non-existing entity + entity_info: + auth: + url: http://localhost:8080 + name: bad-bad-entity + register: result + + - assert: + that: + - result.objects == [] diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_entity/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_entity/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_entity/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_etcd_replicator/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_etcd_replicator/converge.yml new file mode 100644 index 00000000..4b87df9e --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_etcd_replicator/converge.yml @@ -0,0 +1,181 @@ +--- +- name: Converge + hosts: all + gather_facts: false + + tasks: + - name: Make sure we start clean + sensu.sensu_go.etcd_replicator_info: + register: result + - ansible.builtin.assert: + that: + - result is success + - result.objects | length == 0 + + - name: Create new replicator (check mode) + sensu.sensu_go.etcd_replicator: &replicator + name: cluster-role-replicator + ca_cert: /etc/sensu/certs/ca.pem + cert: /etc/sensu/certs/cert.pem + key: /etc/sensu/certs/key.pem + url: https://sensu.alpha.example.com:2379 + resource: ClusterRole + check_mode: true + register: result + - ansible.builtin.assert: &replicator-assertions + that: + - result is changed + - result.object.metadata.name == "cluster-role-replicator" + - result.object.resource == "ClusterRole" + - result.object.insecure is false + - result.object.cert == "/etc/sensu/certs/cert.pem" + + - name: Make sure things are still clean + sensu.sensu_go.etcd_replicator_info: + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 0 + + - name: Create new replicator + sensu.sensu_go.etcd_replicator: *replicator + register: result + - ansible.builtin.debug: + var: result + - ansible.builtin.assert: *replicator-assertions + + - name: Make sure replicator is present on the backend + sensu.sensu_go.etcd_replicator_info: + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == "cluster-role-replicator" + - result.objects.0.resource == "ClusterRole" + - result.objects.0.insecure is false + - result.objects.0.cert == "/etc/sensu/certs/cert.pem" + + - name: Create new replicator (idempotence) + sensu.sensu_go.etcd_replicator: *replicator + register: result + - ansible.builtin.assert: + that: + - result is not changed + + - name: Update existing replicator (check mode) + sensu.sensu_go.etcd_replicator: &update + name: cluster-role-replicator + insecure: true + url: + - http://sensu.alpha.example.com:2379 + resource: ClusterRole + check_mode: true + register: result + - ansible.builtin.assert: &update-assertions + that: + - result is changed + - result.object.metadata.name == "cluster-role-replicator" + - result.object.resource == "ClusterRole" + - result.object.insecure is true + + - name: Make sure replicator did not change + sensu.sensu_go.etcd_replicator_info: + name: cluster-role-replicator + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.insecure is false + + - name: Update existing replicator + sensu.sensu_go.etcd_replicator: *update + register: result + - ansible.builtin.assert: *update-assertions + + - name: Make sure replicator is updated + sensu.sensu_go.etcd_replicator_info: + name: cluster-role-replicator + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == "cluster-role-replicator" + - result.objects.0.resource == "ClusterRole" + - result.objects.0.insecure is true + + - name: Update existing replicator (idempotence) + sensu.sensu_go.etcd_replicator: *update + register: result + - ansible.builtin.assert: + that: + - result is not changed + + - name: Create additional replicator + sensu.sensu_go.etcd_replicator: + name: new-replicator + insecure: true + url: http://dummy.url + resource: Role + namespace: default + register: result + - ansible.builtin.assert: + that: + - result is changed + - result.object.metadata.name == "new-replicator" + - result.object.insecure is true + - result.object.resource == "Role" + - result.object.namespace == "default" + + - name: Make sure we have two replicators now + sensu.sensu_go.etcd_replicator_info: + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 2 + + - name: Delete replicator (check mode) + sensu.sensu_go.etcd_replicator: &delete + name: cluster-role-replicator + state: absent + check_mode: true + register: result + - ansible.builtin.assert: + that: + - result is changed + + - name: Make sure we still have two replicators + sensu.sensu_go.etcd_replicator_info: + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 2 + + - name: Delete replicator + sensu.sensu_go.etcd_replicator: *delete + register: result + - ansible.builtin.assert: + that: + - result is changed + + - name: Now we have only one replicator + sensu.sensu_go.etcd_replicator_info: + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == "new-replicator" + + - name: And the cluster role replicator is no more + sensu.sensu_go.etcd_replicator_info: + name: cluster-role-replicator + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 0 + + - name: Delete replicator (idempotency) + sensu.sensu_go.etcd_replicator: *delete + register: result + - ansible.builtin.assert: + that: + - result is not changed diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_etcd_replicator/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_etcd_replicator/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_etcd_replicator/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_event/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_event/converge.yml new file mode 100644 index 00000000..885698e1 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_event/converge.yml @@ -0,0 +1,217 @@ +--- +- name: Converge + collections: + - sensu.sensu_go + hosts: all + gather_facts: no + tasks: + - name: Event info without entity + event_info: + auth: + url: http://localhost:8080 + check: simple-check + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "result.msg == \"missing parameter(s) required by 'check': entity\"" + + - name: Create event with missing objects on remote + event: + auth: + url: http://localhost:8080 + timestamp: 134532453 + entity: entity + check: check + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - result.msg == "Entity with name 'entity' does not exist on remote." + + - name: Create simple entity + entity: + auth: + url: http://localhost:8080 + name: awesome_entity + entity_class: proxy + + - name: Create event with missing check on remote + event: + auth: + url: http://localhost:8080 + timestamp: 134532453 + entity: awesome_entity + check: check + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - result.msg == "Check with name 'check' does not exist on remote." + + - name: Create a simple check + check: + auth: + url: http://localhost:8080 + name: awesome_check + command: /bin/true + subscriptions: + - checks + - also_checks + interval: 30 + + - name: Get non-existing last event for entity and check combo + event_info: + auth: + url: http://localhost:8080 + entity: awesome_entity + check: awesome_check + register: result + + - assert: + that: + - result.objects == [] + + - name: Create event with minimal parameters + event: + auth: + url: http://localhost:8080 + entity: awesome_entity + check: awesome_check + register: result + + - assert: + that: + - result is changed + - result.object.check.metadata.name == 'awesome_check' + - result.object.check.command == '/bin/true' + - result.object.entity.metadata.name == 'awesome_entity' + + - name: Get last event + event_info: + auth: + url: http://localhost:8080 + entity: awesome_entity + check: awesome_check + register: result + + - assert: + that: + - result.objects.0.check.metadata.name == 'awesome_check' + - result.objects.0.check.command == '/bin/true' + - result.objects.0.entity.metadata.name == 'awesome_entity' + + - name: Get all events + event_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == 1 + + - name: Create second entity + entity: + auth: + url: http://localhost:8080 + name: entity2 + entity_class: proxy + + - name: Create a second check + check: + auth: + url: http://localhost:8080 + name: check2 + command: /bin/false + subscriptions: + - checks + - also_checks + interval: 30 + + - name: Create event with all parameters + event: + auth: + url: http://localhost:8080 + entity: entity2 + check: check2 + check_attributes: + duration: 1.945 + executed: 1522100915 + history: + - executed: 1552505193 + status: 1 + - executed: 1552505293 + status: 0 + - executed: 1552505393 + status: 0 + - executed: 1552505493 + status: 0 + issued: 1552506034 + last_ok: 1552506033 + output: '10' + state: 'passing' + status: 'ok' + total_state_change: 0 + metric_attributes: + handlers: + - handler1 + - handler2 + points: + - name: "sensu-go-sandbox.curl_timings.time_total" + tags: # Both value of the pairs must be strings + - name: "response_time_in_ms" + value: "101" + timestamp: 1552506033 + value: 0.005 + - name: "sensu-go-sandbox.curl_timings.time_namelookup" + tags: + - name: "namelookup_time_in_ms" + value: "57" + timestamp: 1552506033 + value: 0.004 + register: result + + - assert: + that: + - result is changed + - result.object.entity.metadata.name == 'entity2' + - result.object.check.metadata.name == 'check2' + - result.object.check.command == '/bin/false' + - result.object.check.duration == 1.945 + - result.object.check.executed == 1522100915 + - "result.object.check.history.0 == {'executed': 1552505193, 'status': 1}" + - result.object.check.issued == 1552506034 + - result.object.check.last_ok == 1552506033 + - result.object.check.output == '10' + - result.object.check.state == 'passing' + - result.object.check.status == 0 + - result.object.check.total_state_change == 0 + + - name: Get events matching entity2 + event_info: + auth: + url: http://localhost:8080 + entity: entity2 + register: result + + - assert: + that: + - result.objects | length == 1 + - result.objects.0.check.metadata.name == 'check2' + + - name: Get all events + event_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == 2 diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_event/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_event/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_event/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_filter/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_filter/converge.yml new file mode 100644 index 00000000..6ae4f03a --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_filter/converge.yml @@ -0,0 +1,150 @@ +--- +- name: Converge + collections: + - sensu.sensu_go + hosts: all + gather_facts: no + tasks: + - name: Create filter with missing required parameters + filter: + auth: + url: http://localhost:8080 + name: filter + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "result.msg == 'state is present but all of the following are missing: action, expressions'" + + - name: Create filter with minimal parameters + filter: + auth: + url: http://localhost:8080 + name: minimal_filter + action: allow + expressions: + - event.check.occurrences == 1 + register: result + + - assert: + that: + - result is changed + - result.object.action == 'allow' + - result.object.expressions == ['event.check.occurrences == 1'] + - result.object.metadata.name == 'minimal_filter' + + - name: Create filter with minimal parameters idempotence + filter: + auth: + url: http://localhost:8080 + name: minimal_filter + action: allow + expressions: + - event.check.occurences == 1 + register: result + + - name: Create a filter + filter: + auth: + url: http://localhost:8080 + name: filter + action: deny + expressions: + - event.check.interval == 10 + - event.check.occurrences == 1 + runtime_assets: awesomeness + register: result + + - assert: + that: + - result is changed + - result.object.action == 'deny' + - result.object.expressions == ['event.check.interval == 10', 'event.check.occurrences == 1'] + - result.object.runtime_assets == ['awesomeness'] + - result.object.metadata.name == 'filter' + + - name: Test filter creation idempotence + filter: + auth: + url: http://localhost:8080 + name: filter + action: deny + expressions: + - event.check.interval == 10 + - event.check.occurrences == 1 + runtime_assets: awesomeness + register: result + + - assert: + that: result is not changed + + - name: Create a second filter + filter: + auth: + url: http://localhost:8080 + name: filter2 + action: allow + expressions: event.check.interval == 10 + + - name: Fetch all filters + filter_info: + auth: + url: http://localhost:8080 + + - name: Fetch a specific filter + filter_info: + auth: + url: http://localhost:8080 + name: filter + register: result + + - assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'filter' + + - name: Modify a filter + filter: + auth: + url: http://localhost:8080 + name: filter + action: deny + expressions: event.check.interval > 10 + register: result + + - assert: + that: + - result is changed + - result.object.expressions == ['event.check.interval > 10'] + - not result.object.runtime_assets + + - name: Delete a filter + filter: + auth: + url: http://localhost:8080 + name: filter + state: absent + + - name: Get all filters + filter_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == 2 + - result.objects.0.metadata.name == 'filter2' + + - name: Try to fetch non-existing filter + filter_info: + auth: + url: http://localhost:8080 + name: bad-bad-filter + register: result + + - assert: + that: + - result.objects == [] diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_filter/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_filter/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_filter/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_handler_set/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_handler_set/converge.yml new file mode 100644 index 00000000..5a71e7e9 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_handler_set/converge.yml @@ -0,0 +1,109 @@ +--- +- name: Converge + collections: + - sensu.sensu_go + hosts: all + gather_facts: no + tasks: + - name: Create handler set + handler_set: + auth: + url: http://localhost:8080 + name: handler + handlers: + - udp_handler + - tcp_handler + register: result + + - assert: + that: + - result is changed + - result.object.handlers == ['udp_handler', 'tcp_handler'] + - result.object.metadata.name == 'handler' + + - name: Test handler set creation idempotence + handler_set: + auth: + url: http://localhost:8080 + name: handler + handlers: + - udp_handler + - tcp_handler + register: result + + - assert: + that: result is not changed + + - name: Modify handler set + handler_set: + auth: + url: http://localhost:8080 + name: handler + handlers: + - slack + register: result + + - assert: + that: + - result is changed + - result.object.handlers == ['slack'] + + - name: Fetch all set handlers + handler_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == 1 + + - name: Fetch a specific handler set + handler_info: + auth: + url: http://localhost:8080 + name: handler + register: result + + - assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'handler' + + - name: Delete handler set + handler_set: + auth: + url: http://localhost:8080 + name: handler + state: absent + + - name: Get the Sensu Go backend's version + uri: + url: http://localhost:8080/version + return_content: true + register: backend_version + + - name: Get all handlers set + handler_info: + auth: + url: http://localhost:8080 + register: result + # Sensu Go 5.15.0 returns none when no handler sets are present + when: (backend_version.content | from_json).sensu_backend != '5.15.0' + + - assert: + that: + - result.objects | length == 0 + # Sensu Go 5.15.0 returns none when no handler sets are present + when: (backend_version.content | from_json).sensu_backend != '5.15.0' + + - name: Try to fetch non-existing handler + handler_info: + auth: + url: http://localhost:8080 + name: bad-bad-handler + register: result + + - assert: + that: + - result.objects == [] diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_handler_set/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_handler_set/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_handler_set/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_hook/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_hook/converge.yml new file mode 100644 index 00000000..160c27d6 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_hook/converge.yml @@ -0,0 +1,139 @@ +--- +- name: Converge + collections: + - sensu.sensu_go + hosts: all + gather_facts: no + tasks: + - name: Create hook with minimal parameters + hook: + auth: + url: http://localhost:8080 + name: minimal_hook + command: /bin/true + timeout: 10 + register: result + + - assert: + that: + - result is changed + - result.object.command == '/bin/true' + - result.object.metadata.name == 'minimal_hook' + + - name: Create hook with minimal parameters idempotence + hook: + auth: + url: http://localhost:8080 + name: minimal_hook + command: /bin/true + timeout: 10 + register: result + + - assert: + that: + - result is not changed + + - name: Create a hook + hook: + auth: + url: http://localhost:8080 + name: hook + command: /bin/true + timeout: 30 + stdin: yes + runtime_assets: + - ruby2.4 + register: result + + - assert: + that: + - result is changed + - result.object.command == '/bin/true' + - result.object.timeout == 30 + - result.object.stdin == True + - result.object.runtime_assets == ['ruby2.4'] + - result.object.metadata.name == 'hook' + + - name: Test hook creation idempotence + hook: + auth: + url: http://localhost:8080 + name: hook + command: /bin/true + timeout: 30 + stdin: yes + runtime_assets: + - ruby2.4 + register: result + + - assert: + that: result is not changed + + - name: Modify a hook + hook: + auth: + url: http://localhost:8080 + name: hook + command: python -c "print('Modified.')" + timeout: 60 + register: result + + - assert: + that: + - result is changed + - not result.object.stdin + - not result.object.runtime_assets + + - name: Create a second hook + hook: + auth: + url: http://localhost:8080 + name: hook2 + command: sensu-influxdb-hook + timeout: 30 + + - name: Fetch all hooks + hook_info: + auth: + url: http://localhost:8080 + + - name: Fetch a specific hook + hook_info: + auth: + url: http://localhost:8080 + name: hook + register: result + + - assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'hook' + + - name: Delete a hook + hook: + auth: + url: http://localhost:8080 + name: hook + state: absent + + - name: Fetch all hooks + hook_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == 2 + - result.objects.0.metadata.name == 'hook2' + + - name: Try to fetch non-existing hook + hook_info: + auth: + url: http://localhost:8080 + name: bad-bad-hook + register: result + + - assert: + that: + - result.objects == [] diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_hook/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_hook/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_hook/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_ldap_auth_provider/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_ldap_auth_provider/converge.yml new file mode 100644 index 00000000..053bdf82 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_ldap_auth_provider/converge.yml @@ -0,0 +1,358 @@ +--- +- name: Converge + hosts: all + gather_facts: no + tasks: + - name: Fetch all LDAP auth providers and verify the presence + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 0 + + - name: Fail when trying to create a LDAP auth provider with missing required params + sensu.sensu_go.ldap_auth_provider: + auth: + url: http://localhost:8080 + name: openldap + register: result + ignore_errors: true + + - ansible.builtin.assert: + that: + - result is failed + - "'state is present but all of the following are missing' in result.msg" + + - name: Create LDAP auth provider with minimal params (check mode) + sensu.sensu_go.ldap_auth_provider: &create-provider + state: present + name: openldap + servers: + - host: 127.0.0.1 + group_search: + base_dn: dc=acme,dc=org + user_search: + base_dn: dc=acme,dc=org + check_mode: true + register: result + + - ansible.builtin.assert: &create-provider-assertions + that: + - result is changed + - result.object.metadata.name == 'openldap' + - result.object.servers.0.host == '127.0.0.1' + - result.object.servers.0.group_search.base_dn == 'dc=acme,dc=org' + - result.object.servers.0.user_search.base_dn == 'dc=acme,dc=org' + + - name: Make sure LDAP auth provider was not created when running in check mode + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 0 + + - name: Create LDAP auth provider with minimal params + sensu.sensu_go.ldap_auth_provider: *create-provider + register: result + + - ansible.builtin.assert: *create-provider-assertions + + - name: Make sure LDAP auth provider was created + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + name: openldap + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'openldap' + + - name: Idempotence check for LDAP auth provider creation with minimal params + sensu.sensu_go.ldap_auth_provider: *create-provider + register: result + + - ansible.builtin.assert: + that: + - result is not changed + + - name: Update LDAP auth provider (check mode) + sensu.sensu_go.ldap_auth_provider: &update-provider + state: present + name: openldap + servers: + - host: 127.0.0.1 + group_search: + base_dn: dc=acme,dc=org + user_search: + base_dn: dc=acme,dc=org + groups_prefix: dev + check_mode: true + register: result + + - ansible.builtin.assert: &update-provider-assertions + that: + - result is changed + - result.object.metadata.name == 'openldap' + - result.object.servers.0.host == '127.0.0.1' + - result.object.servers.0.group_search.base_dn == 'dc=acme,dc=org' + - result.object.servers.0.user_search.base_dn == 'dc=acme,dc=org' + - result.object.groups_prefix == 'dev' + + - name: Make sure LDAP auth provider was not updated in check mode + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + name: openldap + register: result + + - ansible.builtin.assert: + that: + - result.objects.0.metadata.name == 'openldap' + - result.objects.0.servers.0.host == '127.0.0.1' + - result.objects.0.servers.0.group_search.base_dn == 'dc=acme,dc=org' + - result.objects.0.servers.0.user_search.base_dn == 'dc=acme,dc=org' + - result.objects.0.groups_prefix == '' + + - name: Update LDAP auth provider + sensu.sensu_go.ldap_auth_provider: *update-provider + register: result + + - ansible.builtin.assert: *update-provider-assertions + + - name: Make sure LDAP auth provider was updated + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + name: openldap + register: result + + - ansible.builtin.assert: + that: + - result.objects.0.metadata.name == 'openldap' + - result.objects.0.servers.0.host == '127.0.0.1' + - result.objects.0.servers.0.group_search.base_dn == 'dc=acme,dc=org' + - result.objects.0.servers.0.user_search.base_dn == 'dc=acme,dc=org' + - result.objects.0.groups_prefix == 'dev' + + - name: Idempotence check for LDAP auth provider modification + sensu.sensu_go.ldap_auth_provider: *update-provider + register: result + + - ansible.builtin.assert: + that: + - result is not changed + + - name: Add LDAP auth provider server to existing one (check mode) + sensu.sensu_go.ldap_auth_provider: &create-extra-provider + state: present + name: openldap + servers: + - host: 127.0.0.1 + group_search: + base_dn: dc=acme,dc=org + user_search: + base_dn: dc=acme,dc=org + - host: 127.0.0.2 + group_search: + base_dn: dc=acme,dc=org + user_search: + base_dn: dc=acme,dc=org + groups_prefix: dev + check_mode: true + register: result + + - ansible.builtin.assert: &create-extra-provider-assertions + that: + - result is changed + - result.object.metadata.name == 'openldap' + - result.object.servers.0.host == '127.0.0.1' + - result.object.servers.1.host == '127.0.0.2' + - result.object.servers.1.group_search.base_dn == 'dc=acme,dc=org' + - result.object.servers.1.user_search.base_dn == 'dc=acme,dc=org' + + - name: Make sure extra LDAP auth provider was not created when running in check mode + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.servers | length == 1 + + - name: Create an extra LDAP auth provider + sensu.sensu_go.ldap_auth_provider: *create-extra-provider + register: result + + - ansible.builtin.assert: *create-extra-provider-assertions + + - name: Make sure extra LDAP auth provider was created + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + name: openldap + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.servers | length == 2 + - result.objects.0.metadata.name == 'openldap' + + - name: Idempotence check for extra LDAP auth provider creation + sensu.sensu_go.ldap_auth_provider: *create-extra-provider + register: result + + - ansible.builtin.assert: + that: + - result is not changed + + - name: Create a LDAP auth provider with all params + sensu.sensu_go.ldap_auth_provider: &create-provider-all-params + auth: + url: http://localhost:8080 + state: present + name: other-openldap + servers: + - host: 127.0.0.1 + port: 636 + insecure: false + security: tls + trusted_ca_file: /path/to/trusted-certificate-authorities.pem + client_cert_file: /path/to/ssl/cert.pem + client_key_file: /path/to/ssl/key.pem + binding: + user_dn: cn=binder,dc=acme,dc=org + password: ldap_password + group_search: + base_dn: dc=acme,dc=org + attribute: member + name_attribute: cn + object_class: groupOfNames + user_search: + base_dn: dc=acme,dc=org + attribute: uid + name_attribute: cn + object_class: person + groups_prefix: dev + username_prefix: ldap + register: result + + - ansible.builtin.assert: + that: + - result is changed + - result.object.metadata.name == 'other-openldap' + - result.object.servers.0.host == '127.0.0.1' + - result.object.servers.0.port == 636 + - result.object.servers.0.insecure == false + - result.object.servers.0.security == 'tls' + - result.object.servers.0.trusted_ca_file == '/path/to/trusted-certificate-authorities.pem' + - result.object.servers.0.client_cert_file == '/path/to/ssl/cert.pem' + - result.object.servers.0.client_key_file == '/path/to/ssl/key.pem' + - result.object.servers.0.binding.user_dn == 'cn=binder,dc=acme,dc=org' + - "'password' not in result.object.servers.0.binding" + - result.object.servers.0.group_search.base_dn == 'dc=acme,dc=org' + - result.object.servers.0.group_search.attribute == 'member' + - result.object.servers.0.group_search.name_attribute == 'cn' + - result.object.servers.0.group_search.object_class == 'groupOfNames' + - result.object.servers.0.user_search.base_dn == 'dc=acme,dc=org' + - result.object.servers.0.user_search.attribute == 'uid' + - result.object.servers.0.user_search.name_attribute == 'cn' + - result.object.servers.0.user_search.object_class == 'person' + - result.object.groups_prefix == 'dev' + - result.object.username_prefix == 'ldap' + + - name: Idempotence check for LDAP auth provider creation with all params + sensu.sensu_go.ldap_auth_provider: *create-provider-all-params + register: result + + - ansible.builtin.assert: + that: + - result is not changed + + - name: Delete LDAP auth provider (check mode) + sensu.sensu_go.ldap_auth_provider: &delete-provider + auth: + url: http://localhost:8080 + name: openldap + state: absent + check_mode: true + register: result + + - ansible.builtin.assert: + that: + - result is changed + + - name: Make sure LDAP auth provider was not deleted when running in check mode + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + name: openldap + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 1 + + - name: Delete LDAP auth provider + sensu.sensu_go.ldap_auth_provider: *delete-provider + register: result + + - ansible.builtin.assert: + that: + - result is changed + + - name: Make sure LDAP auth provider was deleted + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + name: openldap + register: result + + - ansible.builtin.assert: + that: + - result.objects == [] + + - name: Check if still any LDAP auth providers exist + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 1 + + - name: Delete LDAP auth provider + sensu.sensu_go.ldap_auth_provider: + auth: + url: http://localhost:8080 + name: other-openldap + state: absent + check_mode: true + register: result + + - ansible.builtin.assert: + that: + - result is changed + + - name: Delete a non-existent LDAP auth provider + sensu.sensu_go.ldap_auth_provider: + auth: + url: http://localhost:8080 + name: i-dont-exist + state: absent + register: result + + - ansible.builtin.assert: + that: + - result is not changed diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_ldap_auth_provider/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_ldap_auth_provider/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_ldap_auth_provider/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_mutator/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_mutator/converge.yml new file mode 100644 index 00000000..52db18f8 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_mutator/converge.yml @@ -0,0 +1,144 @@ +--- +- name: Converge + collections: + - sensu.sensu_go + hosts: all + gather_facts: no + tasks: + - name: Create mutator with minimal parameters + mutator: + auth: + url: http://localhost:8080 + name: minimal_mutator + command: /bin/true + register: result + + - assert: + that: + - result is changed + - result.object.command == '/bin/true' + - result.object.metadata.name == 'minimal_mutator' + + - name: Create mutator with minimal parameters idempotence + mutator: + auth: + url: http://localhost:8080 + name: minimal_mutator + command: /bin/true + register: result + + - assert: + that: + - result is not changed + + - name: Create a mutator + mutator: + auth: + url: http://localhost:8080 + name: mutator + command: sensu-influxdb-mutator + timeout: 30 + env_vars: + INFLUXDB_ADDR: http://influxdb.default.svc.cluster.local:8086 + INFLUXDB_USER: sensu + runtime_assets: + - sensu-influxdb-mutator + register: result + + - assert: + that: + - result is changed + - result.object.command == 'sensu-influxdb-mutator' + - result.object.timeout == 30 + - result.object.runtime_assets == ['sensu-influxdb-mutator'] + - result.object.metadata.name == 'mutator' + + - name: Test mutator creation idempotence + mutator: + auth: + url: http://localhost:8080 + name: mutator + command: sensu-influxdb-mutator + timeout: 30 + env_vars: + INFLUXDB_ADDR: http://influxdb.default.svc.cluster.local:8086 + INFLUXDB_USER: sensu + runtime_assets: + - sensu-influxdb-mutator + register: result + + - assert: + that: result is not changed + + - name: Modify a mutator + mutator: + auth: + url: http://localhost:8080 + name: mutator + command: sensu-influxdb-mutator + timeout: 60 + secrets: + - name: test + secret: value + register: result + + - assert: + that: + - result is changed + - not result.object.env_vars + - not result.object.runtime_assets + - "result.object.secrets == [{'name': 'test', 'secret': 'value'}]" + + - name: Create a second mutator + mutator: + auth: + url: http://localhost:8080 + name: mutator2 + command: sensu-influxdb-mutator + timeout: 30 + + - name: Fetch all mutators + mutator_info: + auth: + url: http://localhost:8080 + + - name: Fetch a specific mutator + mutator_info: + auth: + url: http://localhost:8080 + name: mutator + register: result + + - assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'mutator' + + - name: Delete a mutator + mutator: + auth: + url: http://localhost:8080 + name: mutator + state: absent + + - name: Fetch all mutators + mutator_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == 2 + - result.objects.0.metadata.name == 'minimal_mutator' + + - name: Try to fetch non-existing mutator + mutator_info: + auth: + url: http://localhost:8080 + name: bad-bad-mutator + register: result + + - assert: + that: + - result.objects == [] diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_mutator/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_mutator/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_mutator/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_namespace/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_namespace/converge.yml new file mode 100644 index 00000000..e7f332e2 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_namespace/converge.yml @@ -0,0 +1,78 @@ +--- +- name: Converge + collections: + - sensu.sensu_go + hosts: all + gather_facts: no + tasks: + - name: Fetch all namespaces + namespace_info: + auth: + url: http://localhost:8080 + register: default_state + + - name: store starting length + set_fact: + starting_length: "{{ default_state.objects | length }}" + + - name: Create namespace + namespace: + auth: + url: http://localhost:8080 + name: dev + register: result + + - assert: + that: + - result is changed + - result.object.name == 'dev' + + - name: Create namespace idempotence + namespace: + auth: + url: http://localhost:8080 + name: dev + register: result + + - assert: + that: + - result is not changed + + - name: Create a second namespace + namespace: + auth: + url: http://localhost:8080 + name: production + + - name: Fetch all namespaces + namespace_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == expected_length | int + - result.objects.1.name == 'dev' + vars: + expected_length: "{{ starting_length|int + 2 }}" + + - name: Delete namespace + namespace: + auth: + url: http://localhost:8080 + name: dev + state: absent + + - name: Fetch all namespaces + namespace_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == expected_length | int + - result.objects.1.name == 'production' + vars: + expected_length: "{{ starting_length|int + 1 }}" diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_namespace/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_namespace/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_namespace/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_oidc_auth_provider/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_oidc_auth_provider/converge.yml new file mode 100644 index 00000000..89f42f48 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_oidc_auth_provider/converge.yml @@ -0,0 +1,276 @@ +--- +- name: Converge + hosts: all + gather_facts: no + tasks: + - name: Fetch all auth providers and verify the presence + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 0 + + - name: Fail when trying to create a OIDC auth provider with missing required params + sensu.sensu_go.oidc_auth_provider: + auth: + url: http://localhost:8080 + name: oidc_name + register: result + ignore_errors: true + + - ansible.builtin.assert: + that: + - result is failed + - "'state is present but all of the following are missing' in result.msg" + + - name: Create OIDC auth provider with minimal params (check mode) + sensu.sensu_go.oidc_auth_provider: &create-provider + state: present + name: oidc_name + client_id: a8e43af034e7f2608780 + client_secret: b63968394be6ed2edb61c93847ee792f31bf6216 + server: https://oidc.example.com:9031 + username_claim: email + check_mode: true + register: result + + - ansible.builtin.assert: &create-provider-assertions + that: + - result is changed + - result.object.metadata.name == 'oidc_name' + - result.object.client_id == 'a8e43af034e7f2608780' + - "'client_secret' not in result.object" + - result.object.server == 'https://oidc.example.com:9031' + - result.object.username_claim == 'email' + + - name: Make sure OIDC auth provider was not created when running in check mode + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 0 + + - name: Create OIDC auth provider with minimal params + sensu.sensu_go.oidc_auth_provider: *create-provider + register: result + + - ansible.builtin.assert: *create-provider-assertions + + - name: Make sure OIDC auth provider was created + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + name: oidc_name + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'oidc_name' + + - name: Idempotence check for OIDC auth provider creation with minimal params + sensu.sensu_go.oidc_auth_provider: *create-provider + register: result + + - ansible.builtin.assert: + that: + - result is not changed + + - name: Update OIDC auth provider (check mode) + sensu.sensu_go.oidc_auth_provider: &update-provider + state: present + name: oidc_name + additional_scopes: + - groups + - email + - username + client_id: a8e43af034e7f2608780 + client_secret: b63968394be6ed2edb61c93847ee792f31bf6216 + server: https://oidc.example.com:9031 + username_claim: email + check_mode: true + register: result + + - ansible.builtin.assert: &update-provider-assertions + that: + - result is changed + - result.object.metadata.name == 'oidc_name' + - result.object.additional_scopes == ['groups', 'email', 'username'] + - result.object.client_id == 'a8e43af034e7f2608780' + - "'client_secret' not in result.object" + - result.object.server == 'https://oidc.example.com:9031' + - result.object.username_claim == 'email' + + - name: Make sure OIDC auth provider was not updated in check mode + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + name: oidc_name + register: result + + - ansible.builtin.assert: + that: + - result.objects.0.metadata.name == 'oidc_name' + - result.objects.0.additional_scopes == ['openid'] + - result.objects.0.client_id == 'a8e43af034e7f2608780' + - "'client_secret' not in result.objects.0" + - result.objects.0.server == 'https://oidc.example.com:9031' + - result.objects.0.username_claim == 'email' + + - name: Update OIDC auth provider + sensu.sensu_go.oidc_auth_provider: *update-provider + register: result + + - ansible.builtin.assert: *update-provider-assertions + + - name: Make sure OIDC auth provider was updated + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + name: oidc_name + register: result + + - ansible.builtin.assert: + that: + - result.objects.0.metadata.name == 'oidc_name' + - result.objects.0.additional_scopes == ['groups', 'email', 'username'] + - result.objects.0.client_id == 'a8e43af034e7f2608780' + - "'client_secret' not in result.objects.0" + - result.objects.0.server == 'https://oidc.example.com:9031' + - result.objects.0.username_claim == 'email' + + - name: Idempotence check for OIDC auth provider modification + sensu.sensu_go.oidc_auth_provider: *update-provider + register: result + + - ansible.builtin.assert: + that: + - result is not changed + + - name: Create a OIDC auth provider with all params + sensu.sensu_go.oidc_auth_provider: &create-provider-all-params + auth: + url: http://localhost:8080 + state: present + name: other-oidc_name + additional_scopes: + - groups + - email + - username + client_id: a8e43af034e7f2608780 + client_secret: b63968394be6ed2edb61c93847ee792f31bf6216 + disable_offline_access: false + redirect_uri: http://127.0.0.1:8080/api/enterprise/authentication/v2/oidc/callback + server: https://oidc.example.com:9031 + groups_claim: groups + groups_prefix: 'oidc:' + username_claim: email + username_prefix: 'oidc:' + register: result + + - ansible.builtin.assert: + that: + - result is changed + - result.object.metadata.name == 'other-oidc_name' + - result.object.additional_scopes == ['groups', 'email', 'username'] + - result.object.client_id == 'a8e43af034e7f2608780' + - "'client_secret' not in result.object" + - result.object.disable_offline_access == false + - result.object.redirect_uri == 'http://127.0.0.1:8080/api/enterprise/authentication/v2/oidc/callback' + - result.object.server == 'https://oidc.example.com:9031' + - result.object.groups_claim == 'groups' + - result.object.groups_prefix == 'oidc:' + - result.object.username_claim == 'email' + - result.object.username_prefix == 'oidc:' + + - name: Idempotence check for OIDC auth provider creation with all params + sensu.sensu_go.oidc_auth_provider: *create-provider-all-params + register: result + + - ansible.builtin.assert: + that: + - result is not changed + + - name: Delete OIDC auth provider (check mode) + sensu.sensu_go.oidc_auth_provider: &delete-provider + auth: + url: http://localhost:8080 + name: oidc_name + state: absent + check_mode: true + register: result + + - ansible.builtin.assert: + that: + - result is changed + + - name: Make sure OIDC auth provider was not deleted when running in check mode + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + name: oidc_name + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 1 + + - name: Delete OIDC auth provider + sensu.sensu_go.oidc_auth_provider: *delete-provider + register: result + + - ansible.builtin.assert: + that: + - result is changed + + - name: Make sure OIDC auth provider was deleted + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + name: oidc_name + register: result + + - ansible.builtin.assert: + that: + - result.objects == [] + + - name: Check if still any OIDC auth providers exist + sensu.sensu_go.auth_provider_info: + auth: + url: http://localhost:8080 + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 1 + + - name: Delete OIDC auth provider + sensu.sensu_go.oidc_auth_provider: + auth: + url: http://localhost:8080 + name: other-oidc_name + state: absent + check_mode: true + register: result + + - ansible.builtin.assert: + that: + - result is changed + + - name: Delete a non-existent OIDC auth provider + sensu.sensu_go.oidc_auth_provider: + auth: + url: http://localhost:8080 + name: i-dont-exist + state: absent + register: result + + - ansible.builtin.assert: + that: + - result is not changed diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_oidc_auth_provider/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_oidc_auth_provider/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_oidc_auth_provider/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_pipe_handler/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_pipe_handler/converge.yml new file mode 100644 index 00000000..8fe9f068 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_pipe_handler/converge.yml @@ -0,0 +1,129 @@ +--- +- name: Converge + collections: + - sensu.sensu_go + hosts: all + gather_facts: no + tasks: + - name: Create pipe handler with minimal parameters + pipe_handler: + auth: + url: http://localhost:8080 + name: minimal_handler + command: sensu-influxdb-handler + register: result + + - assert: + that: + - result is changed + - result.object.command == 'sensu-influxdb-handler' + - result.object.metadata.name == 'minimal_handler' + + - name: Create pipe handler with minimal parameters idempotence + pipe_handler: + auth: + url: http://localhost:8080 + name: minimal_handler + command: sensu-influxdb-handler + register: result + + - assert: + that: result is not changed + + - name: Create pipe handler + pipe_handler: &handler + auth: + url: http://localhost:8080 + name: handler + command: sensu-influxdb-handler + mutator: mutate_input + timeout: 30 + filters: + - occurances + - production + env_vars: + INFLUXDB_ADDR: http://influxdb.default.svc.cluster.local:8086 + INFLUXDB_USER: sensu + INFLUXDB_PASS: password + runtime_assets: + - sensu-influxdb-handler + secrets: + - name: test + secret: value + register: result + + - debug: + msg: "{{result}}" + + - assert: + that: + - result is changed + - result.object.command == 'sensu-influxdb-handler' + - result.object.mutator == 'mutate_input' + - result.object.timeout == 30 + - result.object.filters == ['occurances', 'production'] + - result.object.runtime_assets == ['sensu-influxdb-handler'] + - result.object.metadata.name == 'handler' + - "result.object.secrets == [{'name': 'test', 'secret': 'value'}]" + + - name: Test pipe handler creation idempotence + pipe_handler: *handler + register: result + + - assert: + that: result is not changed + + - name: Modify pipe handler + pipe_handler: + auth: + url: http://localhost:8080 + name: handler + command: sensu-influxdb-handler + timeout: 60 + register: result + + - assert: + that: + - result is changed + - not result.object.env_vars + - not result.object.runtime_assets + + - name: Fetch all pipe handlers + handler_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == 2 + + - name: Fetch a specific pipe handler + handler_info: + auth: + url: http://localhost:8080 + name: handler + register: result + + - assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'handler' + + - name: Delete pipe handler + pipe_handler: + auth: + url: http://localhost:8080 + name: handler + state: absent + + - name: Get all pipe handlers + handler_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'minimal_handler' diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_pipe_handler/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_pipe_handler/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_pipe_handler/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_role/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_role/converge.yml new file mode 100644 index 00000000..b1871e70 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_role/converge.yml @@ -0,0 +1,220 @@ +--- +- name: Converge + collections: + - sensu.sensu_go + hosts: all + gather_facts: no + tasks: + - name: Create a role with missing required parameters + role: + auth: + url: http://localhost:8080 + name: test_role + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "result.msg == 'state is present but all of the following are missing: rules'" + + - name: Create a role with empty rules + role: + auth: + url: http://localhost:8080 + name: test_role + rules: [] + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "result.msg == 'state is present but all of the following are missing: rules'" + + - name: Create a role with invalid rule verbs + role: + auth: + url: http://localhost:8080 + name: test_role + rules: + - verbs: + - list + - do_something + resources: + - entities + ignore_errors: true + register: result + + - assert: + that: + - result is failed + + - name: Create a role with minimal parameters + role: + auth: + url: http://localhost:8080 + name: minimal_test_role + rules: + - verbs: + - get + - list + resources: + - entities + register: result + + - assert: + that: + - result is changed + - result.object.metadata.name == 'minimal_test_role' + - result.object.rules | length == 1 + - result.object.rules.0.verbs | length == 2 + - result.object.rules.0.verbs == ['get', 'list'] + - result.object.rules.0.resources == ['entities'] + + - name: Check idempotence of role creation with minimal parameters + role: + auth: + url: http://localhost:8080 + name: minimal_test_role + rules: + - verbs: + - list + - get + resources: + - entities + register: result + + - assert: + that: result is not changed + + - name: Create a role + role: + auth: + url: http://localhost:8080 + name: test_role + rules: + - verbs: + - list + resources: + - assets + - checks + resource_names: + - some_resource_1 + - some_resource_2 + - verbs: + - list + - get + resources: + - checks + register: result + + - assert: + that: + - result is changed + - result.object.metadata.name == 'test_role' + - result.object.rules | length == 2 + - result.object.rules.0.verbs | length == 1 + - result.object.rules.0.verbs == ['list'] + - result.object.rules.0.resources == ['assets', 'checks'] + - result.object.rules.0.resource_names == ['some_resource_1', 'some_resource_2'] + - result.object.rules.1.verbs == ['list', 'get'] + - result.object.rules.1.resources == ['checks'] + - not result.object.rules.1.resource_names + + - name: Check idempotence of role creation + role: + auth: + url: http://localhost:8080 + name: test_role + rules: + - verbs: + - list + resources: + - checks + - assets + resource_names: + - some_resource_2 + - some_resource_1 + - verbs: + - get + - list + resources: + - checks + register: result + + - assert: + that: + - result is not changed + + - name: Modify a role + role: + auth: + url: http://localhost:8080 + name: test_role + rules: + - verbs: + - list + resources: + - assets + register: result + + - assert: + that: + - result is changed + - result.object.rules | length == 1 + - result.object.rules.0.verbs == ['list'] + - result.object.rules.0.resources == ['assets'] + - not result.object.rules.0.resource_names + + - name: Fetch all roles + role_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + # We created two roles but there might be others present by default + - result.objects | length >= 2 + + - name: Fetch a specific role + role_info: + auth: + url: http://localhost:8080 + name: test_role + register: result + + - assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'test_role' + + - name: Delete a role + role: + auth: + url: http://localhost:8080 + state: absent + name: minimal_test_role + register: result + + - name: Fetch all roles after deletion of a role + role_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length >= 1 + + - name: Try to fetch non-existing role + role_info: + auth: + url: http://localhost:8080 + name: bad-bad-role + register: result + + - assert: + that: + - result.objects == [] diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_role/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_role/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_role/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_role_binding/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_role_binding/converge.yml new file mode 100644 index 00000000..25f79676 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_role_binding/converge.yml @@ -0,0 +1,230 @@ +--- +- name: Converge + collections: + - sensu.sensu_go + hosts: all + gather_facts: no + tasks: + - name: Create a role binding with missing required parameters + role_binding: + auth: + url: http://localhost:8080 + name: test_role_binding + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "result.msg == 'state is present but any of the following are missing: role, cluster_role'" + + - name: Create a role binding with mutually exclusive role and cluster_role + role_binding: + auth: + url: http://localhost:8080 + name: test_role_binding + role: test_role + cluster_role: test_cluster_role + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "result.msg == 'parameters are mutually exclusive: role|cluster_role'" + + - name: Create a role binding without providing users or groups + role_binding: + auth: + url: http://localhost:8080 + name: test_role_binding + role: test_role + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "result.msg == 'missing required parameters: users or groups'" + + - name: Create a role binding with minimal parameters (users only) + role_binding: + auth: + url: http://localhost:8080 + name: minimal_test_role_binding_users + role: test_role + users: + - test_user + register: result + + - assert: + that: + - result is changed + - result.object.metadata.name == 'minimal_test_role_binding_users' + - result.object.role_ref.name == 'test_role' + - result.object.subjects | length == 1 + - result.object.subjects.0.name == 'test_user' + - result.object.subjects.0.type == 'User' + + - name: Create a role binding with minimal parameters (groups only) + role_binding: + auth: + url: http://localhost:8080 + name: minimal_test_role_binding_groups + role: test_role + groups: + - test_group_1 + - test_group_2 + register: result + + - assert: + that: + - result is changed + - result.object.metadata.name == 'minimal_test_role_binding_groups' + - result.object.role_ref.name == 'test_role' + - result.object.subjects | length == 2 + + - name: Check idempotence of role binding creation with minimal parameters + role_binding: + auth: + url: http://localhost:8080 + name: minimal_test_role_binding_groups + role: test_role + groups: + - test_group_2 + - test_group_1 + register: result + + - assert: + that: result is not changed + + - name: Create a role binding + role_binding: + auth: + url: http://localhost:8080 + name: test_role_binding + role: test_role + users: + - test_user_1 + - test_user_2 + groups: + - test_group_1 + - test_group_2 + register: result + + - assert: + that: + - result is changed + - result.object.metadata.name == 'test_role_binding' + - result.object.role_ref.name == 'test_role' + - result.object.subjects | length == 4 + + - name: Check idempotence of role binding creation + role_binding: + auth: + url: http://localhost:8080 + name: test_role_binding + role: test_role + users: + - test_user_2 + - test_user_1 + groups: + - test_group_2 + - test_group_1 + register: result + + - assert: + that: + - result is not changed + + - name: Modify a role binding + role_binding: + auth: + url: http://localhost:8080 + name: test_role_binding + cluster_role: test_cluster_role + users: + groups: + - group_1 + register: result + + - assert: + that: + - result is changed + - result.object.metadata.name == 'test_role_binding' + - result.object.role_ref.name == 'test_cluster_role' + - result.object.role_ref.type == 'ClusterRole' + - result.object.subjects | length == 1 + - result.object.subjects.0.name == 'group_1' + - result.object.subjects.0.type == 'Group' + + - name: Create a role binding with cluster role + role_binding: + auth: + url: http://localhost:8080 + name: test_role_binding_with_cluster_role + cluster_role: test_cluster_role + users: + - user_1 + register: result + + - assert: + that: + - result is changed + - result.object.metadata.name == 'test_role_binding_with_cluster_role' + - result.object.role_ref.name == 'test_cluster_role' + - result.object.role_ref.type == 'ClusterRole' + - result.object.subjects | length == 1 + - result.object.subjects.0.name == 'user_1' + - result.object.subjects.0.type == 'User' + + - name: Fetch all role bindings + role_binding_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length >= 4 + + - name: Fetch a specific role binding + role_binding_info: + auth: + url: http://localhost:8080 + name: test_role_binding + register: result + + - assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'test_role_binding' + + - name: Delete a role binding + role_binding: + auth: + url: http://localhost:8080 + state: absent + name: test_role_binding + register: result + + - name: Fetch all roles bindings after deletion + role_binding_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length >= 3 + + - name: Try to fetch non-existing binding + role_binding_info: + auth: + url: http://localhost:8080 + name: bad-bad-binding + register: result + + - assert: + that: + - result.objects == [] diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_role_binding/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_role_binding/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_role_binding/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secret/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secret/converge.yml new file mode 100644 index 00000000..cbb02fee --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secret/converge.yml @@ -0,0 +1,171 @@ +--- +- name: Converge + hosts: all + gather_facts: false + + tasks: + - name: Make sure we start clean + sensu.sensu_go.secret_info: + register: result + - ansible.builtin.assert: + that: + - result is success + - result.objects | length == 0 + + - name: Create new secret (check mode) + sensu.sensu_go.secret: &secret + name: sample-secret + provider: env + id: MY_ENV_VAR + check_mode: true + register: result + - ansible.builtin.assert: &secret-assertions + that: + - result is changed + - result.object.metadata.name == 'sample-secret' + - result.object.id == 'MY_ENV_VAR' + - result.object.provider == 'env' + + - name: Make sure things are still clean + sensu.sensu_go.secret_info: + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 0 + + - name: Create new secret + sensu.sensu_go.secret: *secret + register: result + - ansible.builtin.assert: *secret-assertions + + - name: Make sure secret is present on the backend + sensu.sensu_go.secret_info: + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'sample-secret' + - result.objects.0.id == 'MY_ENV_VAR' + - result.objects.0.provider == 'env' + + - name: Create new secret (idempotence) + sensu.sensu_go.secret: *secret + register: result + - ansible.builtin.assert: + that: + - result is not changed + + - name: Update existing secret (check mode) + sensu.sensu_go.secret: &update + name: sample-secret + id: MY_NEW_ENV_VAR + provider: env + check_mode: true + register: result + - ansible.builtin.assert: &update-assertions + that: + - result is changed + - result.object.metadata.name == 'sample-secret' + - result.object.id == 'MY_NEW_ENV_VAR' + - result.object.provider == 'env' + + - name: Make sure secret did not change + sensu.sensu_go.secret_info: + name: sample-secret + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'sample-secret' + - result.objects.0.id == 'MY_ENV_VAR' + - result.objects.0.provider == 'env' + + - name: Update existing secret + sensu.sensu_go.secret: *update + register: result + - ansible.builtin.assert: *update-assertions + + - name: Make sure secret is updated + sensu.sensu_go.secret_info: + name: sample-secret + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'sample-secret' + - result.objects.0.id == 'MY_NEW_ENV_VAR' + - result.objects.0.provider == 'env' + + - name: Update existing secret (idempotence) + sensu.sensu_go.secret: *update + register: result + - ansible.builtin.assert: + that: + - result is not changed + + - name: Create additional secret + sensu.sensu_go.secret: + name: additional-secret + provider: hashi-vault + id: secret/database#pass + register: result + - ansible.builtin.assert: + that: + - result is changed + - result.object.metadata.name == 'additional-secret' + - result.object.id == 'secret/database#pass' + - result.object.provider == 'hashi-vault' + + - name: Make sure we have two secrets now + sensu.sensu_go.secret_info: + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 2 + + - name: Delete secret (check mode) + sensu.sensu_go.secret: &delete + name: sample-secret + state: absent + check_mode: true + register: result + - ansible.builtin.assert: + that: + - result is changed + + - name: Make sure we still have two secrets + sensu.sensu_go.secret_info: + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 2 + + - name: Delete secret + sensu.sensu_go.secret: *delete + register: result + - ansible.builtin.assert: + that: + - result is changed + + - name: Now we have only one secret + sensu.sensu_go.secret_info: + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'additional-secret' + + - name: And the sample-secret is no more + sensu.sensu_go.secret_info: + name: sample-secret + register: result + - ansible.builtin.assert: + that: + - result.objects | length == 0 + + - name: Delete secret (idempotency) + sensu.sensu_go.secret: *delete + register: result + - ansible.builtin.assert: + that: + - result is not changed diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secret/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secret/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secret/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secrets_provider_env/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secrets_provider_env/converge.yml new file mode 100644 index 00000000..db34cca9 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secrets_provider_env/converge.yml @@ -0,0 +1,127 @@ +--- +- name: Converge + hosts: all + gather_facts: no + tasks: + - name: Fetch all secrets providers and verify the presence of the + default env provider + sensu.sensu_go.secrets_provider_info: + auth: + url: http://localhost:8080 + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 1 # env provider is present by default + - result.objects.0.metadata.name == 'env' + + - name: Check idempotence of env provider creation + sensu.sensu_go.secrets_provider_env: &create-provider + auth: + url: http://localhost:8080 + register: result + + - ansible.builtin.assert: + that: + - result is not changed + + - name: Delete the default env secrets provider (check mode) + sensu.sensu_go.secrets_provider_env: &delete-provider + auth: + url: http://localhost:8080 + state: absent + check_mode: true + register: result + + - ansible.builtin.assert: + that: + - result is changed + + - name: Make sure we didn't delete the env secrets provider in check mode + sensu.sensu_go.secrets_provider_info: + auth: + url: http://localhost:8080 + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 1 + + - name: Delete the default env secrets provider + sensu.sensu_go.secrets_provider_env: *delete-provider + register: result + + - ansible.builtin.assert: + that: + - result is changed + + - name: Make sure env secrets provider was deleted + sensu.sensu_go.secrets_provider_info: + auth: + url: http://localhost:8080 + name: env + register: result + + - ansible.builtin.assert: + that: + - result.objects == [] + + - name: Delete a non-existent env secrets provider + sensu.sensu_go.secrets_provider_env: + auth: + url: http://localhost:8080 + state: absent + register: result + + - ansible.builtin.assert: + that: + - result is not changed + + - name: Fetch a non-existent secrets provider + sensu.sensu_go.secrets_provider_info: + auth: + url: http://localhost:8080 + name: env + register: result + + - ansible.builtin.assert: + that: + - result.objects == [] + + - name: Re-create the env secrets provider (check mode) + sensu.sensu_go.secrets_provider_env: *create-provider + check_mode: true + register: result + + - ansible.builtin.assert: &create-provider-assertions + that: + - result is changed + - result.object.metadata.name == 'env' + + - name: Make sure no secrets providers were created when running in check mode + sensu.sensu_go.secrets_provider_info: + auth: + url: http://localhost:8080 + register: result + + - ansible.builtin.assert: + that: + - result.objects == [] + + - name: Re-create the env secrets provider + sensu.sensu_go.secrets_provider_env: *create-provider + register: result + + - ansible.builtin.assert: *create-provider-assertions + + - name: Make sure env secrets provider was really created + sensu.sensu_go.secrets_provider_info: + auth: + url: http://localhost:8080 + name: env + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'env' diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secrets_provider_env/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secrets_provider_env/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secrets_provider_env/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secrets_provider_vault/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secrets_provider_vault/converge.yml new file mode 100644 index 00000000..a5971afc --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secrets_provider_vault/converge.yml @@ -0,0 +1,272 @@ +--- +- name: Pre-converge step - ensure presence of valid PEM files for TLS configuration + hosts: all + gather_facts: no + tasks: + # As part of configuring TLS for the vault secrets provider, we need + # to specify paths to CA cert and client cert/key files. + # If these files do not exist at the specified paths on SensuGo backend, + # or are not valid PEM files, the API returns error 500. + - name: Copy PEM files to Sensu backend + copy: + src: "files/{{ item }}" + dest: "/tmp/{{ item }}" + mode: 0744 + loop: + - ca.crt + - client.crt + - client.key + +- name: Converge + hosts: all + gather_facts: no + tasks: + - name: Fetch all secrets providers and verify the presence of the + default env provider + sensu.sensu_go.secrets_provider_info: + auth: + url: http://localhost:8080 + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 1 # env provider is present by default + - result.objects.0.metadata.name == 'env' + + - name: Fail when trying to create a vault provider with missing required params + sensu.sensu_go.secrets_provider_vault: + auth: + url: http://localhost:8080 + name: my-vault + register: result + ignore_errors: true + + - ansible.builtin.assert: + that: + - result is failed + - "'state is present but all of the following are missing' in result.msg" + + - name: Create a vault provider with minimal params (check mode) + sensu.sensu_go.secrets_provider_vault: &create-provider + auth: + url: http://localhost:8080 + name: my-vault + address: https://my-vault.com + token: VAULT_TOKEN + version: v1 + check_mode: true + register: result + + - ansible.builtin.assert: &create-provider-assertions + that: + - result is changed + - result.object.metadata.name == 'my-vault' + - result.object.client.address == 'https://my-vault.com' + - result.object.client.version == 'v1' + + - name: Make sure vault provider was not created when running in check mode + sensu.sensu_go.secrets_provider_info: + auth: + url: http://localhost:8080 + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'env' + + - name: Create a vault provider with minimal params + sensu.sensu_go.secrets_provider_vault: *create-provider + register: result + + - ansible.builtin.assert: *create-provider-assertions + + - name: Make sure vault secrets provider was created + sensu.sensu_go.secrets_provider_info: + auth: + url: http://localhost:8080 + name: my-vault + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'my-vault' + + - name: Idempotence check for vault provider creation with minimal params + sensu.sensu_go.secrets_provider_vault: *create-provider + register: result + + - ansible.builtin.assert: + that: + - result is not changed + + - name: Update vault secrets provider (check mode) + sensu.sensu_go.secrets_provider_vault: &update-provider + auth: + url: http://localhost:8080 + name: my-vault + address: https://my-new-vault.com + token: ANOTHER_VAULT_TOKEN + version: v2 + timeout: 5 + max_retries: 1 + rate_limit: 15.3 + burst_limit: 50 + tls: + ca_cert: /tmp/ca.crt + check_mode: true + register: result + + - ansible.builtin.assert: &update-provider-assertions + that: + - result is changed + - result.object.client.address == 'https://my-new-vault.com' + - result.object.client.version == 'v2' + - result.object.client.timeout == '5s' + - result.object.client.max_retries == 1 + - result.object.client.rate_limiter.limit == 15.3 + - result.object.client.rate_limiter.burst == 50 + - result.object.client.tls.ca_cert == '/tmp/ca.crt' + + - name: Make sure vault secrets provider was not updated in check mode + sensu.sensu_go.secrets_provider_info: + auth: + url: http://localhost:8080 + name: my-vault + register: result + + - ansible.builtin.assert: + that: + - result.objects.0.metadata.name == 'my-vault' + - result.objects.0.client.address == 'https://my-vault.com' + - result.objects.0.client.version == 'v1' + - result.objects.0.client.tls == None + + - name: Update vault secrets provider + sensu.sensu_go.secrets_provider_vault: *update-provider + register: result + + - ansible.builtin.assert: *update-provider-assertions + + - name: Make sure vault secrets provider was updated + sensu.sensu_go.secrets_provider_info: + auth: + url: http://localhost:8080 + name: my-vault + register: result + + - ansible.builtin.assert: + that: + - result.objects.0.client.address == 'https://my-new-vault.com' + - result.objects.0.client.version == 'v2' + - result.objects.0.client.timeout == '5s' + - result.objects.0.client.max_retries == 1 + - result.objects.0.client.rate_limiter.limit == 15.3 + - result.objects.0.client.rate_limiter.burst == 50 + - result.objects.0.client.tls.ca_cert == '/tmp/ca.crt' + + - name: Idempotence check for vault provider modification + sensu.sensu_go.secrets_provider_vault: *update-provider + register: result + + - ansible.builtin.assert: + that: + - result is not changed + + - name: Create a vault secrets provider with all params + sensu.sensu_go.secrets_provider_vault: &create-provider-all-params + auth: + url: http://localhost:8080 + name: my-other-vault + address: https://my-other-vault.com + token: OTHER_VAULT_TOKEN + version: v1 + timeout: 30 + max_retries: 1 + rate_limit: 5.2 + burst_limit: 90 + tls: + ca_cert: /tmp/ca.crt + client_cert: /tmp/client.crt + client_key: /tmp/client.key + cname: my-vault.com + register: result + + - ansible.builtin.assert: + that: + - result is changed + - result.object.metadata.name == 'my-other-vault' + - result.object.client.address == 'https://my-other-vault.com' + - result.object.client.version == 'v1' + - result.object.client.timeout == '30s' + - result.object.client.max_retries == 1 + - result.object.client.rate_limiter.limit == 5.2 + - result.object.client.rate_limiter.burst == 90 + - result.object.client.tls.ca_cert == '/tmp/ca.crt' + - result.object.client.tls.client_cert == '/tmp/client.crt' + - result.object.client.tls.client_key == '/tmp/client.key' + - result.object.client.tls.cname == 'my-vault.com' + + - name: Idempotence check for vault provider creation with all params + sensu.sensu_go.secrets_provider_vault: *create-provider-all-params + register: result + + - ansible.builtin.assert: + that: + - result is not changed + + - name: Delete a vault secrets provider (check mode) + sensu.sensu_go.secrets_provider_vault: &delete-provider + auth: + url: http://localhost:8080 + name: my-vault + state: absent + check_mode: true + register: result + + - ansible.builtin.assert: + that: + - result is changed + + - name: Make sure vault secrets provider was not deleted when running in check mode + sensu.sensu_go.secrets_provider_info: + auth: + url: http://localhost:8080 + name: my-vault + register: result + + - ansible.builtin.assert: + that: + - result.objects | length == 1 + + - name: Delete a vault secrets provider + sensu.sensu_go.secrets_provider_vault: *delete-provider + register: result + + - ansible.builtin.assert: + that: + - result is changed + + - name: Make sure vault secrets provider was deleted + sensu.sensu_go.secrets_provider_info: + auth: + url: http://localhost:8080 + name: my-vault + register: result + + - ansible.builtin.assert: + that: + - result.objects == [] + + - name: Delete a non-existent vault secrets provider + sensu.sensu_go.secrets_provider_vault: + auth: + url: http://localhost:8080 + name: i-dont-exist + state: absent + register: result + + - ansible.builtin.assert: + that: + - result is not changed diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secrets_provider_vault/files/ca.crt b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secrets_provider_vault/files/ca.crt new file mode 100644 index 00000000..ceffe521 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secrets_provider_vault/files/ca.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCDCCAfCgAwIBAgITMgYuhrafZc78Bk5PXip8xwQqTjANBgkqhkiG9w0BAQsF +ADAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMjAxMDA3MDYzMzQzWhcNNDAwMTI2 +MDYzMzQzWjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCk8LHitgJipeUaqWW2dsMe44TiT6XO17pPNJwRLFA7eSEN +Vn5gCNd2yfEjsx7lXaZApghwz0YIE6gzkvFPS5cHCbPuBqyI6rSUPEXvMdkt7EGG +40uhcX/otp0FvQr+Uvqo9NQyavTrgEPTudUptLFJd8QkZaVzprx061K2uV8Kjc4h +0MDIX5+4amb+h4eEBloTjH4lc6INe2uezAodiRjUp/TOYJzr5OIPYd3jjbbB1G3T +kwsQEz9qiEEwHN8AUGEFWqIy2O3BNTbS4D5Fv4n3U+HSFQdB5ORHGKbu/s0e8Tr8 +K5udPjM+ld8PazD2UIBBpWDbAqYvUrdTxYhxFqIrAgMBAAGjUzBRMB0GA1UdDgQW +BBS6M9F187X6KhlndKvdTNxC25xRrzAfBgNVHSMEGDAWgBS6M9F187X6KhlndKvd +TNxC25xRrzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAZvMa4 +R+AqR3lR3zpzFtN6OK/oFbUTloOE6+sWkFlE197YlvaUK0NmD67Xlvuor++KfCci +ru2nOhUkqYRHQ5a/ZACEOhv0PyCcTTAsEWcjs/I/zBTxvzJUjX7o6X1wtVmo5lI5 +lLpru+5h5XsqHMnIbdqfGKJup3j44d0qUDUrVKAsS8ioWvmuyZyxczeKvb+CvQLq +sNPSD+7H8l6WdWfo0unmCz7FkZDtSg9LCjzMF0TEe5D9ApWb6GtLy1XRKzg8jGzG +MuuzNzlaqBgWI88HRL25ZkVLwNahb7dkkt82d8ghdmtkisXHm24BHwPnXOpQ2LGn +ZPUwbq0OkVDt9nkr +-----END CERTIFICATE----- diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secrets_provider_vault/files/client.crt b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secrets_provider_vault/files/client.crt new file mode 100644 index 00000000..b6c5fe9f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secrets_provider_vault/files/client.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUbhKhIYBliY9SvDegW9ND5+YAQSowDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDEwMDYwOTMwMjZaFw0zMDEw +MDQwOTMwMjZaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDApjHtQI+w5Lu2uoMMo+iAcVb+6PjzWVL1G34d31HK +hZynEPbCkJzpXsUOnbPagWmTXFMBblqgE1Pj4zT97oqWPBlIV3UA5M7rBQel76Vq +YPKNDcZ8fGtCaL3wFFmN3ou6QaI+Zy3CJAy6Q71426xRawHWCgF7in3k90bVw3Qy +hrW8VrEgcwobdHn79W9idx+OBYcku5Fn1ciZn9RQoL0GRIRsg5IyB0Uy9W1Vyc74 +Bp2LTySn4QwPflfCjb9+83oeMTwPTDU3RHPVbMVoL87+jXD5TeZTCNooIYDRYjP6 +3gWEQMVL+LJpaFZvdp26uArJnCu69wgyvpzGqnExKgl3AgMBAAGjUzBRMB0GA1Ud +DgQWBBRBlAIaZUvLzoGXdIfOXulVTGIg0zAfBgNVHSMEGDAWgBRBlAIaZUvLzoGX +dIfOXulVTGIg0zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBG +U/Xnzm5+H6gXzq8hvFBIeaMYQmTgLwKt0w7yHBSQSNJC4qouIPgbyKKwrQJOvdI3 +/q3GRTkzMB/GKHRwh9nE4Z9wBLLY8b5K2t2yyQjpNtHyKeO+cc3E1gKSa0/QMEHh +OLtQsCixfACb3U0KizhwocACQ52NVMd2LhBuO4UCAGi1I5SFZzrgIcrG9WoB1Qzx +YoWh/49BL45LuLQ9MvPxya6ZuE0Pg38xRr+5yaEbnEAinVGy5eL+PNa5aMPogUB3 ++hINK/Ous3ab0zJyJ+1lN/9ijx43R2RfCo8GXA7x9Wz+N+nW8MAJbICiJR8pljiy +GFQiUdAX24flMYN14ZjX +-----END CERTIFICATE----- diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secrets_provider_vault/files/client.key b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secrets_provider_vault/files/client.key new file mode 100644 index 00000000..d22f6871 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secrets_provider_vault/files/client.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDApjHtQI+w5Lu2 +uoMMo+iAcVb+6PjzWVL1G34d31HKhZynEPbCkJzpXsUOnbPagWmTXFMBblqgE1Pj +4zT97oqWPBlIV3UA5M7rBQel76VqYPKNDcZ8fGtCaL3wFFmN3ou6QaI+Zy3CJAy6 +Q71426xRawHWCgF7in3k90bVw3QyhrW8VrEgcwobdHn79W9idx+OBYcku5Fn1ciZ +n9RQoL0GRIRsg5IyB0Uy9W1Vyc74Bp2LTySn4QwPflfCjb9+83oeMTwPTDU3RHPV +bMVoL87+jXD5TeZTCNooIYDRYjP63gWEQMVL+LJpaFZvdp26uArJnCu69wgyvpzG +qnExKgl3AgMBAAECggEAeP9pWEQ2e7oOFESszqGcBCArrcsRoXY23m+4FHcQ3gxx +SZUkByvhAcpeJkHyloi1kLJqB/oRvXymMfmgbOUH8jgpAQC9IiSTuZTdKuGLmXbu +oJ1ITyOuEnXK5iFB5hMi24chqeQQH2GZBNxLAr+mSTTWYGLEb52aWNbejKBIOPq9 +xnHD3gkFwod5dQt9ZY0bh+jTdM6cRuVyUooxbkFIkblHDQ8vEWsdC+b4rAXVH71e +JjrnI4y/4zDdF5akexpaHrva+J1Y2PCkdFvfO4drl5XKysSiUMX37XN32LxU5/ON +c6umKQnot1GL4ognOQw5HcygkT2Y8ltVbdcEK21xYQKBgQD7e1J48DYvpOhVVMaY +fqV2RuXKpoyy+7RIU+YrxaduwJYqH6ZWnLOYf2ZtlKRVsuHw+4fjAxw4DW6FzkMA +hXcgFwbLQO97dcxETPcq7Hny7mSQy8PO8iLXMHqOerxo16DNivc6Kw5NV+S/5XaU +IMNCmNl+1ZzkdNhUjeknV5Sf/wKBgQDEHEaKXAQoxGffRFoZK0aj3FX8N+qsYn4c +mftCkWZzKLMvwgjt0CXsHqrx9C3WecF2yrTGyMEsuI6eOBWVLqfkTUH9JgcjeFiF +R2c25zBS28WZtZwFaIi3oUtXHX2rKyWza3rQZSqEgXXV+ck/Ks7LFqpuHmttnYHt +qTXNIx+WiQKBgBpsXMAETU04QIkmvS8sr2n8DQz77vCnbcvjtN2IiQ0kAyMt7CZR +lLVDPZnp8lJm10KgyyhZHU/uaVx5zaRyYY/nm3kju4X3XJ0YkSfbbPzPe5WTM2G9 +I1gE6fuqfb1uWqD+JvffqkMKJyjajVkHED0hHkkrXK7McCaCOqs9koo1AoGBAIEN +NmJgUSBetxgWi8/aSZ8VJMRYK6cLHYBG2DCjLC8GDnyUDHoqqnnqaIXWML/d2bEJ +jdLuUyjRvpBhydolHLjBGnazKqltzZrPNR3NH2C3XR5cg3KPqsBkdQa70nHsb9/V +D7nJiSQvaVLJEGTwD6tXnAnhHMLCjrjNzCjVPzk5AoGBANHxGdKUaZivQV3dMo3p +6WHfwe1rTdaiahvEHH2wi4ch+m7b92p+ey2aPg8kvnHKh8TG0NuAcJZ1/lG2qex8 +89pmdwaqkggi/ApPR0vvZiIQgSawlPW3pg1VAW/DtzYGAJlGWwbI212szN94MDYH +t31cQ5V1qUo1rJaQE0rM0OwB +-----END PRIVATE KEY----- diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secrets_provider_vault/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secrets_provider_vault/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_secrets_provider_vault/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_silence/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_silence/converge.yml new file mode 100644 index 00000000..20496d0e --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_silence/converge.yml @@ -0,0 +1,180 @@ +--- +- name: Converge + collections: + - sensu.sensu_go + hosts: all + gather_facts: no + tasks: + - name: Create silence without parameters + silence: + auth: + url: http://localhost:8080 + ignore_errors: True + register: result + + - assert: + that: + - result is failed + - "result.msg == 'one of the following is required: subscription, check'" + + - name: Create silence with only check parameter + silence: + auth: + url: http://localhost:8080 + check: check + register: result + + - assert: + that: + - result is changed + - result.object.check == 'check' + - result.object.metadata.name == '*:check' + + - name: Create silence with only check parameter idempotence + silence: + auth: + url: http://localhost:8080 + check: check + register: result + + - assert: + that: + - result is not changed + + - name: Create silence with only subscription parameter + silence: + auth: + url: http://localhost:8080 + subscription: agent + register: result + + - assert: + that: + - result is changed + - result.object.subscription == 'agent' + - result.object.metadata.name == 'agent:*' + + - name: Create silence with only subscription parameter idempotence + silence: + auth: + url: http://localhost:8080 + subscription: agent + register: result + + - assert: + that: + - result is not changed + + - name: Create a silence + silence: &idempotence + auth: + url: http://localhost:8080 + subscription: entity:mail-server + check: some-check + # Schedule silence in the future (2030) in order to prevent it from + # expiring and getting dropped on the floor by the backend. + begin: 1893452400 + expire: 120 + expire_on_resolve: True + reason: updating mail server + register: result + + - assert: + that: + - result is changed + - result.object.metadata.name == 'entity:mail-server:some-check' + - result.object.subscription == 'entity:mail-server' + - result.object.check == 'some-check' + - result.object.begin == 1893452400 + - result.object.expire == 120 + - result.object.expire_on_resolve == True + - result.object.creator == 'admin' + - result.object.reason == 'updating mail server' + + - name: Test silence creation idempotence + silence: *idempotence + register: result + + - assert: + that: result is not changed + + - name: Modify a silence + silence: + auth: + url: http://localhost:8080 + subscription: entity:mail-server + check: some-check + begin: 1893452500 + register: result + + - assert: + that: + - result is changed + - result.object.begin == 1893452500 + - result.object.subscription == 'entity:mail-server' + - result.object.check == 'some-check' + - not result.object.expire_on_resolve + + - name: Create a second silence + silence: + auth: + url: http://localhost:8080 + subscription: agent + check: check-cpu + + - name: Fetch all silences + silence_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == 4 + - result.objects.0.check == 'check' + - "'subscription' not in result.objects.0" + + - name: Fetch a specific silence + silence_info: + auth: + url: http://localhost:8080 + subscription: agent + check: check-cpu + register: result + + - assert: + that: + - result.objects | length == 1 + - result.objects.0.subscription == 'agent' + - result.objects.0.check == 'check-cpu' + + - name: Delete a silence + silence: + auth: + url: http://localhost:8080 + check: check + state: absent + + - name: Fetch all silecnes + silence_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == 3 + - result.objects.0.subscription == 'agent' + - "'check' not in result.objects.0" + + - name: Try to fetch non-existing silence + silence_info: + auth: + url: http://localhost:8080 + subscription: bad + check: bad + register: result + + - assert: + that: + - result.objects == [] diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_silence/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_silence/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_silence/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_socket_handler/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_socket_handler/converge.yml new file mode 100644 index 00000000..8dcb5fc1 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_socket_handler/converge.yml @@ -0,0 +1,153 @@ +--- +- name: Converge + collections: + - sensu.sensu_go + hosts: all + gather_facts: no + tasks: + - name: Create socket handler with missing required parameters + socket_handler: + auth: + url: http://localhost:8080 + name: handler + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "result.msg == 'state is present but all of the following are missing: type, host, port'" + + - name: Create socket handler with minimal parameters + socket_handler: + auth: + url: http://localhost:8080 + name: minimal_handler + type: tcp + host: 10.1.0.99 + port: 4444 + register: result + + - assert: + that: + - result is changed + - result.object.socket.host == '10.1.0.99' + - result.object.socket.port == 4444 + - result.object.type == 'tcp' + - result.object.metadata.name == 'minimal_handler' + + - name: Create socket handler with minimal parameters idempotence + socket_handler: + auth: + url: http://localhost:8080 + name: minimal_handler + type: tcp + host: 10.1.0.99 + port: 4444 + register: result + + - assert: + that: result is not changed + + - name: Create an socket handler with full parameters + socket_handler: + auth: + url: http://localhost:8080 + name: handler + type: udp + mutator: mutate_input + timeout: 30 + filters: + - occurances + - production + host: 10.1.0.99 + port: 4444 + register: result + + - assert: + that: + - result is changed + - result.object.socket.host == '10.1.0.99' + - result.object.socket.port == 4444 + - result.object.type == 'udp' + - result.object.mutator == 'mutate_input' + - result.object.timeout == 30 + - result.object.filters == ['occurances', 'production'] + + - name: Create socket handler with full parameters idempotence + socket_handler: + auth: + url: http://localhost:8080 + name: handler + type: udp + mutator: mutate_input + timeout: 30 + filters: + - occurances + - production + host: 10.1.0.99 + port: 4444 + register: result + + - assert: + that: result is not changed + + - name: Modify socket handler + socket_handler: + auth: + url: http://localhost:8080 + name: handler + type: tcp + timeout: 60 + host: 10.1.0.99 + port: 4444 + register: result + + - assert: + that: + - result is changed + - result.object.type == 'tcp' + - result.object.socket.host == '10.1.0.99' + - result.object.socket.port == 4444 + - not result.object.filters + - "'mutator' not in result.object" + + - name: Fetch all socket handlers + handler_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == 2 + + - name: Fetch a specific socket handler + handler_info: + auth: + url: http://localhost:8080 + name: handler + register: result + + - assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'handler' + + - name: Delete socket handler + socket_handler: + auth: + url: http://localhost:8080 + name: handler + state: absent + + - name: Get all socket handlers + handler_info: + auth: + url: http://localhost:8080 + register: result + + - assert: + that: + - result.objects | length == 1 + - result.objects.0.metadata.name == 'minimal_handler'
\ No newline at end of file diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_socket_handler/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_socket_handler/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_socket_handler/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_tessen/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_tessen/converge.yml new file mode 100644 index 00000000..a06da305 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_tessen/converge.yml @@ -0,0 +1,53 @@ +--- +- name: Converge + collections: + - sensu.sensu_go + hosts: all + gather_facts: no + tasks: + - name: Call tessen with missing required parameters + tessen: + auth: + url: http://localhost:8080 + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "result.msg == 'missing required arguments: state'" + + - name: Disable tessen # Tessen is enabled by default on Sensu backends + tessen: + auth: + url: http://localhost:8080 + state: disabled + register: result + + - assert: + that: + - result is changed + - result.object.opt_out == True + + - name: Enable tessen + tessen: + auth: + url: http://localhost:8080 + state: enabled + register: result + + - assert: + that: + - result is changed + - result.object.opt_out == False + + - name: Try to enable already enabled tessen + tessen: + auth: + url: http://localhost:8080 + state: enabled + register: result + + - assert: + that: + - result is not changed diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_tessen/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_tessen/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_tessen/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_user/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_user/converge.yml new file mode 100644 index 00000000..7312779e --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_user/converge.yml @@ -0,0 +1,198 @@ +--- +- name: Converge + collections: + - sensu.sensu_go + hosts: all + gather_facts: no + environment: + SENSU_URL: http://localhost:8080 + + tasks: + - name: Create user with minimal parameters + user: + name: awesome_username + password: hidden_password? + register: result + + - assert: + that: + - result is changed + - result.object.username == 'awesome_username' + + - name: Minimal parameter idempotence check + user: + name: awesome_username + password: hidden_password? + register: result + + - assert: + that: + - result is not changed + + - name: Disable enabled user + user: + name: awesome_username + state: disabled + register: result + + - assert: + that: + - result.object.disabled == True + + - name: Add disabled user to some groups + user: + name: awesome_username + groups: [ a, b, c ] + state: disabled + register: result + + - assert: + that: + - result is changed + - result.object.groups | sort == ['a', 'b', 'c'] + + - name: Change password on disabled user + user: + name: awesome_username + password: new_pass + state: disabled + register: result + + - assert: + that: + - result is changed + + - name: Create a disabled user + user: + name: test_username + password: hidden_password? + state: disabled + groups: + - dev + - prod + register: result + + - assert: + that: + - result is changed + - result.object.username == 'test_username' + - result.object.disabled == True + - result.object.groups == ['dev', 'prod'] + + - name: Try to disable an already disabled user + user: + name: test_username + state: disabled + register: result + + - assert: + that: + - result is not changed + + - name: Enable a disabled user + user: + name: test_username + register: result + + - assert: + that: + - result is changed + - result.object.disabled == False + + - name: Modify a user + user: + name: test_username + password: hidden_password! + groups: + - dev + register: result + + - assert: + that: + - result is changed + - result.object.groups == ['dev'] + + - name: Fetch all users + user_info: + register: result + + - debug: + var: result + + - assert: + that: + - result.objects | length == 4 # users admin and agent already exist + + - name: Fetch specific user + user_info: + name: test_username + register: result + + - assert: + that: + - result.objects | length == 1 + - result.objects.0.username == 'test_username' + - result.objects.0.disabled == False + + - name: Try to create a user with no password + user: + name: missing_user + state: disabled + ignore_errors: true + register: result + + - assert: + that: + - result is failed + + - name: Try to fetch non-existing user + user_info: + name: bad-bad-user + register: result + + - assert: + that: + - result.objects == [] + + - name: Create a user with password hash (check mode) + user: &hash_pass_user + name: hash_pass_user + password_hash: $5f$14$.brXRviMZpbaleSq9kjoUuwm67V/s4IziOLGHjEqxJbzPsreQAyNm + check_mode: true + register: result + - assert: + that: + - result is changed + - result.object.username == "hash_pass_user" + + - name: Make sure nothing changed + user_info: + name: hash_pass_user + register: result + - assert: + that: + - result.objects | length == 0 + + - name: Create a user with password hash + user: *hash_pass_user + register: result + - assert: + that: + - result is changed + - result.object.username == "hash_pass_user" + + - name: Make sure new user appeared + user_info: + name: hash_pass_user + register: result + - assert: + that: + - result.objects | length == 1 + - result.objects.0.username == "hash_pass_user" + + - name: Create a user with password hash (failed idempotence for hash) + user: *hash_pass_user + register: result + - assert: + that: + - result is changed diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_user/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_user/molecule.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/module_user/molecule.yml diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_config/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_config/converge.yml new file mode 100644 index 00000000..f00ffa71 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_config/converge.yml @@ -0,0 +1,134 @@ +- name: Converge + hosts: all + + tasks: + - name: Configure overloaded variables + include_role: + name: sensu.sensu_go.agent + tasks_from: configure + vars: + agent_config: + backend-url: + - "ws://0.0.0.0:4321" + + - name: Confirm overloaded variables + slurp: + src: /etc/sensu/agent.yml + register: agent_yml + + - assert: + that: + - | + agent_yml.content | b64decode | from_yaml == { + "backend-url": ["ws://0.0.0.0:4321"] + } + + - name: Configure full set of optional variables + include_role: + name: sensu.sensu_go.agent + tasks_from: configure + vars: + agent_config: + annotations: + example-key: "example value" + example-key2: "example value 2" + backend-url: + - "ws://0.0.0.0:4321" + cache-dir: /tmp/sensu-agent/cache + disable-assets: true + allow-list: /etc/sensu/check-allow-list.yaml + label: + example_label1: "example label" + example_label2: "example label 2" + name: "agent-01" + log-level: fatal + api-hosts: "192.168.10.3" + api-port: 3031 + disable-api: true + events-burst-limit: 20 + events-rate-limit: 13.4 + deregister: false + deregister-handler: "deregister" + keepalive-interval: 35 + keepalive-timeout: 215 + namespace: "ops" + user: "sensuadmin" + password: "Never2GuessM3" + redact: + - secret_key + - usernames + - passwords + trusted-ca-file: "/var/lib/sensu/ca.pem" + insecure-skip-tls-verify: true + socket-host: "0.0.0.0" + socket-port: 13030 + disable-sockets: true + statsd-disable: true + statsd-event-handlers: + - influxdb + - opentsdb + statsd-flush-interval: 18 + statsd-metrics-host: 192.168.50.60 + statsd-metrics-port: 18125 + exec: "/usr/local/bin/check_disk.sh" + sha512: b648feb599b35722248e3d0b0c43ba1c60e4531e9547552e552e7b8dfdb54bb8e1ea414cf33b366ec0a51dd1423bd9a1846b9338771ab41d02a98695d9577834 + args: + - /dev/sda1 + - /dev/sda2 + - "-r" + enable_env: false + + - name: Confirm full set of optional variables + slurp: + src: /etc/sensu/agent.yml + register: agent_yml + + - debug: + var: agent_yml.content | b64decode | from_yaml + + - assert: + that: + - | + agent_yml.content | b64decode | from_yaml == { + "allow-list": "/etc/sensu/check-allow-list.yaml", + "annotations": { + "example-key": "example value", + "example-key2": "example value 2", + }, + "api-hosts": "192.168.10.3", + "api-port": 3031, + "args": ["/dev/sda1", "/dev/sda2", "-r"], + "backend-url": ["ws://0.0.0.0:4321"], + "cache-dir": "/tmp/sensu-agent/cache", + "deregister": False, + "deregister-handler": "deregister", + "disable-api": True, + "disable-assets": True, + "disable-sockets": True, + "enable_env": False, + "events-burst-limit": 20, + "events-rate-limit": 13.4, + "exec": "/usr/local/bin/check_disk.sh", + "insecure-skip-tls-verify": True, + "keepalive-interval": 35, + "keepalive-timeout": 215, + "label": { + "example_label1": "example label", + "example_label2": "example label 2", + }, + "log-level": "fatal", + "name": "agent-01", + "namespace": "ops", + "password": "Never2GuessM3", + "redact": ["secret_key", "usernames", "passwords"], + "sha512": "b648feb599b35722248e3d0b0c43ba1c60e4531e9547552e552e7b8dfdb54bb8e1ea414cf33b366ec0a51dd1423bd9a1846b9338771ab41d02a98695d9577834", + "socket-host": "0.0.0.0", + "socket-port": 13030, + "statsd-disable": True, + "statsd-event-handlers": ["influxdb", "opentsdb"], + "statsd-flush-interval": 18, + "statsd-metrics-host": "192.168.50.60", + "statsd-metrics-port": 18125, + "trusted-ca-file": "/var/lib/sensu/ca.pem", + "user": "sensuadmin", + } diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_config/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_config/molecule.yml new file mode 100644 index 00000000..7ba0b239 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_config/molecule.yml @@ -0,0 +1,14 @@ +--- +scenario: + test_sequence: + - destroy + - create + - prepare + - converge + - destroy + +platforms: + - name: centos + image: quay.io/xlab-steampunk/sensu-go-tests-centos:7 + pre_build_image: true + pull: true diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_config/prepare.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_config/prepare.yml new file mode 100644 index 00000000..40ba6223 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_config/prepare.yml @@ -0,0 +1,24 @@ +--- +- name: Prepare + hosts: all + + tasks: + - name: Create sensu group + group: + name: sensu + + - name: Create sensu user + # We need FQCN here because we are running test from within the + # collection. In this case, our collection becomes the default + # collection and so the sensu.sensu_go.user module shadows the builtin + # one. + ansible.builtin.user: + name: sensu + groups: sensu + + - name: Create /etc/sensu folder + file: + state: directory + path: /etc/sensu + owner: sensu + group: sensu diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_default/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_default/converge.yml new file mode 100644 index 00000000..f56054f0 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_default/converge.yml @@ -0,0 +1,5 @@ +--- +- name: Converge + hosts: agents + roles: + - sensu.sensu_go.agent diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_default/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_default/molecule.yml new file mode 100644 index 00000000..505b2263 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_default/molecule.yml @@ -0,0 +1,30 @@ +--- +scenario: + test_sequence: + - destroy + - create + - converge + - verify + - check + - destroy + +platforms: + - name: centos + image: quay.io/xlab-steampunk/sensu-go-tests-centos:7 + pre_build_image: true + pull: true + groups: [ agents ] + override_command: false + privileged: true + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:ro + +# Upstream container - just to populate the inventory +provisioner: + inventory: + hosts: + all: + children: + backends: + hosts: + upstream-backend: diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_default/verify.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_default/verify.yml new file mode 100644 index 00000000..bbe82156 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_default/verify.yml @@ -0,0 +1,28 @@ +--- +- name: Verify + hosts: agents + + tasks: + - name: Agent configuration must exist + stat: + path: /etc/sensu/agent.yml + register: result + + - assert: + that: + - result.stat.exists + - result.stat.mode == '0600' + - result.stat.pw_name == 'sensu' + - result.stat.gr_name == 'sensu' + + - name: Confirm default configuration settings + slurp: + src: /etc/sensu/agent.yml + register: agent_yml + + - assert: + that: + - | + agent_yml.content | b64decode | from_yaml == { + "backend-url": ["ws://upstream-backend:8081"], + } diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_secured/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_secured/converge.yml new file mode 100644 index 00000000..7e0e1ccf --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_secured/converge.yml @@ -0,0 +1,71 @@ +--- +- name: Pre-converge secure backend config step + hosts: backends + gather_facts: no + tasks: + - name: Set dummy backend PKI variables + set_fact: + api_key_file: /tmp/dummy.key + + - debug: + msg: "{{ hostvars['upstream-backend']['api_key_file'] }}" + +- name: Converge + hosts: agents + vars: + agent_trusted_ca_file: files/sensu-agent-trusted-ca.crt + roles: + - sensu.sensu_go.agent + +- name: Verify configure_agent + hosts: agents + tasks: + - name: The trusted CA store file must exist + stat: + path: /etc/sensu/sensu-agent-trusted-ca.crt + register: result + + - assert: + that: + - "{{ result.stat.exists }}" + - "{{ result.stat.pw_name == 'sensu' }}" + - "{{ result.stat.gr_name == 'sensu' }}" + - "{{ result.stat.mode == '0644' }}" + + - name: Confirm secured agent configuration settings + lineinfile: + path: &agent_yml /etc/sensu/agent.yml + line: '{{ item }}' + with_items: + - 'backend-url:' + - '- wss://upstream-backend:8081' + - 'trusted-ca-file: /etc/sensu/sensu-agent-trusted-ca.crt' + - 'insecure-skip-tls-verify: false' + register: result + + - assert: + that: + - result is not changed + +- name: Default configuration + hosts: agents + gather_facts: no + roles: + - sensu.sensu_go.agent + +- name: Verify default configuration + hosts: agents + tasks: + - name: Confirm that none of secured configuration settings leak in + lineinfile: + path: *agent_yml + regexp: '{{ item }}' + state: absent + with_items: + - '^trusted-ca-file:' + - '^insecure-skip-tls-verify:' + register: result + + - assert: + that: + - result is not changed diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_secured/files/sensu-agent-trusted-ca.crt b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_secured/files/sensu-agent-trusted-ca.crt new file mode 100644 index 00000000..fe9c08f4 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_secured/files/sensu-agent-trusted-ca.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUTCCAjmgAwIBAgIULehsb6UOfWOosQs9hh0u0GJfIUcwDQYJKoZIhvcNAQEL +BQAwGDEWMBQGA1UEAwwNU2Vuc3UtdGVzdCBDQTAeFw0xOTExMDYxNzIyMzlaFw0y +OTExMDMxNzIyMzlaMBgxFjAUBgNVBAMMDVNlbnN1LXRlc3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDXGeaaBh1Ysfd+YWbOHrlb9fIb39wp/Xj3 +8JvvdcTvEu9IvDrFASUYuyDo/y8s1XGMulzjKO1BzJr7NUTcMfvxMrXlKQFxmPWS +KxNVlPerBgiY8013ETtx1v91rq6K2tX2s1GnQOJ2+MWBHLpLRqa69l2XtIxR33+u +53T/vJAZVxzXU7pSfbRA7EvvaUry4IdCVhf8GCgvYopfZrxzByJg0fVgLNfbAlXb +7bBFnpbVabseAaL+jKeikejK1wy2DgcNvfTxZRc8V5MohyQKepgbIYegrmFx9gKg +vRtn/neGBFv3KndlogNZrYkH6qJk9Qkgjm4ZxgylGgnKiT/9Bn07AgMBAAGjgZIw +gY8wHQYDVR0OBBYEFGVvNX0RCwv9O8/mO5X6NWuADSfpMFMGA1UdIwRMMEqAFGVv +NX0RCwv9O8/mO5X6NWuADSfpoRykGjAYMRYwFAYDVQQDDA1TZW5zdS10ZXN0IENB +ghQt6GxvpQ59Y6ixCz2GHS7QYl8hRzAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB +BjANBgkqhkiG9w0BAQsFAAOCAQEAP01K867avDUQpSsdhKONcZ/QHpL7ao/cO+in +NbjQ9BEvV0Zjiw+xhlNy2U3G1UhbwOgCTIs6QLSA9sQyl3uAgXua9VmVH2bHT84m +tRS6K0olAw5xAMwCH6Nf6wTBABDk4O4ny1XYFRpMYsSrvk1S1LBGSwLuDVHIb+3K +3yOxCIROFsPy00+CimqXS3GwpFBerXXCAH7Rq8dyCMC1anXMX8YJz5WrpKW9wCrQ +RtnuWLImCu5w9Oe0QtKKZmh3wJD6uBo7gxX8ZugtrpZcZMUpo6DS1KsiKOTnibCJ +6k+A09PhNoorVrNnw/xmWgLf0J3CBE2jxUlq57QPYcVnFSs39w== +-----END CERTIFICATE----- diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_secured/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_secured/molecule.yml new file mode 100644 index 00000000..430054e8 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_agent_secured/molecule.yml @@ -0,0 +1,21 @@ +--- +platforms: + - name: centos + image: quay.io/xlab-steampunk/sensu-go-tests-centos:7 + pre_build_image: true + pull: true + groups: [ agents ] + override_command: false + privileged: true + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:ro + +# Upstream container - just to populate the inventory +provisioner: + inventory: + hosts: + all: + children: + backends: + hosts: + upstream-backend: diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_config/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_config/converge.yml new file mode 100644 index 00000000..05699844 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_config/converge.yml @@ -0,0 +1,74 @@ +--- +- name: Converge + hosts: all + + tasks: + - name: Configure select set of optional variables + include_role: + name: sensu.sensu_go.backend + tasks_from: configure + vars: + backend_config: + debug: no + log-level: debug + api-listen-address: "[::]:4430" + + - name: Confirm optional configuration settings + slurp: + src: /etc/sensu/backend.yml + register: backend_yml + + - assert: + that: + - | + backend_yml.content | b64decode | from_yaml == { + "state-dir": "/var/lib/sensu/sensu-backend", + "debug": False, + "log-level": "debug", + "api-listen-address": "[::]:4430", + } + + - name: Configure full set of optional variables + include_role: + name: sensu.sensu_go.backend + tasks_from: configure + vars: + backend_config: + debug: yes + log-level: debug + state-dir: /tmp/different/state + deregistration-handler: /tmp/handler.sh + agent-host: "127.0.0.1" + agent-port: 4431 + api-listen-address: "[::]:4430" + api-url: "http://localhost:4432" + dashboard-host: "192.168.10.6" + dashboard-port: 4433 + etcd-initial-advertise-peer-urls: + - https://10.10.0.1:2380 + - https://10.20.0.1:2380 + + - name: Confirm full configuration settings + slurp: + src: /etc/sensu/backend.yml + register: backend_yml + + - assert: + that: + - | + backend_yml.content | b64decode | from_yaml == { + "debug": True, + "log-level": "debug", + "state-dir": "/tmp/different/state", + "deregistration-handler": "/tmp/handler.sh", + "agent-host": "127.0.0.1", + "agent-port": 4431, + "api-listen-address": "[::]:4430", + "api-url": "http://localhost:4432", + "dashboard-host": "192.168.10.6", + "dashboard-port": 4433, + "etcd-initial-advertise-peer-urls": [ + "https://10.10.0.1:2380", + "https://10.20.0.1:2380", + ] + } diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_config/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_config/molecule.yml new file mode 100644 index 00000000..7ba0b239 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_config/molecule.yml @@ -0,0 +1,14 @@ +--- +scenario: + test_sequence: + - destroy + - create + - prepare + - converge + - destroy + +platforms: + - name: centos + image: quay.io/xlab-steampunk/sensu-go-tests-centos:7 + pre_build_image: true + pull: true diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_config/prepare.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_config/prepare.yml new file mode 100644 index 00000000..40ba6223 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_config/prepare.yml @@ -0,0 +1,24 @@ +--- +- name: Prepare + hosts: all + + tasks: + - name: Create sensu group + group: + name: sensu + + - name: Create sensu user + # We need FQCN here because we are running test from within the + # collection. In this case, our collection becomes the default + # collection and so the sensu.sensu_go.user module shadows the builtin + # one. + ansible.builtin.user: + name: sensu + groups: sensu + + - name: Create /etc/sensu folder + file: + state: directory + path: /etc/sensu + owner: sensu + group: sensu diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_default/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_default/converge.yml new file mode 100644 index 00000000..69becbbf --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: all + + tasks: + - name: Install backend + include_role: + name: sensu.sensu_go.backend diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_default/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_default/molecule.yml new file mode 100644 index 00000000..f9c28ce1 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_default/molecule.yml @@ -0,0 +1,20 @@ +--- +scenario: + test_sequence: + - destroy + - create + - converge + - idempotence + - verify + - check + - destroy + +platforms: + - name: centos + image: quay.io/xlab-steampunk/sensu-go-tests-centos:7 + pre_build_image: true + pull: true + override_command: false + privileged: true + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:ro diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_default/verify.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_default/verify.yml new file mode 100644 index 00000000..c6d487ea --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_default/verify.yml @@ -0,0 +1,35 @@ +--- +- name: Verify + hosts: all + + tasks: + - name: Backend configuration must exist + stat: + path: /etc/sensu/backend.yml + register: result + + - assert: + that: + - result.stat.exists + - result.stat.mode == '0600' + - result.stat.pw_name == 'sensu' + - result.stat.gr_name == 'sensu' + + - name: Confirm default configuration settings + slurp: + src: /etc/sensu/backend.yml + register: backend_yml + + - assert: + that: + - | + backend_yml.content | b64decode | from_yaml == { + "state-dir": "/var/lib/sensu/sensu-backend", + } + + - name: Make sure login works + uri: + url: http://localhost:8080/auth + url_username: admin + url_password: P@ssw0rd! + force_basic_auth: yes diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/converge.yml new file mode 100644 index 00000000..fd686187 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/converge.yml @@ -0,0 +1,164 @@ +--- +- name: Converge + hosts: all + + tasks: + - name: Install secured backend + include_role: + name: sensu.sensu_go.backend + tasks_from: configure + vars: + etcd_cert_file: files/etcd-client.crt + etcd_key_file: files/etcd-client.key + etcd_trusted_ca_file: files/client-ca.crt + etcd_peer_cert_file: files/etcd-peer.crt + etcd_peer_key_file: files/etcd-peer.key + etcd_peer_trusted_ca_file: files/etcd-peer-ca.crt + api_cert_file: files/sensu-api.crt + api_key_file: files/sensu-api.key + api_trusted_ca_file: files/sensu-api-ca.crt + dashboard_cert_file: files/sensu-dashboard.crt + dashboard_key_file: files/sensu-dashboard.key + + - name: The public keys must exist + stat: + path: "{{ item }}" + register: result + loop: + - /etc/sensu/etcd-client.crt + - /etc/sensu/etcd-client-ca.crt + - /etc/sensu/etcd-peer.crt + - /etc/sensu/etcd-peer-ca.crt + - /etc/sensu/api.crt + - /etc/sensu/api-ca.crt + - /etc/sensu/dashboard.crt + + - assert: + that: + - item.stat.exists + - item.stat.pw_name == 'sensu' + - item.stat.gr_name == 'sensu' + - item.stat.mode == '0644' + loop: "{{ result.results }}" + + - name: The private keys must exist and be protected + stat: + path: "{{ item }}" + register: result + loop: + - /etc/sensu/etcd-client.key + - /etc/sensu/etcd-peer.key + - /etc/sensu/api.key + - /etc/sensu/dashboard.key + + - assert: + that: + - item.stat.exists + - item.stat.pw_name == 'sensu' + - item.stat.gr_name == 'sensu' + - item.stat.mode == '0400' + loop: "{{ result.results }}" + + - name: Confirm secured configuration settings + slurp: + src: /etc/sensu/backend.yml + register: backend_yml + + - assert: + that: + - | + backend_yml.content | b64decode | from_yaml == { + "state-dir": "/var/lib/sensu/sensu-backend", + "etcd-listen-client-urls": "https://localhost:2379", + "etcd-listen-peer-urls": "https://localhost:2380", + "etcd-initial-advertise-peer-urls": "https://localhost:2380", + "etcd-initial-cluster": "default=https://localhost:2380", + "etcd-cert-file": "/etc/sensu/etcd-client.crt", + "etcd-key-file": "/etc/sensu/etcd-client.key", + "etcd-trusted-ca-file": "/etc/sensu/etcd-client-ca.crt", + "etcd-client-cert-auth": True, + "etcd-peer-cert-file": "/etc/sensu/etcd-peer.crt", + "etcd-peer-key-file": "/etc/sensu/etcd-peer.key", + "etcd-peer-client-cert-auth": True, + "etcd-peer-trusted-ca-file": "/etc/sensu/etcd-peer-ca.crt", + "cert-file": "/etc/sensu/api.crt", + "key-file": "/etc/sensu/api.key", + "trusted-ca-file": "/etc/sensu/api-ca.crt", + "insecure-skip-tls-verify": False, + "api-url": "https://localhost:8080", + "dashboard-cert-file": "/etc/sensu/dashboard.crt", + "dashboard-key-file": "/etc/sensu/dashboard.key", + } + + - name: Configure an overriding of managed vars + include_role: + name: sensu.sensu_go.backend + tasks_from: configure + vars: + etcd_cert_file: files/etcd-client.crt + etcd_key_file: files/etcd-client.key + etcd_trusted_ca_file: files/client-ca.crt + etcd_peer_cert_file: files/etcd-peer.crt + etcd_peer_key_file: files/etcd-peer.key + etcd_peer_trusted_ca_file: files/etcd-peer-ca.crt + api_cert_file: files/sensu-api.crt + api_key_file: files/sensu-api.key + api_trusted_ca_file: files/sensu-api-ca.crt + dashboard_cert_file: files/sensu-dashboard.crt + dashboard_key_file: files/sensu-dashboard.key + backend_config: + debug: true + log-level: debug + state-dir: /tmp/different-state + etcd-listen-client-urls: "https://127.0.0.1:2379" + etcd-listen-peer-urls: "https://127.0.0.1:2380" + etcd-initial-advertise-peer-urls: "https://127.0.0.1:2380" + etcd-initial-cluster: "default=https://127.0.0.1:2380" + etcd-cert-file: "/etc/sensu/../sensu/etcd-client.crt" + etcd-key-file: "/etc/sensu/../sensu/etcd-client.key" + etcd-trusted-ca-file: "/etc/sensu/../sensu/etcd-client-ca.crt" + etcd-client-cert-auth: false + etcd-peer-cert-file: "/etc/sensu/../sensu/etcd-peer.crt" + etcd-peer-key-file: "/etc/sensu/../sensu/etcd-peer.key" + etcd-peer-client-cert-auth: false + etcd-peer-trusted-ca-file: "/etc/sensu/../sensu/etcd-peer-ca.crt" + cert-file: "/etc/sensu/../sensu/api.crt" + key-file: "/etc/sensu/../sensu/api.key" + trusted-ca-file: "/etc/sensu/../sensu/api-ca.crt" + insecure-skip-tls-verify: true + api-url: "https://127.0.0.1:8080" + dashboard-cert-file: /etc/sensu/../sensu/dashboard.crt + dashboard-key-file: /etc/sensu/../sensu/dashboard.key + + - name: Confirm overriding of managed vars configuration settings + slurp: + src: /etc/sensu/backend.yml + register: backend_yml + + - assert: + that: + - | + backend_yml.content | b64decode | from_yaml == { + "api-url": "https://127.0.0.1:8080", + "cert-file": "/etc/sensu/../sensu/api.crt", + "dashboard-cert-file": "/etc/sensu/../sensu/dashboard.crt", + "dashboard-key-file": "/etc/sensu/../sensu/dashboard.key", + "debug": True, + "etcd-cert-file": "/etc/sensu/../sensu/etcd-client.crt", + "etcd-client-cert-auth": False, + "etcd-initial-advertise-peer-urls": "https://127.0.0.1:2380", + "etcd-initial-cluster": "default=https://127.0.0.1:2380", + "etcd-key-file": "/etc/sensu/../sensu/etcd-client.key", + "etcd-listen-client-urls": "https://127.0.0.1:2379", + "etcd-listen-peer-urls": "https://127.0.0.1:2380", + "etcd-peer-cert-file": "/etc/sensu/../sensu/etcd-peer.crt", + "etcd-peer-client-cert-auth": False, + "etcd-peer-key-file": "/etc/sensu/../sensu/etcd-peer.key", + "etcd-peer-trusted-ca-file": "/etc/sensu/../sensu/etcd-peer-ca.crt", + "etcd-trusted-ca-file": "/etc/sensu/../sensu/etcd-client-ca.crt", + "insecure-skip-tls-verify": True, + "key-file": "/etc/sensu/../sensu/api.key", + "log-level": "debug", + "state-dir": "/tmp/different-state", + "trusted-ca-file": "/etc/sensu/../sensu/api-ca.crt", + } diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/client-ca.crt b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/client-ca.crt new file mode 100644 index 00000000..fe9c08f4 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/client-ca.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUTCCAjmgAwIBAgIULehsb6UOfWOosQs9hh0u0GJfIUcwDQYJKoZIhvcNAQEL +BQAwGDEWMBQGA1UEAwwNU2Vuc3UtdGVzdCBDQTAeFw0xOTExMDYxNzIyMzlaFw0y +OTExMDMxNzIyMzlaMBgxFjAUBgNVBAMMDVNlbnN1LXRlc3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDXGeaaBh1Ysfd+YWbOHrlb9fIb39wp/Xj3 +8JvvdcTvEu9IvDrFASUYuyDo/y8s1XGMulzjKO1BzJr7NUTcMfvxMrXlKQFxmPWS +KxNVlPerBgiY8013ETtx1v91rq6K2tX2s1GnQOJ2+MWBHLpLRqa69l2XtIxR33+u +53T/vJAZVxzXU7pSfbRA7EvvaUry4IdCVhf8GCgvYopfZrxzByJg0fVgLNfbAlXb +7bBFnpbVabseAaL+jKeikejK1wy2DgcNvfTxZRc8V5MohyQKepgbIYegrmFx9gKg +vRtn/neGBFv3KndlogNZrYkH6qJk9Qkgjm4ZxgylGgnKiT/9Bn07AgMBAAGjgZIw +gY8wHQYDVR0OBBYEFGVvNX0RCwv9O8/mO5X6NWuADSfpMFMGA1UdIwRMMEqAFGVv +NX0RCwv9O8/mO5X6NWuADSfpoRykGjAYMRYwFAYDVQQDDA1TZW5zdS10ZXN0IENB +ghQt6GxvpQ59Y6ixCz2GHS7QYl8hRzAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB +BjANBgkqhkiG9w0BAQsFAAOCAQEAP01K867avDUQpSsdhKONcZ/QHpL7ao/cO+in +NbjQ9BEvV0Zjiw+xhlNy2U3G1UhbwOgCTIs6QLSA9sQyl3uAgXua9VmVH2bHT84m +tRS6K0olAw5xAMwCH6Nf6wTBABDk4O4ny1XYFRpMYsSrvk1S1LBGSwLuDVHIb+3K +3yOxCIROFsPy00+CimqXS3GwpFBerXXCAH7Rq8dyCMC1anXMX8YJz5WrpKW9wCrQ +RtnuWLImCu5w9Oe0QtKKZmh3wJD6uBo7gxX8ZugtrpZcZMUpo6DS1KsiKOTnibCJ +6k+A09PhNoorVrNnw/xmWgLf0J3CBE2jxUlq57QPYcVnFSs39w== +-----END CERTIFICATE----- diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/etcd-client.crt b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/etcd-client.crt new file mode 100644 index 00000000..85b70556 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/etcd-client.crt @@ -0,0 +1,87 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 70:e5:70:1d:0c:bb:93:ba:81:72:b6:5b:47:4f:9a:28 + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=Sensu-test CA + Validity + Not Before: Nov 6 17:22:40 2019 GMT + Not After : Oct 21 17:22:40 2022 GMT + Subject: CN=etcd-client + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:d3:ac:be:a4:45:22:ed:8f:0b:b2:19:0d:ab:60: + db:0b:c4:fb:8a:73:22:9a:f8:54:16:a0:4c:12:00: + a1:21:ef:03:ca:83:29:8e:90:12:87:03:78:12:0e: + 4f:b9:1c:f0:a2:57:06:51:fe:9c:6a:ef:45:9a:6b: + d5:32:df:58:44:ea:9b:ed:4d:e1:19:df:ce:ed:ea: + 90:a4:b1:2e:55:a2:6c:87:53:13:38:de:db:cc:f6: + cc:49:ba:85:aa:29:3d:0e:c9:6a:c0:b8:85:16:a7: + 34:1a:2a:d9:cc:f4:0e:1e:68:35:6d:4a:dd:5d:43: + f6:70:da:0a:27:5e:77:ee:8c:d9:46:f8:70:2e:e3: + 3c:97:7d:55:c7:c6:0d:e7:20:61:df:9e:59:78:97: + 6c:0d:5c:ed:d3:c8:d7:d8:f3:7c:7b:58:2f:0a:f9: + b3:a5:63:41:b6:6a:e6:b2:bc:48:eb:cb:4b:76:2b: + 2e:3e:9c:1a:db:c9:f1:2f:1a:7c:43:db:38:6b:9b: + 7f:d0:d7:25:7c:3f:50:1d:70:11:06:a8:d6:14:b5: + c4:dd:ea:3d:28:d9:57:b7:aa:d2:26:01:cc:5e:f1: + ae:48:28:a1:4a:ee:ac:ed:9c:7e:61:ef:37:12:67: + 7b:d9:05:0b:84:51:75:be:04:96:89:b7:d1:95:d6: + ca:61 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Subject Key Identifier: + C5:76:B1:3A:CC:A2:6C:9B:DD:43:D2:59:E1:E2:51:65:A0:59:09:70 + X509v3 Authority Key Identifier: + keyid:65:6F:35:7D:11:0B:0B:FD:3B:CF:E6:3B:95:FA:35:6B:80:0D:27:E9 + DirName:/CN=Sensu-test CA + serial:2D:E8:6C:6F:A5:0E:7D:63:A8:B1:0B:3D:86:1D:2E:D0:62:5F:21:47 + + X509v3 Extended Key Usage: + TLS Web Client Authentication + X509v3 Key Usage: + Digital Signature + X509v3 Subject Alternative Name: + IP Address:176.16.117.105 + Signature Algorithm: sha256WithRSAEncryption + 22:8f:57:1e:79:d1:78:9f:d4:fa:c5:41:9d:b1:b0:90:99:d4: + 85:4d:83:0e:ec:c6:0d:fa:a5:4b:7f:16:70:58:9f:5e:1a:f4: + 15:cf:45:2f:cc:69:8e:56:bd:f9:d1:96:6a:34:b5:96:12:b4: + 84:07:21:8a:6f:13:ac:a2:6e:e0:e3:63:17:f3:33:86:ed:c5: + ea:48:c1:b3:af:05:75:28:5f:4f:77:5f:6c:83:cc:50:09:e4: + 56:59:e3:ec:b0:f1:56:01:ca:d0:69:90:b3:e3:a4:a5:c0:cd: + 99:5e:65:ad:33:30:42:53:70:d7:47:c8:4c:47:fd:b6:b1:b1: + f6:cb:0d:8b:6c:ab:14:f5:75:1e:3b:20:f9:00:05:87:bf:e0: + a7:57:12:af:e9:67:9d:5a:96:c3:52:55:25:a8:ec:86:20:49: + 74:e8:6a:19:c0:4a:27:5b:fc:8a:4d:a4:5e:6d:fa:9a:91:48: + c5:e4:84:e1:ed:f9:d5:6e:21:69:6e:20:4a:0e:cb:44:d7:4a: + 38:10:23:12:9f:9b:48:d0:a3:9f:02:e4:84:6d:d9:be:17:eb: + 60:d0:d3:39:60:60:13:7a:b1:a2:38:e4:75:5b:28:4e:5d:25: + 35:04:52:a2:c1:ce:8b:94:9f:37:30:3c:a8:35:36:44:db:ed: + b2:6f:4a:b1 +-----BEGIN CERTIFICATE----- +MIIDbjCCAlagAwIBAgIQcOVwHQy7k7qBcrZbR0+aKDANBgkqhkiG9w0BAQsFADAY +MRYwFAYDVQQDDA1TZW5zdS10ZXN0IENBMB4XDTE5MTEwNjE3MjI0MFoXDTIyMTAy +MTE3MjI0MFowFjEUMBIGA1UEAwwLZXRjZC1jbGllbnQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDTrL6kRSLtjwuyGQ2rYNsLxPuKcyKa+FQWoEwSAKEh +7wPKgymOkBKHA3gSDk+5HPCiVwZR/pxq70Waa9Uy31hE6pvtTeEZ387t6pCksS5V +omyHUxM43tvM9sxJuoWqKT0OyWrAuIUWpzQaKtnM9A4eaDVtSt1dQ/Zw2gonXnfu +jNlG+HAu4zyXfVXHxg3nIGHfnll4l2wNXO3TyNfY83x7WC8K+bOlY0G2auayvEjr +y0t2Ky4+nBrbyfEvGnxD2zhrm3/Q1yV8P1AdcBEGqNYUtcTd6j0o2Ve3qtImAcxe +8a5IKKFK7qztnH5h7zcSZ3vZBQuEUXW+BJaJt9GV1sphAgMBAAGjgbUwgbIwCQYD +VR0TBAIwADAdBgNVHQ4EFgQUxXaxOsyibJvdQ9JZ4eJRZaBZCXAwUwYDVR0jBEww +SoAUZW81fRELC/07z+Y7lfo1a4ANJ+mhHKQaMBgxFjAUBgNVBAMMDVNlbnN1LXRl +c3QgQ0GCFC3obG+lDn1jqLELPYYdLtBiXyFHMBMGA1UdJQQMMAoGCCsGAQUFBwMC +MAsGA1UdDwQEAwIHgDAPBgNVHREECDAGhwSwEHVpMA0GCSqGSIb3DQEBCwUAA4IB +AQAij1ceedF4n9T6xUGdsbCQmdSFTYMO7MYN+qVLfxZwWJ9eGvQVz0UvzGmOVr35 +0ZZqNLWWErSEByGKbxOsom7g42MX8zOG7cXqSMGzrwV1KF9Pd19sg8xQCeRWWePs +sPFWAcrQaZCz46SlwM2ZXmWtMzBCU3DXR8hMR/22sbH2yw2LbKsU9XUeOyD5AAWH +v+CnVxKv6WedWpbDUlUlqOyGIEl06GoZwEonW/yKTaRebfqakUjF5ITh7fnVbiFp +biBKDstE10o4ECMSn5tI0KOfAuSEbdm+F+tg0NM5YGATerGiOOR1WyhOXSU1BFKi +wc6LlJ83MDyoNTZE2+2yb0qx +-----END CERTIFICATE----- diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/etcd-client.key b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/etcd-client.key new file mode 100644 index 00000000..1bed38d5 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/etcd-client.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDTrL6kRSLtjwuy +GQ2rYNsLxPuKcyKa+FQWoEwSAKEh7wPKgymOkBKHA3gSDk+5HPCiVwZR/pxq70Wa +a9Uy31hE6pvtTeEZ387t6pCksS5VomyHUxM43tvM9sxJuoWqKT0OyWrAuIUWpzQa +KtnM9A4eaDVtSt1dQ/Zw2gonXnfujNlG+HAu4zyXfVXHxg3nIGHfnll4l2wNXO3T +yNfY83x7WC8K+bOlY0G2auayvEjry0t2Ky4+nBrbyfEvGnxD2zhrm3/Q1yV8P1Ad +cBEGqNYUtcTd6j0o2Ve3qtImAcxe8a5IKKFK7qztnH5h7zcSZ3vZBQuEUXW+BJaJ +t9GV1sphAgMBAAECggEAX+J1bUYgH0pX+vIcXhB+ySMO6tVizJ5GwQUV0GXWl9+E +cRzfG0QqEFzC7DLtbCYu3ura4xOZ2VrPxbapGeVQP8+imGlZ2XWnb+B8aw4Tthjg +XqCEKZPSL1NwkMlcOQt7LBKTN/+d0fglwuC8TnoTVzTPVtW91rytistKJDFH1JfI +AwOyMoWkNYP3zDdMJn9dWkOXsNjK2VoxzCh/DhOcI/2gO4v7fM1w8A8iGYi5uMD4 +b1IzIqcvdgeyGEXhzu2PcJ0Kr1dR2B2VGJJ4kUqIjUGxG7gSE8X0siEBmSBNBou9 +lFp35esx2ffEnzPZaxAdqOpW0eV/iWxxYKFKX37PAQKBgQDpBOxqgbaGqu20MBVB +rQWh4CjalnvDbjyCyLfAb/6iI8MLnSvw9nfUS9X0TGnMmbZ5gk+7LDKczfs6NOHC +qBs34tAHHebimYqNII5UFc6OyYHpZsLmCG7+JgJDDD299jyZsNHBuw46TUOwdj9T +rRUdAX4s5fDzLmNzu/vN7PqXcQKBgQDojO8qscWy8GWbbxUpRjCetxNPpBf83rke +SL8o283ziMNmhn2L1RHa5vAYBHU1n2aIbYJiU6UEoJnCCpjxcigZdEacE7wsx8Et +alCzlitLuCW0C3bdvxERm1Ya9frhxq+ljE9Kjn8IQ9+1+4zE0Xzk9bnEQVQsySWk +siJUay9J8QKBgQCu6iY5cPM9tZNHfgyGx0WCFM02AF4Y/mfn2Jmul6MxvNyNnEa+ +05RxxRdvEekdT4ldPsdw/iVj9W8Pa2DIiP4dfmGf5f9Ju+34MCcG0XPDVVnyhVPp +7wy6NHfgMpEqRmuJBuT9otd0Rkl4bdrtifBeXJ+FPnoXYYv/9W14T9pv0QKBgBpn +Tgxp7Ml5VAAG578s8f5DSRUEy5hxbVFL7zBjbInXIGB2qrCCu6lACUig0PIKrCiX +TnN9jcHtvidy7pSTYowpUI8OCpKHB57xcEJDrZzGRrmfh5p7xCNcoLUk8pxJ482H +FcWgUjoNAsx1yDDcnDKe1725sSX4nKaLdyxgkxjxAoGAVzuCl7Kl2RjOCIWKG46N +vIl0nCdyTlJb1ammNr2cxIkjreSjenYS+Y8aXJOVVVDux+QB2LiQioMBDN/KIQ0k +55enQr0iKrjNIa5GeGN8uNnsrUoJ/9YfRsEq3rokns7kvSdsNFGFH4UOwuuEd4Qb +y6OFkERhzgGm4TcsRPeOPiM= +-----END PRIVATE KEY----- diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/etcd-peer-ca.crt b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/etcd-peer-ca.crt new file mode 100644 index 00000000..fe9c08f4 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/etcd-peer-ca.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUTCCAjmgAwIBAgIULehsb6UOfWOosQs9hh0u0GJfIUcwDQYJKoZIhvcNAQEL +BQAwGDEWMBQGA1UEAwwNU2Vuc3UtdGVzdCBDQTAeFw0xOTExMDYxNzIyMzlaFw0y +OTExMDMxNzIyMzlaMBgxFjAUBgNVBAMMDVNlbnN1LXRlc3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDXGeaaBh1Ysfd+YWbOHrlb9fIb39wp/Xj3 +8JvvdcTvEu9IvDrFASUYuyDo/y8s1XGMulzjKO1BzJr7NUTcMfvxMrXlKQFxmPWS +KxNVlPerBgiY8013ETtx1v91rq6K2tX2s1GnQOJ2+MWBHLpLRqa69l2XtIxR33+u +53T/vJAZVxzXU7pSfbRA7EvvaUry4IdCVhf8GCgvYopfZrxzByJg0fVgLNfbAlXb +7bBFnpbVabseAaL+jKeikejK1wy2DgcNvfTxZRc8V5MohyQKepgbIYegrmFx9gKg +vRtn/neGBFv3KndlogNZrYkH6qJk9Qkgjm4ZxgylGgnKiT/9Bn07AgMBAAGjgZIw +gY8wHQYDVR0OBBYEFGVvNX0RCwv9O8/mO5X6NWuADSfpMFMGA1UdIwRMMEqAFGVv +NX0RCwv9O8/mO5X6NWuADSfpoRykGjAYMRYwFAYDVQQDDA1TZW5zdS10ZXN0IENB +ghQt6GxvpQ59Y6ixCz2GHS7QYl8hRzAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB +BjANBgkqhkiG9w0BAQsFAAOCAQEAP01K867avDUQpSsdhKONcZ/QHpL7ao/cO+in +NbjQ9BEvV0Zjiw+xhlNy2U3G1UhbwOgCTIs6QLSA9sQyl3uAgXua9VmVH2bHT84m +tRS6K0olAw5xAMwCH6Nf6wTBABDk4O4ny1XYFRpMYsSrvk1S1LBGSwLuDVHIb+3K +3yOxCIROFsPy00+CimqXS3GwpFBerXXCAH7Rq8dyCMC1anXMX8YJz5WrpKW9wCrQ +RtnuWLImCu5w9Oe0QtKKZmh3wJD6uBo7gxX8ZugtrpZcZMUpo6DS1KsiKOTnibCJ +6k+A09PhNoorVrNnw/xmWgLf0J3CBE2jxUlq57QPYcVnFSs39w== +-----END CERTIFICATE----- diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/etcd-peer.crt b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/etcd-peer.crt new file mode 100644 index 00000000..90e86640 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/etcd-peer.crt @@ -0,0 +1,87 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + ae:53:a2:43:b9:f9:fc:21:b9:f1:2c:e3:c3:b4:45:14 + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=Sensu-test CA + Validity + Not Before: Nov 6 17:22:40 2019 GMT + Not After : Oct 21 17:22:40 2022 GMT + Subject: CN=etcd-peer + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:dd:2a:35:47:b2:6d:77:0d:d5:a2:16:50:20:97: + 93:e9:f6:c4:01:04:b7:2a:62:04:52:01:68:5c:98: + f2:e5:82:f6:eb:9a:77:9b:ca:0a:9f:61:34:4d:f4: + b5:ae:e0:bd:8a:5d:79:03:b4:10:c3:ea:f3:8c:e2: + 88:79:ff:68:2a:dd:d5:d1:d5:68:9a:61:2d:0b:49: + 03:ae:fc:1b:26:a7:89:1f:fb:76:e2:81:81:72:bd: + 42:c2:9e:60:d9:0d:2e:72:81:c3:11:c6:cd:63:64: + 52:a9:5d:d5:85:c1:35:b0:93:2a:de:d3:46:2c:e4: + 10:d5:7d:92:e9:3c:ff:f5:b4:7e:e2:fc:fd:6b:9e: + c1:a1:a0:c9:6c:31:ad:d2:42:09:55:9e:de:ee:37: + 06:40:c7:61:da:c9:5e:d8:40:cb:3f:1f:7b:93:11: + c3:e5:5c:9d:e5:fc:77:67:92:de:c3:8a:3e:87:15: + dc:47:ad:34:17:84:b4:b2:21:8e:8f:2f:d8:3a:92: + 99:4c:80:d7:06:77:5d:3c:66:db:53:15:ee:a5:5f: + 12:29:19:07:30:a1:71:7b:d5:1f:ee:24:b3:9c:02: + 6e:b6:42:cd:19:96:73:e3:c7:c4:20:c5:a4:75:b4: + b5:85:43:68:e4:91:5f:3d:fa:ed:0e:34:f1:fe:a9: + 0c:a5 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Subject Key Identifier: + 75:26:89:17:1C:ED:2A:53:BD:CE:87:EA:34:F5:C6:76:7C:C3:B5:08 + X509v3 Authority Key Identifier: + keyid:65:6F:35:7D:11:0B:0B:FD:3B:CF:E6:3B:95:FA:35:6B:80:0D:27:E9 + DirName:/CN=Sensu-test CA + serial:2D:E8:6C:6F:A5:0E:7D:63:A8:B1:0B:3D:86:1D:2E:D0:62:5F:21:47 + + X509v3 Extended Key Usage: + TLS Web Server Authentication + X509v3 Key Usage: + Digital Signature, Key Encipherment + X509v3 Subject Alternative Name: + IP Address:176.16.117.105 + Signature Algorithm: sha256WithRSAEncryption + 3a:39:39:8c:d5:9d:ca:88:0e:d3:c4:b2:e5:91:3b:b4:66:5e: + a3:92:44:f4:b4:fa:20:b0:a7:ee:6d:69:f4:8e:51:db:7b:e8: + 50:b9:15:00:2a:20:17:4e:0d:bf:4a:62:56:3e:cf:be:33:f6: + 44:54:c9:5e:22:1a:7a:3f:40:74:77:ed:96:e9:09:9e:27:dd: + 0a:69:bb:d3:5b:b2:4e:95:08:00:71:c1:a6:a2:ed:b9:46:41: + 94:96:6a:10:57:d4:7a:c2:0d:ce:c3:14:0c:ff:ab:fe:6e:09: + fd:18:09:32:d9:7c:f8:a4:dd:0c:fd:56:55:36:ae:03:fc:59: + a3:c5:4b:6f:2e:be:5f:d7:cd:bd:7d:de:3d:29:1a:5b:78:85: + 7a:4c:59:f3:15:31:e6:93:9c:db:28:c8:10:86:6f:30:db:3f: + 59:29:b1:05:9f:9b:b5:2e:20:b2:b8:dc:2e:3b:c2:75:dc:bf: + 39:eb:db:d6:5c:f7:cc:b5:61:ea:a8:c0:45:89:5b:c2:1a:fa: + ce:a7:69:63:4b:f6:3c:be:4c:f2:cf:2b:bf:5b:d4:f1:b8:e3: + 2a:e2:50:3a:62:e5:0d:42:fc:ac:63:b2:76:3d:c4:0a:26:70: + 60:16:ec:12:d7:60:04:10:bd:e7:9d:7c:3c:65:ab:25:51:f7: + aa:6b:5e:b1 +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIRAK5TokO5+fwhufEs48O0RRQwDQYJKoZIhvcNAQELBQAw +GDEWMBQGA1UEAwwNU2Vuc3UtdGVzdCBDQTAeFw0xOTExMDYxNzIyNDBaFw0yMjEw +MjExNzIyNDBaMBQxEjAQBgNVBAMMCWV0Y2QtcGVlcjCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAN0qNUeybXcN1aIWUCCXk+n2xAEEtypiBFIBaFyY8uWC +9uuad5vKCp9hNE30ta7gvYpdeQO0EMPq84ziiHn/aCrd1dHVaJphLQtJA678Gyan +iR/7duKBgXK9QsKeYNkNLnKBwxHGzWNkUqld1YXBNbCTKt7TRizkENV9kuk8//W0 +fuL8/WuewaGgyWwxrdJCCVWe3u43BkDHYdrJXthAyz8fe5MRw+VcneX8d2eS3sOK +PocV3EetNBeEtLIhjo8v2DqSmUyA1wZ3XTxm21MV7qVfEikZBzChcXvVH+4ks5wC +brZCzRmWc+PHxCDFpHW0tYVDaOSRXz367Q408f6pDKUCAwEAAaOBtTCBsjAJBgNV +HRMEAjAAMB0GA1UdDgQWBBR1JokXHO0qU73Oh+o09cZ2fMO1CDBTBgNVHSMETDBK +gBRlbzV9EQsL/TvP5juV+jVrgA0n6aEcpBowGDEWMBQGA1UEAwwNU2Vuc3UtdGVz +dCBDQYIULehsb6UOfWOosQs9hh0u0GJfIUcwEwYDVR0lBAwwCgYIKwYBBQUHAwEw +CwYDVR0PBAQDAgWgMA8GA1UdEQQIMAaHBLAQdWkwDQYJKoZIhvcNAQELBQADggEB +ADo5OYzVncqIDtPEsuWRO7RmXqOSRPS0+iCwp+5tafSOUdt76FC5FQAqIBdODb9K +YlY+z74z9kRUyV4iGno/QHR37ZbpCZ4n3Qppu9Nbsk6VCABxwaai7blGQZSWahBX +1HrCDc7DFAz/q/5uCf0YCTLZfPik3Qz9VlU2rgP8WaPFS28uvl/Xzb193j0pGlt4 +hXpMWfMVMeaTnNsoyBCGbzDbP1kpsQWfm7UuILK43C47wnXcvznr29Zc98y1Yeqo +wEWJW8Ia+s6naWNL9jy+TPLPK79b1PG44yriUDpi5Q1C/KxjsnY9xAomcGAW7BLX +YAQQveedfDxlqyVR96prXrE= +-----END CERTIFICATE----- diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/etcd-peer.key b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/etcd-peer.key new file mode 100644 index 00000000..3d3dfd47 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/etcd-peer.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDdKjVHsm13DdWi +FlAgl5Pp9sQBBLcqYgRSAWhcmPLlgvbrmnebygqfYTRN9LWu4L2KXXkDtBDD6vOM +4oh5/2gq3dXR1WiaYS0LSQOu/Bsmp4kf+3bigYFyvULCnmDZDS5ygcMRxs1jZFKp +XdWFwTWwkyre00Ys5BDVfZLpPP/1tH7i/P1rnsGhoMlsMa3SQglVnt7uNwZAx2Ha +yV7YQMs/H3uTEcPlXJ3l/Hdnkt7Dij6HFdxHrTQXhLSyIY6PL9g6kplMgNcGd108 +ZttTFe6lXxIpGQcwoXF71R/uJLOcAm62Qs0ZlnPjx8QgxaR1tLWFQ2jkkV89+u0O +NPH+qQylAgMBAAECggEALk6aHUr0tIrHAksdt9VE+SXb4EK2fz9KnEkDKvAzW27S +eH49MPdaxgg6RWBJcjZIOWJc5jOblwnouMTtwm2ByAfuryK55ikWn1hIVykeHjfR +9EpYmBB5pCaQheNXb9rcsMkOqPgxJPBqhl/JR1ou/auyvDkMZnXSOIE0c3V8OluO +nL39RHNCz76bNZSpe6BbYH6Ch8xkhCuYrzlIuBTSduE1aOK/Zugf+AMlNbxekdYv +8rhlCkO4lX752Dy8Z6x6KqRwRrXk+yeAhL7BITpsMCtJCR7XIsXKr9u/465tkjhC +DlkqFhwk2I9uG0ygtkUkYpfakx0FBoroAQc3qAgQuQKBgQD+6BPb9nvvS403DcEh +KNV0Pegdjms4KGiZmhOskWPOmMVKyIUHULgcQiwoROPzSfHjHABRuaVUz2+n37Kx +kU9h+FTolIQUD/S9aYwEIf83Eu7CGj9ictLUPeLxbGSJ6B5+74D8RuOj5jS2VNug +kJeBl0OWgD7ZXJF3LotSmnyiuwKBgQDeHRPaaBqp+5rZOLghyztsXnXQEcvMl5pt +wUnm77XMfDIJf+KDKdOA7avelcMsuv1UDQNpckshaeU6W5UYrw6CI/gpB5JHJBjQ +Bs6kTEw73t0+B1C9X/MA/AHIbk4TYLluxrv8pCprAptACuvEyHX+d03lETw+slzD +mLWDMnqIHwKBgQCDbifnL1TBkkPyke31afd9IvpOWwNfhj5AjJf+T0yV1mFLaM5m +cjErqNbZwIOECqlkfyiO+tiLPRWGCio5sgGrMv6cmQ7sxTlcfFJMQzczL1jZzezG +lwurkWk1L706+erXaigoa2iuNmERbfl79XGYyOR5chB1xGvgdqgxZCRFRwKBgDxz +s4yVGvS6uwl2C39/Hdw/1VbdERfNB0Xp/qAxC8zs3H2DZfYG8z668TUyk51gA0TW +CeCwL8yXUsFQXcMLGirHeWpJWkGsjGhKAgHrljARVyvjt9DjBXN2I1IW238gqzeA +NXfsgnL/kZubnVHAsYShmfzHdRsnVmIR9Q0RNxJHAoGBAMfqM6egc5JRSmDhA408 +d/oKasqYuPfNKWjZNfmDJcs1yHjaCJdQMkLlAi7a9QTi05KfEJGFf/k+ai7OIInw +TCgworaS8+y/1YihDIMGHfFF2B3IYEJQkEIDHXUBaVA9xMXgYLBDYAme82riZia2 +9mDJn1VPU0HRotyDYwoC34JV +-----END PRIVATE KEY----- diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/sensu-api-ca.crt b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/sensu-api-ca.crt new file mode 100644 index 00000000..fe9c08f4 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/sensu-api-ca.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUTCCAjmgAwIBAgIULehsb6UOfWOosQs9hh0u0GJfIUcwDQYJKoZIhvcNAQEL +BQAwGDEWMBQGA1UEAwwNU2Vuc3UtdGVzdCBDQTAeFw0xOTExMDYxNzIyMzlaFw0y +OTExMDMxNzIyMzlaMBgxFjAUBgNVBAMMDVNlbnN1LXRlc3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDXGeaaBh1Ysfd+YWbOHrlb9fIb39wp/Xj3 +8JvvdcTvEu9IvDrFASUYuyDo/y8s1XGMulzjKO1BzJr7NUTcMfvxMrXlKQFxmPWS +KxNVlPerBgiY8013ETtx1v91rq6K2tX2s1GnQOJ2+MWBHLpLRqa69l2XtIxR33+u +53T/vJAZVxzXU7pSfbRA7EvvaUry4IdCVhf8GCgvYopfZrxzByJg0fVgLNfbAlXb +7bBFnpbVabseAaL+jKeikejK1wy2DgcNvfTxZRc8V5MohyQKepgbIYegrmFx9gKg +vRtn/neGBFv3KndlogNZrYkH6qJk9Qkgjm4ZxgylGgnKiT/9Bn07AgMBAAGjgZIw +gY8wHQYDVR0OBBYEFGVvNX0RCwv9O8/mO5X6NWuADSfpMFMGA1UdIwRMMEqAFGVv +NX0RCwv9O8/mO5X6NWuADSfpoRykGjAYMRYwFAYDVQQDDA1TZW5zdS10ZXN0IENB +ghQt6GxvpQ59Y6ixCz2GHS7QYl8hRzAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB +BjANBgkqhkiG9w0BAQsFAAOCAQEAP01K867avDUQpSsdhKONcZ/QHpL7ao/cO+in +NbjQ9BEvV0Zjiw+xhlNy2U3G1UhbwOgCTIs6QLSA9sQyl3uAgXua9VmVH2bHT84m +tRS6K0olAw5xAMwCH6Nf6wTBABDk4O4ny1XYFRpMYsSrvk1S1LBGSwLuDVHIb+3K +3yOxCIROFsPy00+CimqXS3GwpFBerXXCAH7Rq8dyCMC1anXMX8YJz5WrpKW9wCrQ +RtnuWLImCu5w9Oe0QtKKZmh3wJD6uBo7gxX8ZugtrpZcZMUpo6DS1KsiKOTnibCJ +6k+A09PhNoorVrNnw/xmWgLf0J3CBE2jxUlq57QPYcVnFSs39w== +-----END CERTIFICATE----- diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/sensu-api.crt b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/sensu-api.crt new file mode 100644 index 00000000..68363428 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/sensu-api.crt @@ -0,0 +1,87 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + a0:13:00:67:13:31:d4:25:47:fa:1a:48:2b:76:a9:7f + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=Sensu-test CA + Validity + Not Before: Nov 6 17:22:42 2019 GMT + Not After : Oct 21 17:22:42 2022 GMT + Subject: CN=sensu-api + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:c4:16:71:d3:3b:08:26:1e:81:60:96:6e:9c:75: + ec:58:f2:dc:8f:22:e7:c8:64:b9:52:55:11:51:d5: + eb:d7:ae:7f:48:81:25:ea:4e:66:33:ee:be:73:25: + 84:ed:02:a6:f5:5f:d4:c0:2b:a6:d3:ae:c7:41:64: + 2c:5e:4b:6c:12:92:64:53:41:4a:59:b6:ef:d2:88: + 24:96:26:30:e3:98:25:45:c6:59:c4:e6:33:2c:a2: + 38:25:ce:f5:d5:d2:ba:39:68:1b:ec:f1:77:e6:e1: + 20:19:40:e5:ed:8c:bc:44:40:ce:0c:26:7d:86:60: + 0d:19:1f:31:59:85:a5:6a:d2:41:c8:1c:d6:7e:7b: + d1:1f:d4:35:2e:c2:c8:f6:da:03:39:54:0e:e6:8c: + 1b:26:e4:e6:3c:1f:88:64:c7:4f:46:89:70:6c:5b: + 26:ee:f2:a3:26:93:ac:00:4f:5d:d3:8b:5f:19:cc: + 53:f8:68:6b:61:fe:20:ba:b5:af:8a:9c:04:ea:bd: + 25:2c:e5:bf:7d:89:f3:d0:8d:90:79:7e:62:0e:88: + 47:cc:cf:94:26:00:76:c9:c1:93:85:a5:1b:19:5e: + 34:73:3a:eb:23:72:23:03:d6:83:29:26:4c:5d:4f: + ad:7d:1c:1f:e7:85:c3:d4:4e:0a:b2:1f:68:c4:67: + 19:c3 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Subject Key Identifier: + E8:97:A6:14:2C:C4:75:73:3A:9B:AD:8D:91:3E:1D:6C:7E:C0:C1:9D + X509v3 Authority Key Identifier: + keyid:65:6F:35:7D:11:0B:0B:FD:3B:CF:E6:3B:95:FA:35:6B:80:0D:27:E9 + DirName:/CN=Sensu-test CA + serial:2D:E8:6C:6F:A5:0E:7D:63:A8:B1:0B:3D:86:1D:2E:D0:62:5F:21:47 + + X509v3 Extended Key Usage: + TLS Web Server Authentication + X509v3 Key Usage: + Digital Signature, Key Encipherment + X509v3 Subject Alternative Name: + IP Address:176.16.117.105 + Signature Algorithm: sha256WithRSAEncryption + 13:92:90:3f:2e:08:d3:e7:ce:77:4d:fe:db:d0:75:ed:4f:ca: + ab:6e:2a:8c:6d:05:1d:b2:d0:be:2f:ab:f5:ff:84:71:e0:24: + 93:92:89:2a:e4:14:ef:1c:dc:d3:e7:f1:6e:44:cc:35:b9:90: + 9b:c4:c3:18:64:97:bb:70:67:22:59:94:cd:c0:f3:f2:85:7c: + f6:d0:97:e2:f5:4a:fa:6a:0a:94:19:b3:de:90:84:1c:49:12: + 9c:f7:29:93:51:07:9f:84:61:36:19:86:02:6e:7f:8f:1c:e2: + 86:93:8b:cb:04:f4:54:b6:9b:22:ee:c3:bb:ab:6f:7d:f8:ae: + 9e:6b:71:e1:f7:4b:73:21:a2:f4:d3:44:1e:20:f5:55:10:9b: + ce:24:33:24:5f:21:a3:d3:75:c3:76:0d:b5:35:b3:95:d9:da: + 97:ed:15:9e:3e:6e:df:0f:a3:2a:10:e4:f9:49:b8:b2:f2:b2: + 99:e3:99:12:88:20:5c:2f:ad:1a:d6:e7:f1:29:e4:4d:e3:fb: + 30:72:1a:1b:53:69:ac:40:f7:a5:84:8d:12:e9:6f:78:a6:b9: + c0:b5:28:28:64:88:c9:da:69:d0:8e:6b:e9:7c:56:9f:68:ad: + 09:76:e0:a3:26:21:86:65:ff:7b:15:89:12:41:83:52:04:5d: + 61:b4:88:25 +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIRAKATAGcTMdQlR/oaSCt2qX8wDQYJKoZIhvcNAQELBQAw +GDEWMBQGA1UEAwwNU2Vuc3UtdGVzdCBDQTAeFw0xOTExMDYxNzIyNDJaFw0yMjEw +MjExNzIyNDJaMBQxEjAQBgNVBAMMCXNlbnN1LWFwaTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAMQWcdM7CCYegWCWbpx17Fjy3I8i58hkuVJVEVHV69eu +f0iBJepOZjPuvnMlhO0CpvVf1MArptOux0FkLF5LbBKSZFNBSlm279KIJJYmMOOY +JUXGWcTmMyyiOCXO9dXSujloG+zxd+bhIBlA5e2MvERAzgwmfYZgDRkfMVmFpWrS +Qcgc1n570R/UNS7CyPbaAzlUDuaMGybk5jwfiGTHT0aJcGxbJu7yoyaTrABPXdOL +XxnMU/hoa2H+ILq1r4qcBOq9JSzlv32J89CNkHl+Yg6IR8zPlCYAdsnBk4WlGxle +NHM66yNyIwPWgykmTF1PrX0cH+eFw9ROCrIfaMRnGcMCAwEAAaOBtTCBsjAJBgNV +HRMEAjAAMB0GA1UdDgQWBBTol6YULMR1czqbrY2RPh1sfsDBnTBTBgNVHSMETDBK +gBRlbzV9EQsL/TvP5juV+jVrgA0n6aEcpBowGDEWMBQGA1UEAwwNU2Vuc3UtdGVz +dCBDQYIULehsb6UOfWOosQs9hh0u0GJfIUcwEwYDVR0lBAwwCgYIKwYBBQUHAwEw +CwYDVR0PBAQDAgWgMA8GA1UdEQQIMAaHBLAQdWkwDQYJKoZIhvcNAQELBQADggEB +ABOSkD8uCNPnzndN/tvQde1PyqtuKoxtBR2y0L4vq/X/hHHgJJOSiSrkFO8c3NPn +8W5EzDW5kJvEwxhkl7twZyJZlM3A8/KFfPbQl+L1SvpqCpQZs96QhBxJEpz3KZNR +B5+EYTYZhgJuf48c4oaTi8sE9FS2myLuw7urb334rp5rceH3S3MhovTTRB4g9VUQ +m84kMyRfIaPTdcN2DbU1s5XZ2pftFZ4+bt8PoyoQ5PlJuLLyspnjmRKIIFwvrRrW +5/Ep5E3j+zByGhtTaaxA96WEjRLpb3imucC1KChkiMnaadCOa+l8Vp9orQl24KMm +IYZl/3sViRJBg1IEXWG0iCU= +-----END CERTIFICATE----- diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/sensu-api.key b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/sensu-api.key new file mode 100644 index 00000000..e5854027 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/sensu-api.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDEFnHTOwgmHoFg +lm6cdexY8tyPIufIZLlSVRFR1evXrn9IgSXqTmYz7r5zJYTtAqb1X9TAK6bTrsdB +ZCxeS2wSkmRTQUpZtu/SiCSWJjDjmCVFxlnE5jMsojglzvXV0ro5aBvs8Xfm4SAZ +QOXtjLxEQM4MJn2GYA0ZHzFZhaVq0kHIHNZ+e9Ef1DUuwsj22gM5VA7mjBsm5OY8 +H4hkx09GiXBsWybu8qMmk6wAT13Ti18ZzFP4aGth/iC6ta+KnATqvSUs5b99ifPQ +jZB5fmIOiEfMz5QmAHbJwZOFpRsZXjRzOusjciMD1oMpJkxdT619HB/nhcPUTgqy +H2jEZxnDAgMBAAECggEAK8zTqhpCjLk9rwSLOpnArHG7QKHMYl/VYYWs87m0D55j +wh9PB9JxU+JdWj0kPwjboG3CiRZ3Eku1KG8m1f1E67UVgd9Qq0+IrF9KxNtNClme +4cIXpTrCbZLitddP5G5IuK//pOKfJMxeriVn9rL8Dsbm/6HNYimsY1MrY9LNi1l9 +zAxSAihUPI0JM0DEA+5C4vaIFiOt1ulhTXuRU94v8gjcDMnpp/rB8pP0XvpjNdzb +7LExeMrywEOnxGEju1Co5Q5jp2epDwjSowlTg+wJCu0dlCW6uWGV0umW+KwK+aQ0 +Z/Mo4VwE4kRO/2vth9N0tboHOL5EoDdKprk1v5E1+QKBgQDi8nqgaDlfsO4HIGPE +NiQvevxQBQ7D2vElt04fYpla4iTJpmXeo35194sW40pAbt2N3/xsEAFZPGWiGWVe +km8tLdF2vHHNp4wFHEjb86hu6h07XAikr+1+ZI+musn09lgH8gXmy610PJ+OLHnf +Rmkfhr73EhBa2PLp4GArwq6D/wKBgQDdMKL/SkdwCUqz3Z3UP2cRBBNi0JDXCGTb +jpQMvJZtrosxR9su55w5ZtIKLRPn7fR0AfRztBKUQBmEYgcp+1VkhIA5v6GGzNAj +af/dVZhyXV9OPdSc5pDk+bWbrj8GxQr/5C04clVnwZ2ESNm8tulpwo2MZPPoj/vk +tuN7UZZaPQKBgQCAZLhVicFzzrBLXdqzhgHgzs5yIvpgebxWHydWgDzMewZfAwG8 +/HguGzcYYsx+OXqkqmSvajqpFo9VLtL3txao07QeXaxwsep4dbEOpwHShia1j3Lg +YRuWlyPiKujY6omRLS6DjRV7nlSSZb0pQTd1+5CMTS7thrGe+S7PcxuyVwKBgBgK +WDztAtSvfdoMxUGzXm1gBwdfac6lT+j5FyhHOwZSyTgi+jSf4b/vZ/bJLXewyjft +mncU5EwOp3dW/DZY5dAWAqXEKTcwfZLLy45v5jDP6zLiz3/6I1dvuIhiKOGAexCS +6UNQUe4EAi3FiTzUmIvxJFdVBZmKRLN4GUNm+7N1AoGAA9VfdyWQYTu+z0y91/ob +y77j0hiG+A7HK7PNW2ZIwnhjczxuQ7Dcpk/GIqnOamrslLMNRU9zNETPRdEkoMnX +I8ZyrGFKCby9FNBHpgbDFjY7+EgaoWwVjt4UhbZOQQAVVkoBS3CUSMFxs29iAun1 +uqY0wSO5KtVggDqqcZJpkHw= +-----END PRIVATE KEY----- diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/sensu-dashboard.crt b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/sensu-dashboard.crt new file mode 100644 index 00000000..8cc61dad --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/sensu-dashboard.crt @@ -0,0 +1,87 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 9a:76:0c:19:84:14:e0:bf:39:06:f1:25:83:09:32:fc + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=Sensu-test CA + Validity + Not Before: Nov 6 17:22:42 2019 GMT + Not After : Oct 21 17:22:42 2022 GMT + Subject: CN=sensu-dashboard + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:b0:e0:10:ec:e6:50:88:db:f6:78:ba:21:a4:c4: + 17:b5:f0:57:9b:54:da:4d:1f:75:d2:da:15:4f:17: + 1f:d7:cd:15:0e:1e:39:7c:50:6a:32:11:11:9c:76: + 01:77:8e:a4:07:49:55:49:8e:96:1a:c7:a5:90:3e: + 69:4d:ec:a9:45:a3:07:05:89:7a:dd:a6:34:cb:29: + c8:36:71:ba:b6:ec:d8:20:60:58:5f:23:ca:61:78: + e1:ca:a5:6a:b6:ea:d9:dd:4f:ce:fb:2e:9c:9d:06: + eb:de:d9:be:58:d3:a8:da:2b:2d:4f:f9:7a:32:43: + 52:68:60:41:e5:b7:65:c7:4d:ec:ae:56:99:b1:05: + 66:5c:77:03:41:28:e1:b9:75:7f:67:3c:09:e7:d3: + 4d:f2:77:51:74:2c:8b:82:ab:5f:97:ea:c0:f7:45: + e5:85:35:a2:5c:95:65:79:a1:dd:bd:bf:a6:e8:76: + ae:36:c7:36:7c:e7:d9:e8:57:65:e2:3f:ea:0b:8d: + 16:61:3c:c9:3b:89:3d:a7:cf:5a:09:4c:f2:98:03: + fd:6d:d0:20:8c:12:4c:a3:38:af:53:71:75:54:30: + f9:e8:d3:44:4e:f2:fc:b8:ec:f9:49:ff:c8:bc:27: + 00:b2:2c:c6:15:05:b4:5b:f7:ac:20:fc:df:b5:56: + 26:e3 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Subject Key Identifier: + 08:7A:07:19:64:FD:EA:87:C3:25:FE:65:E6:8B:A7:9E:E2:46:8C:A7 + X509v3 Authority Key Identifier: + keyid:65:6F:35:7D:11:0B:0B:FD:3B:CF:E6:3B:95:FA:35:6B:80:0D:27:E9 + DirName:/CN=Sensu-test CA + serial:2D:E8:6C:6F:A5:0E:7D:63:A8:B1:0B:3D:86:1D:2E:D0:62:5F:21:47 + + X509v3 Extended Key Usage: + TLS Web Server Authentication + X509v3 Key Usage: + Digital Signature, Key Encipherment + X509v3 Subject Alternative Name: + IP Address:176.16.117.105 + Signature Algorithm: sha256WithRSAEncryption + 47:ca:52:10:7a:01:5e:e3:0d:ae:e7:5e:a7:f8:19:d2:0e:0a: + 03:ef:9f:dc:2f:4f:94:05:15:ae:ee:66:6a:7c:41:cc:07:8f: + 75:09:8c:be:e6:ea:8c:1c:f2:d5:20:23:76:b1:cb:ec:da:1e: + 07:83:23:3b:ce:62:07:65:1f:49:26:f5:59:50:d0:2b:b2:5f: + 61:9d:6d:6b:1f:e4:8d:60:ae:c2:fd:02:40:38:4f:6c:d3:48: + 5c:ca:4e:ac:70:88:63:57:21:f8:47:57:be:d9:bb:ec:ce:e4: + ac:be:81:f7:21:60:95:6d:56:b9:ce:93:8b:f5:3f:c9:de:77: + 3b:34:9a:73:d1:9a:99:ea:35:47:ef:fb:01:48:98:6d:f9:dd: + d2:16:7e:f2:d5:17:d0:66:c7:b1:2b:55:18:80:f7:7d:5d:d3: + 1b:11:18:ef:1d:e4:35:a5:7d:25:a8:b3:cd:80:5c:7e:95:4d: + 9b:79:e1:b0:38:84:75:58:f1:45:4f:48:ed:8b:84:9b:a6:26: + a0:f6:d1:f0:f7:f2:1c:e7:47:ee:b3:30:a9:61:82:4b:fc:d9: + dc:45:ab:2c:f4:30:60:19:0e:92:cc:67:d8:3b:6e:26:80:fb: + b3:3c:21:4e:66:45:fe:05:55:6a:88:04:74:85:ca:1a:75:70: + 38:0f:ca:d4 +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgIRAJp2DBmEFOC/OQbxJYMJMvwwDQYJKoZIhvcNAQELBQAw +GDEWMBQGA1UEAwwNU2Vuc3UtdGVzdCBDQTAeFw0xOTExMDYxNzIyNDJaFw0yMjEw +MjExNzIyNDJaMBoxGDAWBgNVBAMMD3NlbnN1LWRhc2hib2FyZDCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBALDgEOzmUIjb9ni6IaTEF7XwV5tU2k0fddLa +FU8XH9fNFQ4eOXxQajIREZx2AXeOpAdJVUmOlhrHpZA+aU3sqUWjBwWJet2mNMsp +yDZxurbs2CBgWF8jymF44cqlarbq2d1PzvsunJ0G697ZvljTqNorLU/5ejJDUmhg +QeW3ZcdN7K5WmbEFZlx3A0Eo4bl1f2c8CefTTfJ3UXQsi4KrX5fqwPdF5YU1olyV +ZXmh3b2/puh2rjbHNnzn2ehXZeI/6guNFmE8yTuJPafPWglM8pgD/W3QIIwSTKM4 +r1NxdVQw+ejTRE7y/Ljs+Un/yLwnALIsxhUFtFv3rCD837VWJuMCAwEAAaOBtTCB +sjAJBgNVHRMEAjAAMB0GA1UdDgQWBBQIegcZZP3qh8Ml/mXmi6ee4kaMpzBTBgNV +HSMETDBKgBRlbzV9EQsL/TvP5juV+jVrgA0n6aEcpBowGDEWMBQGA1UEAwwNU2Vu +c3UtdGVzdCBDQYIULehsb6UOfWOosQs9hh0u0GJfIUcwEwYDVR0lBAwwCgYIKwYB +BQUHAwEwCwYDVR0PBAQDAgWgMA8GA1UdEQQIMAaHBLAQdWkwDQYJKoZIhvcNAQEL +BQADggEBAEfKUhB6AV7jDa7nXqf4GdIOCgPvn9wvT5QFFa7uZmp8QcwHj3UJjL7m +6owc8tUgI3axy+zaHgeDIzvOYgdlH0km9VlQ0CuyX2GdbWsf5I1grsL9AkA4T2zT +SFzKTqxwiGNXIfhHV77Zu+zO5Ky+gfchYJVtVrnOk4v1P8nedzs0mnPRmpnqNUfv ++wFImG353dIWfvLVF9Bmx7ErVRiA931d0xsRGO8d5DWlfSWos82AXH6VTZt54bA4 +hHVY8UVPSO2LhJumJqD20fD38hznR+6zMKlhgkv82dxFqyz0MGAZDpLMZ9g7biaA ++7M8IU5mRf4FVWqIBHSFyhp1cDgPytQ= +-----END CERTIFICATE----- diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/sensu-dashboard.key b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/sensu-dashboard.key new file mode 100644 index 00000000..da9a9f95 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/files/sensu-dashboard.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCw4BDs5lCI2/Z4 +uiGkxBe18FebVNpNH3XS2hVPFx/XzRUOHjl8UGoyERGcdgF3jqQHSVVJjpYax6WQ +PmlN7KlFowcFiXrdpjTLKcg2cbq27NggYFhfI8pheOHKpWq26tndT877LpydBuve +2b5Y06jaKy1P+XoyQ1JoYEHlt2XHTeyuVpmxBWZcdwNBKOG5dX9nPAnn003yd1F0 +LIuCq1+X6sD3ReWFNaJclWV5od29v6bodq42xzZ859noV2XiP+oLjRZhPMk7iT2n +z1oJTPKYA/1t0CCMEkyjOK9TcXVUMPno00RO8vy47PlJ/8i8JwCyLMYVBbRb96wg +/N+1VibjAgMBAAECggEBAKrHTdBO+MeMCsi6fy2FoJcs/omePkFk9PCnXRfGbhqB +i6jcCgk7461/yY9WaUg006+tYMnrAIfO6M8DU83HihEQvgDco2NLzYG0T/oxiWSB +/pQUMn96IThH7UsquITw8Xa8Tk88zD7ZpfjAKQe/JjOwqMmlShUp53GcL0RL9due +huI8QBzbiKvBUMME1A8yaNbyy6BwHBjxfJHXaSeNwmUnlKS0A/jMNKtav8jCfdnt +QEuBdL4kexZf/lt+Mu6PmODoeT8VZRrxEf4v79MZxJZNTLOVUjIhELQF1UESkbqI +RoU1vVzU7CsbOWHAaym7ShZK3QDzfnVLkhFAEwsRPsECgYEA6n+SULpYTR7CtBlw +zVSPePe8CiA2W28VoQrXk/9FY/j1o2cGyHidl0mVuagIROW+LyCv+HQxinJzSQrK +AW/aoPgrUUfkTbpmxrPvM5FszG5cVkdFYMsW2Ni/CY8LCRdUReICdEl5xDMMfB2e +/yM6oNFh7LLNrSwUsGlifJ7OcPMCgYEAwRflbOzB98lV6guQJRQISqO4+9OCIXJL +ZDoHxaJ9TVr3cEFUYfm7gFvtgn2QFlqgfCN1omY3vlTuYp4llFw/Z4NX9F673erU +jtt26C1pxmC7kdGApupz+gVWSqO4WhOSgpEUErUX9aX+YEy6UmdzkHnb/1cyT1eG +lVvz2/eUblECgYA9aw5agIQSJuVeIG+wB97QEyq4CDnUduLWXC2cgLae+Zz0oE5h +gV3dOxOxHbaUvQuz8j7Et0ImfdV+IwpHmBFOKdHGpyq/xPuYPZaADi3N2XXrzxz3 +vhmM0DAxA7sjNW4II6r65Ce1YJ17gJKdRo/bgRvB0A8YtTvx/JgkBcASSwKBgBzx +2BJb6zeZlqde1Fy6hAOsRy54pikdWO/NQxz9HotZ9318TYniRZkYLqJA8DhpnWT+ +a8PMTs7ZLGLcEgYLTfXWWnjnOoIpkXNYsppbNF/oYDWbkg1zV69C3YySvi/Cf1PT +K48iVlUcbOVCmyt/FnOx0KiWCZSbKjF5dzSiCD4BAoGAeWRARbEkptVmG1iBdhU1 +SNy1WhNU2L6TfUEbevYWWElQYZiQmwR4XvOfRaR3Dx8pTicABqcAwLgYPbqzCknu +QcPULpLU/EJKP9xRhZClJVSJZ7JRHQrZt/i0WRVuTCCtafp50dns0vMYWBF4vVrE +KUa5kMVzh3unaWHUCJ7qODw= +-----END PRIVATE KEY----- diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/molecule.yml new file mode 100644 index 00000000..9d526ef3 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/molecule.yml @@ -0,0 +1,18 @@ +--- +scenario: + test_sequence: + - destroy + - create + - prepare + - converge + - destroy + +platforms: + - name: centos + image: quay.io/xlab-steampunk/sensu-go-tests-centos:7 + pre_build_image: true + pull: true + override_command: false + privileged: true + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:ro diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/prepare.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/prepare.yml new file mode 100644 index 00000000..40ba6223 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_backend_secured/prepare.yml @@ -0,0 +1,24 @@ +--- +- name: Prepare + hosts: all + + tasks: + - name: Create sensu group + group: + name: sensu + + - name: Create sensu user + # We need FQCN here because we are running test from within the + # collection. In this case, our collection becomes the default + # collection and so the sensu.sensu_go.user module shadows the builtin + # one. + ansible.builtin.user: + name: sensu + groups: sensu + + - name: Create /etc/sensu folder + file: + state: directory + path: /etc/sensu + owner: sensu + group: sensu diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_custom_build/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_custom_build/converge.yml new file mode 100644 index 00000000..0aa2bf12 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_custom_build/converge.yml @@ -0,0 +1,50 @@ +--- +- name: Test different version builds + hosts: all + tasks: + - name: Install a specific build + include_role: + name: sensu.sensu_go.install + vars: + components: [sensu-go-backend] + channel: testing + version: 5.16.0 + build: 8290 + + - package_facts: + manager: auto + + - assert: + that: + - ansible_facts.packages["sensu-go-backend"][0].version == "5.16.0-8290" + when: ansible_facts.packages["sensu-go-backend"][0].source == "apt" + + - assert: + that: + - ansible_facts.packages["sensu-go-backend"][0].version == "5.16.0" + - ansible_facts.packages["sensu-go-backend"][0].release == "8290" + when: ansible_facts.packages["sensu-go-backend"][0].source == "rpm" + + - name: Update to a specific build + include_role: + name: sensu.sensu_go.install + tasks_from: packages + vars: + components: [sensu-go-backend] + channel: testing + version: 5.16.0 + build: 8320 + + - package_facts: + manager: auto + + - assert: + that: + - ansible_facts.packages["sensu-go-backend"][0].version == "5.16.0-8320" + when: ansible_facts.packages["sensu-go-backend"][0].source == "apt" + + - assert: + that: + - ansible_facts.packages["sensu-go-backend"][0].version == "5.16.0" + - ansible_facts.packages["sensu-go-backend"][0].release == "8320" + when: ansible_facts.packages["sensu-go-backend"][0].source == "rpm" diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_custom_build/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_custom_build/molecule.yml new file mode 100644 index 00000000..59e497b6 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_custom_build/molecule.yml @@ -0,0 +1,11 @@ +--- +platforms: + - name: ubuntu + image: quay.io/xlab-steampunk/sensu-go-tests-ubuntu:16.04 + pre_build_image: true + pull: true + + - name: centos + image: quay.io/xlab-steampunk/sensu-go-tests-centos:7 + pre_build_image: true + pull: true diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_custom_version/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_custom_version/converge.yml new file mode 100644 index 00000000..70be5856 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_custom_version/converge.yml @@ -0,0 +1,31 @@ +--- +- name: Converge with older versions of components + hosts: all + + tasks: + - name: Install all components + include_role: + name: sensu.sensu_go.install + vars: + components: + - sensu-go-backend + - sensu-go-agent + - sensu-go-cli + version: 5.14.1 + + - name: Make sure components are installed + command: + cmd: "{{ item }} version" + loop: + - sensu-backend + - sensu-agent + - sensuctl + register: result + + - assert: + quiet: true + that: + - item.stdout is search("5.14.1") + loop: "{{ result.results }}" + loop_control: + label: "{{ item.item }}" # Reduce verbosity a bit diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_custom_version/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_custom_version/molecule.yml new file mode 100644 index 00000000..59e497b6 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_custom_version/molecule.yml @@ -0,0 +1,11 @@ +--- +platforms: + - name: ubuntu + image: quay.io/xlab-steampunk/sensu-go-tests-ubuntu:16.04 + pre_build_image: true + pull: true + + - name: centos + image: quay.io/xlab-steampunk/sensu-go-tests-centos:7 + pre_build_image: true + pull: true diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_default_deb/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_default_deb/converge.yml new file mode 100644 index 00000000..7ac2dd83 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_default_deb/converge.yml @@ -0,0 +1,13 @@ +--- +- name: Converge with latest versions of components + hosts: all + + tasks: + - name: Install all components + include_role: + name: sensu.sensu_go.install + vars: + components: + - sensu-go-backend + - sensu-go-agent + - sensu-go-cli diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_default_deb/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_default_deb/molecule.yml new file mode 100644 index 00000000..1261bc9d --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_default_deb/molecule.yml @@ -0,0 +1,35 @@ +--- +scenario: + test_sequence: + - destroy + - create + - converge + - verify + - check + - destroy + +platforms: + - name: debian-9 + image: quay.io/xlab-steampunk/sensu-go-tests-debian:9 + pre_build_image: true + pull: true + + - name: debian-10 + image: quay.io/xlab-steampunk/sensu-go-tests-debian:10 + pre_build_image: true + pull: true + + - name: ubuntu-14.04 + image: quay.io/xlab-steampunk/sensu-go-tests-ubuntu:14.04 + pre_build_image: true + pull: true + + - name: ubuntu-16.04 + image: quay.io/xlab-steampunk/sensu-go-tests-ubuntu:16.04 + pre_build_image: true + pull: true + + - name: ubuntu-18.04 + image: quay.io/xlab-steampunk/sensu-go-tests-ubuntu:18.04 + pre_build_image: true + pull: true diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_default_deb/verify.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_default_deb/verify.yml new file mode 100644 index 00000000..8dadfc5e --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_default_deb/verify.yml @@ -0,0 +1,12 @@ +--- +- name: Verify + hosts: all + + tasks: + - name: Make sure components are installed + command: + cmd: "{{ item }} version" + loop: + - sensu-backend + - sensu-agent + - sensuctl diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_default_rpm/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_default_rpm/converge.yml new file mode 100644 index 00000000..7ac2dd83 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_default_rpm/converge.yml @@ -0,0 +1,13 @@ +--- +- name: Converge with latest versions of components + hosts: all + + tasks: + - name: Install all components + include_role: + name: sensu.sensu_go.install + vars: + components: + - sensu-go-backend + - sensu-go-agent + - sensu-go-cli diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_default_rpm/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_default_rpm/molecule.yml new file mode 100644 index 00000000..a0d8b5e6 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_default_rpm/molecule.yml @@ -0,0 +1,30 @@ +--- +scenario: + test_sequence: + - destroy + - create + - converge + - verify + - check + - destroy + +platforms: + - name: redhat-7 + image: quay.io/xlab-steampunk/sensu-go-tests-redhat:7 + pre_build_image: true + pull: true + + - name: amazon-1 + image: quay.io/xlab-steampunk/sensu-go-tests-amazon:1 + pre_build_image: true + pull: true + + - name: amazon-2 + image: quay.io/xlab-steampunk/sensu-go-tests-amazon:2 + pre_build_image: true + pull: true + + - name: oracle-8 + image: quay.io/xlab-steampunk/sensu-go-tests-oracle:8 + pre_build_image: true + pull: true diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_default_rpm/verify.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_default_rpm/verify.yml new file mode 100644 index 00000000..8dadfc5e --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_default_rpm/verify.yml @@ -0,0 +1,12 @@ +--- +- name: Verify + hosts: all + + tasks: + - name: Make sure components are installed + command: + cmd: "{{ item }} version" + loop: + - sensu-backend + - sensu-agent + - sensuctl diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_downgrade/converge.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_downgrade/converge.yml new file mode 100644 index 00000000..b498ee94 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_downgrade/converge.yml @@ -0,0 +1,39 @@ +--- +- name: Test downgrade + hosts: all + + tasks: + - name: Install initial version components + include_role: + name: sensu.sensu_go.install + vars: + components: + - sensu-go-agent + version: 6.2.5 + + - name: Make sure components are installed + command: + cmd: sensu-agent version + register: result + - assert: + # quiet: true + that: + - result.stdout is search("6.2.5") + + - name: Downgrade components + include_role: + name: sensu.sensu_go.install + tasks_from: packages + vars: + components: + - sensu-go-agent + version: 6.1.4 + + - name: Make sure components were downgraded + command: + cmd: sensu-agent version + register: result + - assert: + quiet: true + that: + - result.stdout is search("6.1.4") diff --git a/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_downgrade/molecule.yml b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_downgrade/molecule.yml new file mode 100644 index 00000000..c276ce55 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/integration/molecule/role_install_downgrade/molecule.yml @@ -0,0 +1,13 @@ +--- +platforms: + # yum test + - name: redhat-7 + image: quay.io/xlab-steampunk/sensu-go-tests-redhat:7 + pre_build_image: true + pull: true + + # apt test + - name: debian-10 + image: quay.io/xlab-steampunk/sensu-go-tests-debian:10 + pre_build_image: true + pull: true diff --git a/ansible_collections/sensu/sensu_go/tests/sanity/ignore-2.10.txt b/ansible_collections/sensu/sensu_go/tests/sanity/ignore-2.10.txt new file mode 100644 index 00000000..af9fcfb5 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/sanity/ignore-2.10.txt @@ -0,0 +1,4 @@ +plugins/modules/bonsai_asset.py validate-modules:nonexistent-parameter-documented # This is not a real module, more helper for the asset module +tests/unit/plugins/module_utils/test_utils.py pylint:ansible-deprecated-no-collection-name # sanity misdetects this as module deprecation call +tools/windows-versions.py replace-urlopen # Maintainer tools should not be bound by the general collection constraints +tests/sanity/validate-role-metadata.py replace-urlopen # Maintainer tools should not be bound by the general collection constraints diff --git a/ansible_collections/sensu/sensu_go/tests/sanity/ignore-2.11.txt b/ansible_collections/sensu/sensu_go/tests/sanity/ignore-2.11.txt new file mode 100644 index 00000000..af9fcfb5 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/sanity/ignore-2.11.txt @@ -0,0 +1,4 @@ +plugins/modules/bonsai_asset.py validate-modules:nonexistent-parameter-documented # This is not a real module, more helper for the asset module +tests/unit/plugins/module_utils/test_utils.py pylint:ansible-deprecated-no-collection-name # sanity misdetects this as module deprecation call +tools/windows-versions.py replace-urlopen # Maintainer tools should not be bound by the general collection constraints +tests/sanity/validate-role-metadata.py replace-urlopen # Maintainer tools should not be bound by the general collection constraints diff --git a/ansible_collections/sensu/sensu_go/tests/sanity/ignore-2.12.txt b/ansible_collections/sensu/sensu_go/tests/sanity/ignore-2.12.txt new file mode 100644 index 00000000..af9fcfb5 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/sanity/ignore-2.12.txt @@ -0,0 +1,4 @@ +plugins/modules/bonsai_asset.py validate-modules:nonexistent-parameter-documented # This is not a real module, more helper for the asset module +tests/unit/plugins/module_utils/test_utils.py pylint:ansible-deprecated-no-collection-name # sanity misdetects this as module deprecation call +tools/windows-versions.py replace-urlopen # Maintainer tools should not be bound by the general collection constraints +tests/sanity/validate-role-metadata.py replace-urlopen # Maintainer tools should not be bound by the general collection constraints diff --git a/ansible_collections/sensu/sensu_go/tests/sanity/ignore-2.13.txt b/ansible_collections/sensu/sensu_go/tests/sanity/ignore-2.13.txt new file mode 100644 index 00000000..af9fcfb5 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/sanity/ignore-2.13.txt @@ -0,0 +1,4 @@ +plugins/modules/bonsai_asset.py validate-modules:nonexistent-parameter-documented # This is not a real module, more helper for the asset module +tests/unit/plugins/module_utils/test_utils.py pylint:ansible-deprecated-no-collection-name # sanity misdetects this as module deprecation call +tools/windows-versions.py replace-urlopen # Maintainer tools should not be bound by the general collection constraints +tests/sanity/validate-role-metadata.py replace-urlopen # Maintainer tools should not be bound by the general collection constraints diff --git a/ansible_collections/sensu/sensu_go/tests/sanity/ignore-2.14.txt b/ansible_collections/sensu/sensu_go/tests/sanity/ignore-2.14.txt new file mode 100644 index 00000000..af9fcfb5 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/sanity/ignore-2.14.txt @@ -0,0 +1,4 @@ +plugins/modules/bonsai_asset.py validate-modules:nonexistent-parameter-documented # This is not a real module, more helper for the asset module +tests/unit/plugins/module_utils/test_utils.py pylint:ansible-deprecated-no-collection-name # sanity misdetects this as module deprecation call +tools/windows-versions.py replace-urlopen # Maintainer tools should not be bound by the general collection constraints +tests/sanity/validate-role-metadata.py replace-urlopen # Maintainer tools should not be bound by the general collection constraints diff --git a/ansible_collections/sensu/sensu_go/tests/sanity/ignore-2.15.txt b/ansible_collections/sensu/sensu_go/tests/sanity/ignore-2.15.txt new file mode 100644 index 00000000..af9fcfb5 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/sanity/ignore-2.15.txt @@ -0,0 +1,4 @@ +plugins/modules/bonsai_asset.py validate-modules:nonexistent-parameter-documented # This is not a real module, more helper for the asset module +tests/unit/plugins/module_utils/test_utils.py pylint:ansible-deprecated-no-collection-name # sanity misdetects this as module deprecation call +tools/windows-versions.py replace-urlopen # Maintainer tools should not be bound by the general collection constraints +tests/sanity/validate-role-metadata.py replace-urlopen # Maintainer tools should not be bound by the general collection constraints diff --git a/ansible_collections/sensu/sensu_go/tests/sanity/ignore-2.9.txt b/ansible_collections/sensu/sensu_go/tests/sanity/ignore-2.9.txt new file mode 100644 index 00000000..5a7ce765 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/sanity/ignore-2.9.txt @@ -0,0 +1,3 @@ +plugins/modules/bonsai_asset.py validate-modules:nonexistent-parameter-documented # This is not a real module, more helper for the asset module +tools/windows-versions.py replace-urlopen # Maintainer tools should not be bound by the general collection constraints +tests/sanity/validate-role-metadata.py replace-urlopen # Maintainer tools should not be bound by the general collection constraints diff --git a/ansible_collections/sensu/sensu_go/tests/sanity/validate-role-metadata.py b/ansible_collections/sensu/sensu_go/tests/sanity/validate-role-metadata.py new file mode 100755 index 00000000..b8544ff9 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/sanity/validate-role-metadata.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import argparse +import json +import os +import sys + +import yaml + +import urllib.request + + +def _get_arg_parser(): + parser = argparse.ArgumentParser(description="Validate role metadata") + parser.add_argument("role", nargs="+", help="role path") + return parser + + +def _validate_role_platforms(platforms): + base_url = "https://galaxy.ansible.com/api/v1/platforms/?name={0}&release={1}" + msgs = [] + for platform in platforms: + for release in platform["versions"]: + url = base_url.format(platform["name"], release) + f = urllib.request.urlopen(url) + + if len(json.loads(f.read().decode('utf-8'))["results"]) != 1: + msgs.append(("ERROR", "Invalid platform '{0} {1}'".format( + platform["name"], release, + ))) + + return msgs + + +def _validate_role(role_path): + meta_file = os.path.join(role_path, "meta", "main.yml") + with open(meta_file) as fd: + galaxy_info = yaml.safe_load(fd)["galaxy_info"] + + msgs = [] + msgs.extend(_validate_role_platforms(galaxy_info["platforms"])) + + return msgs + + +def main(): + args = _get_arg_parser().parse_args() + no_msgs = 0 + for role in args.role: + msgs = _validate_role(role) + for msg in msgs: + no_msgs += 1 + print("{0}: {1}".format(*msg)) + + return 0 if no_msgs == 0 else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/action/test_bonsai_asset.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/action/test_bonsai_asset.py new file mode 100644 index 00000000..100b30c6 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/action/test_bonsai_asset.py @@ -0,0 +1,316 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import sys + +import pytest + +from ansible.playbook.task import Task + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + bonsai, errors, +) +from ansible_collections.sensu.sensu_go.plugins.action import bonsai_asset + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestValidate: + @pytest.mark.parametrize("name,args,required,typ", [ + # Required values must match the selected type. + ("a", dict(a=3), True, int), + ("a", dict(a=3.3), True, float), + ("a", dict(a="b"), True, str), + ("a", dict(a=[]), True, list), + ("a", dict(a={}), True, dict), + # Optional values are not checked for type-correctness if they are + # missing. + ("a", dict(), False, int), + ("a", dict(), False, float), + ("a", dict(), False, str), + ("a", dict(), False, list), + ("a", dict(), False, dict), + ]) + def test_valid_values(self, name, args, required, typ): + bonsai_asset.validate(name, args, required, typ) + + def test_missing_required(self): + with pytest.raises(errors.Error, match="required"): + bonsai_asset.validate("a", {}, True, str) + + def test_invalid_type(self): + with pytest.raises(errors.Error, match="should"): + bonsai_asset.validate("a", dict(a=3), True, str) + + def test_invalid_type_for_optional_value(self): + with pytest.raises(errors.Error, match="should"): + bonsai_asset.validate("a", dict(a=3), False, dict) + + +class TestValidateArguments: + def test_valid_minimal_args(self): + bonsai_asset.ActionModule.validate_arguments(dict( + name="abc", version="1.2.3", + )) + + def test_valid_all_args(self): + bonsai_asset.ActionModule.validate_arguments(dict( + name="abc", version="1.2.3", rename="def", + labels={}, annotations={}, + )) + + def test_valid_unicode_strings_python2(self): + bonsai_asset.ActionModule.validate_arguments(dict( + name=u"abc", version=u"1.2.3", rename=u"def", + labels={}, annotations={}, + )) + + def test_invalid_name(self): + with pytest.raises(errors.Error, match="name"): + bonsai_asset.ActionModule.validate_arguments(dict( + name=1.234, version="1.2.3", + )) + + def test_missing_name(self): + with pytest.raises(errors.Error, match="name"): + bonsai_asset.ActionModule.validate_arguments(dict( + version="1.2.3", + )) + + def test_invalid_version(self): + with pytest.raises(errors.Error, match="version"): + bonsai_asset.ActionModule.validate_arguments(dict( + name="abc", version=1.2, + )) + + def test_missing_version(self): + with pytest.raises(errors.Error, match="version"): + bonsai_asset.ActionModule.validate_arguments(dict( + name="abc", + )) + + def test_invalid_rename(self): + with pytest.raises(errors.Error, match="rename"): + bonsai_asset.ActionModule.validate_arguments(dict( + name="abc", version="1.2.3", rename=1, + )) + + def test_invalid_labels(self): + with pytest.raises(errors.Error, match="labels"): + bonsai_asset.ActionModule.validate_arguments(dict( + name="abc", version="1.2.3", labels=1, + )) + + def test_invalid_annotations(self): + with pytest.raises(errors.Error, match="annotations"): + bonsai_asset.ActionModule.validate_arguments(dict( + name="abc", version="1.2.3", annotations=1, + )) + + +class TestBuildAssetArgs: + def test_no_additional_metadata(self): + result = bonsai_asset.ActionModule.build_asset_args( + dict(name="test/asset", version="1.2.3"), + dict(builds=[], labels=None, annotations=None), + ) + + assert result == dict( + name="test/asset", + state="present", + builds=[], + ) + + def test_bonsai_metadata_only(self): + result = bonsai_asset.ActionModule.build_asset_args( + dict(name="test/asset", version="1.2.3"), + dict(builds=[], labels=dict(a="b"), annotations=dict(c="d")), + ) + + assert result == dict( + name="test/asset", + state="present", + builds=[], + annotations=dict(c="d"), + labels=dict(a="b"), + ) + + def test_user_metadata_only(self): + result = bonsai_asset.ActionModule.build_asset_args( + dict( + name="test/asset", + version="1.2.3", + labels=dict(my="label"), + annotations=dict(my="annotation"), + ), + dict(builds=[1, 2, 3], labels=None, annotations=None), + ) + + assert result == dict( + name="test/asset", + state="present", + builds=[1, 2, 3], + annotations=dict(my="annotation"), + labels=dict(my="label"), + ) + + def test_mixed_metadata(self): + result = bonsai_asset.ActionModule.build_asset_args( + dict( + name="test/asset", + version="1.2.3", + labels=dict(my="label"), + annotations=dict(my="annotation"), + ), + dict(builds=[], labels=dict(my="x", a="b"), annotations=dict(my="c")), + ) + + assert result == dict( + name="test/asset", + state="present", + builds=[], + annotations=dict(my="annotation"), + labels=dict(my="label", a="b"), + ) + + def test_rename(self): + result = bonsai_asset.ActionModule.build_asset_args( + dict(name="test/asset", version="1.2.3", rename="my-asset"), + dict(builds=[], labels=None, annotations=None), + ) + + assert result == dict( + name="my-asset", + state="present", + builds=[], + ) + + def test_auth_passthrough(self): + result = bonsai_asset.ActionModule.build_asset_args( + dict( + auth=dict(url="http://localhost:1234"), + name="test/asset", + version="1.2.3", + ), + dict(builds=[], labels=None, annotations=None), + ) + + assert result == dict( + auth=dict(url="http://localhost:1234"), + name="test/asset", + state="present", + builds=[], + ) + + def test_namespace_passthrough(self): + result = bonsai_asset.ActionModule.build_asset_args( + dict(namespace='default', name="test/asset", version="1.2.3"), + dict(builds=[], labels=None, annotations=None), + ) + + assert result == dict( + name="test/asset", + namespace='default', + state="present", + builds=[], + ) + + +class TestDownloadAssetDefinition: + def get_mock_action(self, mocker, result): + action = bonsai_asset.ActionModule( + mocker.MagicMock(), mocker.MagicMock(), mocker.MagicMock(), loader=None, + templar=None, shared_loader_obj=None, + ) + action._execute_module = mocker.MagicMock(return_value=result) + return action + + def test_download_on_control_node(self, mocker): + bonsai_params = mocker.patch.object(bonsai, "get_asset_parameters") + bonsai_params.return_value = dict(sample="value") + action = self.get_mock_action(mocker, {}) + + result = action.download_asset_definition( + on_remote=False, name="test/asset", version="1.2.3", task_vars=None, + ) + + assert result == dict(sample="value") + bonsai_params.assert_called_once() + action._execute_module.assert_not_called() + + def test_fail_download_on_control_node(self, mocker): + bonsai_params = mocker.patch.object(bonsai, "get_asset_parameters") + bonsai_params.side_effect = errors.BonsaiError("Bonsai bad") + action = self.get_mock_action(mocker, {}) + + with pytest.raises(errors.Error, match="Bonsai bad"): + action.download_asset_definition( + on_remote=False, name="test/asset", version="1.2.3", task_vars=None, + ) + + bonsai_params.assert_called_once() + action._execute_module.assert_not_called() + + def test_download_on_target_node(self, mocker): + bonsai_params = mocker.patch.object(bonsai, "get_asset_parameters") + action = self.get_mock_action(mocker, dict(asset="sample")) + + result = action.download_asset_definition( + on_remote=True, name="test/asset", version="1.2.3", task_vars=None, + ) + + assert result == "sample" + bonsai_params.assert_not_called() + action._execute_module.assert_called_once() + + def test_fail_on_target_node(self, mocker): + bonsai_params = mocker.patch.object(bonsai, "get_asset_parameters") + action = self.get_mock_action(mocker, dict(failed=True, msg="Bad err")) + + with pytest.raises(errors.Error, match="Bad err"): + action.download_asset_definition( + on_remote=True, name="test/asset", version="1.2.3", task_vars=None, + ) + + bonsai_params.assert_not_called() + action._execute_module.assert_called_once() + + +class TestRun: + def test_success(self, mocker): + task = mocker.MagicMock(Task, async_val=0, args=dict( + name="test/asset", + version="1.2.3", + )) + action = bonsai_asset.ActionModule( + task, mocker.MagicMock(), mocker.MagicMock(), loader=None, + templar=None, shared_loader_obj=None, + ) + action._execute_module = mocker.MagicMock(return_value=dict(a=3)) + action.download_asset_definition = mocker.MagicMock( + return_value=dict(builds=[], labels=None, annotations=None), + ) + + result = action.run() + + assert result == dict(a=3) + + def test_fail(self, mocker): + task = mocker.MagicMock(Task, async_val=0, args=dict( + name="test/asset", + )) + action = bonsai_asset.ActionModule( + task, mocker.MagicMock(), mocker.MagicMock(), loader=None, + templar=None, shared_loader_obj=None, + ) + action._execute_module = mocker.MagicMock(return_value=dict(a=3)) + action.download_asset_definition = mocker.MagicMock( + return_value=dict(builds=[], labels=None, annotations=None), + ) + + result = action.run() + + assert result["failed"] is True diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/filter/test_backends.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/filter/test_backends.py new file mode 100644 index 00000000..261080e3 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/filter/test_backends.py @@ -0,0 +1,54 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.filter import backends + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestBackends: + def test_backends_in_groups_no_ssl(self): + hostvars = { + "1.2.3.4": {"inventory_hostname": "1.2.3.4"}, + "1.2.3.5": {"inventory_hostname": "1.2.3.5"}, + "1.2.3.6": {"inventory_hostname": "1.2.3.6"}, + "1.2.3.7": {"inventory_hostname": "1.2.3.7"}, + } + groups = {"backends": ["1.2.3.4", "1.2.3.5"]} + + assert backends.backends(hostvars, groups) == [ + "ws://1.2.3.4:8081", + "ws://1.2.3.5:8081", + ] + + def test_backends_in_groups_ssl(self): + hostvars = { + "1.2.3.4": {"inventory_hostname": "1.2.3.4"}, + "1.2.3.5": {"inventory_hostname": "1.2.3.5"}, + "1.2.3.6": { + "inventory_hostname": "1.2.3.6", + "api_key_file": "path/to/key.file", + }, + "1.2.3.7": {"inventory_hostname": "1.2.3.7"}, + } + groups = {"backends": ["1.2.3.6"]} + + assert backends.backends(hostvars, groups) == ["wss://1.2.3.6:8081"] + + def test_backends_not_in_groups(self): + hostvars = { + "1.2.3.4": {"inventory_hostname": "1.2.3.4"}, + "1.2.3.5": {"inventory_hostname": "1.2.3.5"}, + "1.2.3.6": {"inventory_hostname": "1.2.3.6"}, + "1.2.3.7": {"inventory_hostname": "1.2.3.7"}, + } + groups = {} + + assert backends.backends(hostvars, groups) == [] diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/filter/test_package_name.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/filter/test_package_name.py new file mode 100644 index 00000000..794727ab --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/filter/test_package_name.py @@ -0,0 +1,58 @@ +# Copyright: (c) 2020, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.filter import package_name + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestPackageName: + def test_yum_latest_version(self): + assert "package" == package_name.package_name( + "yum", "package", "latest", "latest", + ) + + def test_yum_latest_build(self): + assert "package-123" == package_name.package_name( + "yum", "package", "123", "latest", + ) + + def test_yum_selected_build(self): + assert "package-123-456" == package_name.package_name( + "yum", "package", "123", "456", + ) + + def test_yum_ignore_build_if_latest_version(self): + assert "package" == package_name.package_name( + "yum", "package", "latest", "456", + ) + + def test_apt_latest_version(self): + assert "package" == package_name.package_name( + "apt", "package", "latest", "latest", + ) + + def test_apt_latest_build(self): + assert "package=123-*" == package_name.package_name( + "apt", "package", "123", "latest", + ) + + def test_apt_selected_build(self): + assert "package=123-456" == package_name.package_name( + "apt", "package", "123", "456", + ) + + def test_apt_ignore_build_if_latest_version(self): + assert "package" == package_name.package_name( + "apt", "package", "latest", "456", + ) diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_arguments.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_arguments.py new file mode 100644 index 00000000..ae62f1f5 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_arguments.py @@ -0,0 +1,158 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + arguments, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestGetSpec: + @pytest.mark.parametrize("param", [ + "auth", "state", "name", "labels", "annotations", + ]) + def test_valid_parameter(self, param): + assert set(arguments.get_spec(param).keys()) == set((param,)) + + def test_invalid_parameter(self): + with pytest.raises(KeyError): + arguments.get_spec("bad_parameter_name") + + def test_multiple_parameters(self): + assert set(arguments.get_spec("auth", "name", "labels").keys()) == set( + ("auth", "name", "labels") + ) + + +class TestGetSpecPayload: + def test_no_key(self): + params = dict( + name="name", + key="value" + ) + + assert arguments.get_spec_payload(params) == dict() + + def test_spec_payload(self): + params = dict( + name="name", + key="value", + ) + + assert arguments.get_spec_payload(params, "key") == dict( + key="value", + ) + + +class TestGetRenamedSpecPayload: + def test_no_mapping(self): + params = dict( + name="name", + key="value", + ) + assert arguments.get_renamed_spec_payload(params, dict()) == dict() + + def test_renamed_payload(self): + params = dict( + name="name", + key="value", + ) + mapping = dict( + name="new_name", + ) + assert arguments.get_renamed_spec_payload(params, mapping) == dict( + new_name="name", + ) + + +class TestGetMutationPayload: + def test_name_only(self): + params = dict( + name="name", + ) + + assert arguments.get_mutation_payload(params) == dict( + metadata=dict( + name="name", + ), + ) + + def test_name_and_namespace(self): + params = dict( + name="name", + namespace="space", + ) + + assert arguments.get_mutation_payload(params) == dict( + metadata=dict( + name="name", + namespace="space", + ), + ) + + def test_wanted_key(self): + params = dict( + name="name", + key="value", + ) + + assert arguments.get_mutation_payload(params, "key") == dict( + key="value", + metadata=dict( + name="name", + ), + ) + + def test_namespace_is_none(self): + params = dict( + name="name", + namespace=None, + ) + + with pytest.raises(AssertionError, match="BUG"): + arguments.get_mutation_payload(params) + + def test_labels(self): + params = dict( + name="name", + labels=dict( + some="label", + numeric=3, + ), + ) + + assert arguments.get_mutation_payload(params) == dict( + metadata=dict( + name="name", + labels=dict( + some="label", + numeric="3", + ), + ), + ) + + def test_annotations(self): + params = dict( + name="name", + annotations=dict( + my="Annotation", + number=45, + ), + ) + + assert arguments.get_mutation_payload(params) == dict( + metadata=dict( + name="name", + annotations=dict( + my="Annotation", + number="45", + ), + ), + ) diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_bonsai.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_bonsai.py new file mode 100644 index 00000000..cf0d8a49 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_bonsai.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + bonsai, errors, http, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestGet: + def test_url_construction(self, mocker): + http_mock = mocker.patch.object(bonsai, "http") + http_mock.request.return_value = http.Response(200, "{}") + + bonsai.get("path") + + assert http_mock.request.call_args[0] == ( + "GET", "https://bonsai.sensu.io/api/v1/assets/path", + ) + + def test_bad_status(self, mocker): + http_mock = mocker.patch.object(bonsai, "http") + http_mock.request.return_value = http.Response(400, "{}") + + with pytest.raises(errors.BonsaiError, match="400"): + bonsai.get("path") + + def test_invalid_json(self, mocker): + http_mock = mocker.patch.object(bonsai, "http") + http_mock.request.return_value = http.Response(200, "{ a }") + + with pytest.raises(errors.BonsaiError, match="JSON"): + bonsai.get("path") + + +class TestGetAvailableAssetVersions: + def test_valid_data(self, mocker): + get = mocker.patch.object(bonsai, "get") + get.return_value = dict( + versions=[ + dict(version="1.2.3", assets=[]), + dict(version="1.2.4", assets=[]), + dict(version="1.2.5", assets=[]), + ], + ) + + result = bonsai.get_available_asset_versions("namespace", "name") + + assert set(("1.2.3", "1.2.4", "1.2.5")) == result + assert get.call_args[0] == ("namespace/name",) + + @pytest.mark.parametrize("data", [ + "invalid", + dict(invalid="toplevel"), + dict(versions="oh-no"), + dict(versions=["not", "ok"]), + dict(versions=[dict(invalid="internal")]), + ]) + def test_invalid_data(self, mocker, data): + get = mocker.patch.object(bonsai, "get") + get.return_value = data + + with pytest.raises(errors.BonsaiError, match="versions"): + bonsai.get_available_asset_versions("namespace", "name") + + +class TestGetAssetVersionBuilds: + def test_url_construction(self, mocker): + get = mocker.patch.object(bonsai, "get") + get.return_value = dict(spec=dict(builds=[])) + + bonsai.get_asset_version_builds("x", "y", "z") + + assert get.call_args[0] == ("x/y/z/release_asset_builds",) + + @pytest.mark.parametrize("data", [ + "invalid", + dict(missing="spec"), + dict(spec="invalid"), + dict(spec=dict(missing="builds")), + ]) + def test_invalid_data(self, mocker, data): + get = mocker.patch.object(bonsai, "get") + get.return_value = data + + with pytest.raises(errors.BonsaiError, match="spec"): + bonsai.get_asset_version_builds("x", "y", "z") + + +class TestGetAssetParameters: + def test_valid_all_data(self, mocker): + versions = mocker.patch.object(bonsai, "get_available_asset_versions") + versions.return_value = set(("t", "u", "v")) + builds = mocker.patch.object(bonsai, "get_asset_version_builds") + builds.return_value = dict( + metadata=dict( + annotations=dict(annotation="value"), + labels=dict(label="value"), + ), + spec=dict(builds=[1, 2, 3]), + ) + + result = bonsai.get_asset_parameters("x/y", "v") + + assert result == dict( + labels=dict(label="value"), + annotations=dict(annotation="value"), + builds=[1, 2, 3], + ) + assert versions.call_args[0] == ("x", "y") + assert builds.call_args[0] == ("x", "y", "v") + + def test_valid_minimal_data(self, mocker): + versions = mocker.patch.object(bonsai, "get_available_asset_versions") + versions.return_value = set(("t", "u", "v")) + builds = mocker.patch.object(bonsai, "get_asset_version_builds") + builds.return_value = dict( + spec=dict(builds=[1, 2, 3]), + ) + + result = bonsai.get_asset_parameters("x/y", "v") + + assert result == dict( + labels=None, + annotations=None, + builds=[1, 2, 3], + ) + assert versions.call_args[0] == ("x", "y") + assert builds.call_args[0] == ("x", "y", "v") + + def test_invalid_name(self, mocker): + with pytest.raises(errors.BonsaiError, match="names"): + bonsai.get_asset_parameters("x.y", "v") + + def test_invalid_version(self, mocker): + versions = mocker.patch.object(bonsai, "get_available_asset_versions") + versions.return_value = set(("t", "u")) + + with pytest.raises(errors.BonsaiError, match="Version"): + bonsai.get_asset_parameters("x/y", "v") diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_client.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_client.py new file mode 100644 index 00000000..ecc6fc54 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_client.py @@ -0,0 +1,295 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + client, errors, http +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestAuthHeader: + def test_using_valid_token(self, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(200, '{"access_token": "token"}') + + c = client.Client( + "http://example.com/", "user", "pass", None, True, None, + ) + + assert dict(Authorization="Bearer token") == c.auth_header + assert 1 == request.call_count + assert ("GET", "http://example.com/auth") == request.call_args[0] + assert "user" == request.call_args[1]["url_username"] + assert "pass" == request.call_args[1]["url_password"] + + def test_cache_auth_headers_with_token(self, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(200, '{"access_token": "token"}') + + c = client.Client( + "http://example.com/", "user", "pass", None, True, None, + ) + for i in range(5): + c.auth_header + + assert 1 == request.call_count + + def test_login_failure_token_bad_status(self, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(500, '{"access_token": "token"}') + + with pytest.raises(errors.SensuError, match="500"): + client.Client( + "http://example.com/", "user", "pass", None, True, None, + ).auth_header + + def test_login_failure_token_bad_json(self, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(200, "{ not a json }") + + with pytest.raises(errors.SensuError, match="JSON"): + client.Client( + "http://example.com/", "user", "pass", None, True, None, + ).auth_header + + def test_login_failure_token_missing_token(self, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(200, '{"access_bla": "token"}') + + with pytest.raises(errors.SensuError, match="token"): + client.Client( + "http://example.com/", "user", "pass", None, True, None, + ).auth_header + + +class TestVersion: + def test_valid_version(self, mocker): + c = client.Client("http://example.com/", "u", "p", None, True, None) + mocker.patch.object(c, "get").return_value = http.Response( + 200, '{"sensu_backend":"5.21.0#sha-here"}', + ) + + assert c.version == "5.21.0" + + def test_valid_version_is_cached(self, mocker): + c = client.Client("http://example.com/", "u", "p", None, True, None) + get = mocker.patch.object(c, "get") + get.return_value = http.Response( + 200, '{"sensu_backend":"5.21.0#sha-here"}', + ) + + for i in range(4): + c.version + + get.assert_called_once() + + def test_non_200_response(self, mocker): + c = client.Client("http://example.com/", "u", "p", None, True, None) + mocker.patch.object(c, "get").return_value = http.Response( + 400, '{"sensu_backend":"5.21.0#sha-here"}', + ) + + with pytest.raises(errors.SensuError, match="400"): + c.version + + def test_bad_json_response(self, mocker): + c = client.Client("http://example.com/", "u", "p", None, True, None) + mocker.patch.object(c, "get").return_value = http.Response( + 200, '"sensu_backend', + ) + + with pytest.raises(errors.SensuError, match="JSON"): + c.version + + def test_missing_backend_version_in_response(self, mocker): + c = client.Client("http://example.com/", "u", "p", None, True, None) + mocker.patch.object(c, "get").return_value = http.Response(200, '{}') + + with pytest.raises(errors.SensuError, match="backend"): + c.version + + def test_invalid_version(self, mocker): + c = client.Client("http://example.com/", "u", "p", None, True, None) + mocker.patch.object(c, "get").return_value = http.Response( + 200, '{"sensu_backend":"devel"}', + ) + + assert c.version == c.BAD_VERSION + + +class TestRequest: + def test_request_payload_token(self, mocker): + request = mocker.patch.object(http, "request") + request.side_effect = ( + http.Response(200, '{"access_token": "token"}'), + http.Response(200, "data"), + ) + + client.Client( + "http://example.com/", "user", "pass", None, True, None, + ).request("PUT", "/path", dict(some="payload")) + + request.assert_called_with( + "PUT", "http://example.com/path", + payload=dict(some="payload"), + headers=dict(Authorization="Bearer token"), + validate_certs=True, + ca_path=None, + ) + + def test_request_payload_api_key(self, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(200, "data") + + client.Client( + "http://example.com/", None, None, "key", False, None, + ).request("PUT", "/path", dict(some="payload")) + + request.assert_called_once_with( + "PUT", "http://example.com/path", + payload=dict(some="payload"), + headers=dict(Authorization="Key key"), + validate_certs=False, + ca_path=None, + ) + + def test_request_no_payload_token(self, mocker): + request = mocker.patch.object(http, "request") + request.side_effect = ( + http.Response(200, '{"access_token": "token"}'), + http.Response(200, "data"), + ) + + client.Client( + "http://example.com/", "user", "pass", None, True, "/ca", + ).request("PUT", "/path") + + request.assert_called_with( + "PUT", "http://example.com/path", payload=None, + headers=dict(Authorization="Bearer token"), + validate_certs=True, + ca_path="/ca", + ) + + def test_request_no_payload_api_key(self, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(200, "data") + + client.Client( + "http://example.com/", "u", "p", "key", False, "/ca", + ).request("PUT", "/path") + + request.assert_called_once_with( + "PUT", "http://example.com/path", payload=None, + headers=dict(Authorization="Key key"), + validate_certs=False, + ca_path="/ca", + ) + + @pytest.mark.parametrize("status", [401, 403]) + def test_request_bad_credentials(self, status, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(status, "data") + + with pytest.raises(errors.SensuError, match="credentials"): + client.Client( + "http://example.com/", None, None, "key", True, None, + ).request("PUT", "/path", dict(some="payload")) + + request.assert_called_once_with( + "PUT", "http://example.com/path", + payload=dict(some="payload"), + headers=dict(Authorization="Key key"), + validate_certs=True, + ca_path=None, + ) + + +class TestGet: + def test_get(self, mocker): + c = client.Client( + "http://example.com/", "user", "pass", None, True, None, + ) + c.request = mocker.Mock() + + c.get("/path") + + c.request.assert_called_with("GET", "/path") + + +class TestPut: + def test_put(self, mocker): + c = client.Client( + "http://example.com/", "user", "pass", None, True, None, + ) + c.request = mocker.Mock() + + c.put("/path", {}) + + c.request.assert_called_with("PUT", "/path", {}) + + +class TestDelete: + def test_delete(self, mocker): + c = client.Client( + "http://example.com/", "user", "pass", None, True, None, + ) + c.request = mocker.Mock() + + c.delete("/path") + + c.request.assert_called_with("DELETE", "/path") + + +class TestValidateAuthData: + def test_valid_creds(self, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(200, None) + c = client.Client( + "http://example.com/", "user", "pass", None, True, None, + ) + + result = c.validate_auth_data("check_user", "check_pass") + + assert result + assert 1 == request.call_count + assert ("GET", "http://example.com/auth/test") == request.call_args[0] + assert "check_user" == request.call_args[1]["url_username"] + assert "check_pass" == request.call_args[1]["url_password"] + + def test_invalid_creds(self, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(401, None) + c = client.Client( + "http://example.com/", "user", "pass", None, True, None, + ) + + result = c.validate_auth_data("check_user", "check_pass") + + assert not result + assert 1 == request.call_count + assert ("GET", "http://example.com/auth/test") == request.call_args[0] + assert "check_user" == request.call_args[1]["url_username"] + assert "check_pass" == request.call_args[1]["url_password"] + + def test_broken_backend(self, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(500, None) + c = client.Client( + "http://example.com/", "user", "pass", None, True, None, + ) + + with pytest.raises(errors.SensuError, match="500"): + c.validate_auth_data("check_user", "check_pass") diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_http.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_http.py new file mode 100644 index 00000000..84fddb53 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_http.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +import ssl +import sys + +import pytest + +from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, http, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestResponse: + def test_with_valid_json(self): + resp = http.Response(201, '{"some": ["json", "data", 3]}') + + assert 201 == resp.status + assert '{"some": ["json", "data", 3]}' == resp.data + assert {"some": ["json", "data", 3]} == resp.json + + def test_with_invalid_json(self): + resp = http.Response(404, "") + + assert 404 == resp.status + assert "" == resp.data + assert resp.json is None + + +class TestRequest: + def test_ok_request(self, mocker): + data_resp = mocker.Mock() + data_resp.read.return_value = "data" + data_resp.getcode.return_value = 200 + open_url = mocker.patch.object(http, "open_url") + open_url.return_value = data_resp + + resp = http.request("GET", "example.com/path") + + assert 200 == resp.status + assert "data" == resp.data + assert "GET" == open_url.call_args[1]["method"] + assert "example.com/path" == open_url.call_args[1]["url"] + + def test_non_20x_status(self, mocker): + open_url = mocker.patch.object(http, "open_url") + open_url.side_effect = HTTPError( + "url", 404, "missing", {}, None, + ) + + resp = http.request("GET", "example.com/bad") + + assert 404 == resp.status + assert "missing" == resp.data + assert "GET" == open_url.call_args[1]["method"] + assert "example.com/bad" == open_url.call_args[1]["url"] + + def test_url_error(self, mocker): + open_url = mocker.patch.object(http, "open_url") + open_url.side_effect = URLError("Invalid") + + with pytest.raises(errors.HttpError): + http.request("GET", "example.com/bad") + + def test_payload_no_headers(self, mocker): + data_resp = mocker.Mock() + data_resp.read.return_value = "data" + data_resp.getcode.return_value = 200 + open_url = mocker.patch.object(http, "open_url") + open_url.return_value = data_resp + + http.request("PUT", "example.com/path", payload=dict(a=2)) + + assert "PUT" == open_url.call_args[1]["method"] + assert "example.com/path" == open_url.call_args[1]["url"] + assert '{"a":2}' == open_url.call_args[1]["data"] + headers = open_url.call_args[1]["headers"] + assert {"content-type": "application/json"} == headers + + def test_payload_with_headers(self, mocker): + data_resp = mocker.Mock() + data_resp.read.return_value = "data" + data_resp.getcode.return_value = 200 + open_url = mocker.patch.object(http, "open_url") + open_url.return_value = data_resp + + http.request( + "PUT", "example.com/path", payload=dict(b=4), headers=dict(h="v"), + ) + + assert "PUT" == open_url.call_args[1]["method"] + assert "example.com/path" == open_url.call_args[1]["url"] + assert '{"b":4}' == open_url.call_args[1]["data"] + headers = open_url.call_args[1]["headers"] + assert {"content-type": "application/json", "h": "v"} == headers + + def test_payload_overrides_data(self, mocker): + data_resp = mocker.Mock() + data_resp.read.return_value = "data" + data_resp.getcode.return_value = 200 + open_url = mocker.patch.object(http, "open_url") + open_url.return_value = data_resp + + http.request( + "PUT", "example.com/path", payload=dict(a=2), data="data", + ) + + assert "PUT" == open_url.call_args[1]["method"] + assert "example.com/path" == open_url.call_args[1]["url"] + assert '{"a":2}' == open_url.call_args[1]["data"] + headers = open_url.call_args[1]["headers"] + assert {"content-type": "application/json"} == headers + + def test_data(self, mocker): + data_resp = mocker.Mock() + data_resp.read.return_value = "data" + data_resp.getcode.return_value = 200 + open_url = mocker.patch.object(http, "open_url") + open_url.return_value = data_resp + + http.request("PUT", "example.com/path", data="data") + + assert "PUT" == open_url.call_args[1]["method"] + assert "example.com/path" == open_url.call_args[1]["url"] + assert "data" == open_url.call_args[1]["data"] + assert open_url.call_args[1]["headers"] is None + + def test_kwargs(self, mocker): + data_resp = mocker.Mock() + data_resp.read.return_value = "data" + data_resp.getcode.return_value = 200 + open_url = mocker.patch.object(http, "open_url") + open_url.return_value = data_resp + + http.request("PUT", "example.com/path", a=3, b="f") + + assert "PUT" == open_url.call_args[1]["method"] + assert "example.com/path" == open_url.call_args[1]["url"] + assert 3 == open_url.call_args[1]["a"] + assert "f" == open_url.call_args[1]["b"] + + def test_cert_error_ssl_module_present(self, mocker): + open_url = mocker.patch.object(http, "open_url") + open_url.side_effect = ssl.CertificateError("Invalid") + + with pytest.raises(errors.HttpError): + http.request("GET", "example.com/bad") + + def test_cert_error_ssl_module_absent(self, mocker): + class Dummy(Exception): + pass + + open_url = mocker.patch.object(http, "open_url") + open_url.side_effect = ssl.CertificateError("Invalid") + mocker.patch.object(http, "CertificateError", Dummy) + + with pytest.raises(ssl.CertificateError): + http.request("GET", "example.com/bad") diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_role_utils.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_role_utils.py new file mode 100644 index 00000000..e4f6ff57 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_role_utils.py @@ -0,0 +1,285 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import role_utils + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestDoSubjectsDiffer: + def test_different_lengths(self): + assert role_utils._do_subjects_differ( + [{"type": "a", "name": "a"}], + [{"type": "a", "name": "a"}, {"type": "a", "name": "b"}] + ) is True + + def test_different_type_with_same_name(self): + assert role_utils._do_subjects_differ( + [{"type": "a", "name": "same"}], + [{"type": "b", "name": "same"}] + ) is True + + def test_equal_with_different_order_within_type(self): + assert role_utils._do_subjects_differ( + [{"type": "a", "name": "a2"}, {"type": "a", "name": "a1"}], + [{"type": "a", "name": "a1"}, {"type": "a", "name": "a2"}] + ) is False + + def test_equal_with_different_order_multiple_types(self): + assert role_utils._do_subjects_differ( + [ + {"type": "a", "name": "a2"}, + {"type": "b", "name": "b1"}, + {"type": "a", "name": "a1"} + ], + [ + {"type": "a", "name": "a1"}, + {"type": "a", "name": "a2"}, + {"type": "b", "name": "b1"} + ] + ) is False + + def test_different(self): + assert role_utils._do_subjects_differ( + [ + {"type": "a", "name": "a2"}, + {"type": "b", "name": "b3"}, + ], + [ + {"type": "c", "name": "c2"}, + {"type": "a", "name": "s2"}, + ] + ) is True + + +class TestDoRoleBindingsDiffer: + def test_equal_role_binding(self): + current = { + 'role_ref': 'a', + 'subjects': [ + {"type": "User", "name": "b"}, + {"type": "Group", "name": "g"}, + ] + } + desired = { + 'role_ref': 'a', + 'subjects': [ + {"type": "User", "name": "b"}, + {"type": "Group", "name": "g"}, + ] + } + assert role_utils.do_role_bindings_differ(current, desired) is False + + def test_equal_role_binding_mixed_users_and_groups(self): + current = { + 'role_ref': 'a', + 'subjects': [ + {"type": "Group", "name": "g1"}, + {"type": "User", "name": "u1"}, + {"type": "Group", "name": "g2"}, + {"type": "User", "name": "u2"}, + ] + } + desired = { + 'role_ref': 'a', + 'subjects': [ + {"type": "User", "name": "u2"}, + {"type": "User", "name": "u1"}, + {"type": "Group", "name": "g2"}, + {"type": "Group", "name": "g1"}, + ] + } + assert role_utils.do_role_bindings_differ(current, desired) is False + + def test_updated_role_binding_subjects(self): + current = { + 'role_ref': 'a', + 'subjects': [ + {"type": "User", "name": "b"}, + ] + } + desired = { + 'role_ref': 'a', + 'subjects': [ + {"type": "User", "name": "b"}, + {"type": "Group", "name": "g"}, + ] + } + assert role_utils.do_role_bindings_differ(current, desired) is True + + +class TestRuleSets: + def test_all_keys_none(self): + assert role_utils._rule_set([{}]) == set( + ((frozenset(), frozenset(), frozenset()),) + ) + + def test_rules_multiple(self): + assert role_utils._rule_set([{ + 'verbs': ['list', 'get'], + 'resources': ['entities', 'checks'], + 'resource_names': None + }, { + 'verbs': ['list', 'delete'], + 'resources': ['entities', 'checks'], + 'resource_names': None + }]) == set(( + (frozenset(['delete', 'list']), frozenset(['checks', 'entities']), frozenset()), + (frozenset(['get', 'list']), frozenset(['checks', 'entities']), frozenset()) + )) + + def test_missing_key(self): + assert role_utils._rule_set([{ + 'verbs': ['list', 'get'], + 'resources': ['entities', 'checks'], + }]) == set( + ((frozenset(['get', 'list']), frozenset(['checks', 'entities']), frozenset()),) + ) + + +class TestDoRulesDiffer: + def test_empty_values(self): + assert role_utils._do_rules_differ( + [{'verbs': []}], + [{'verbs': []}] + ) is False + + def test_rules_when_current_values_are_none(self): + assert role_utils._do_rules_differ( + [{'verbs': None}], + [{'verbs': ['get', 'list']}] + ) is True + + def test_rules_when_desired_values_are_none(self): + assert role_utils._do_rules_differ( + [{'verbs': ['get', 'list']}], + [{'verbs': None}] + ) is True + + def test_rules_are_different(self): + assert role_utils._do_rules_differ( + [{'verbs': ['list', 'get']}], + [{'verbs': ['get', 'delete']}] + ) is True + + def test_rules_with_additional_keys_in_current(self): + assert role_utils._do_rules_differ( + [{'verbs': ['list', 'get'], 'resources': ['checks', 'entities']}], + [{'verbs': ['get', 'list']}] + ) is True + + def test_rules_are_the_same(self): + assert role_utils._do_rules_differ( + [{'verbs': ['list', 'get']}], + [{'verbs': ['get', 'list']}] + ) is False + + +class TestDoRolesDiffer: + def test_rules_when_values_in_current_are_none(self): + current = { + 'rules': [{ + 'resource_names': None + }] + } + desired = { + 'rules': [{ + 'resource_names': ['check-cpu'] + }] + } + assert role_utils.do_roles_differ(current, desired) is True + + def test_rules_when_values_in_desired_are_none(self): + current = { + 'rules': [{ + 'resource_names': ['check-cpu'] + }] + } + desired = { + 'rules': [{ + 'resource_names': None + }] + } + assert role_utils.do_roles_differ(current, desired) is True + + def test_different_rules_order(self): + current = { + 'rules': [{ + 'verbs': ['get', 'list'], + 'resources': ['entities', 'checks'] + }, { + 'verbs': ['create', 'delete', 'update'], + 'resources': ['assets', 'hooks'] + }] + } + desired = { + 'rules': [{ + 'verbs': ['delete', 'create', 'update'], + 'resources': ['hooks', 'assets'] + }, { + 'verbs': ['list', 'get'], + 'resources': ['checks', 'entities'] + }] + } + assert role_utils.do_roles_differ(current, desired) is False + + def test_key_missing_in_current(self): + current = { + 'rules': [{ + 'verbs': ['update', 'create'], + 'resources': ['hooks', 'assets'] + }] + } + desired = { + 'rules': [{ + 'verbs': ['create', 'update'], + 'resources': ['hooks', 'assets'], + 'resource_names': ['check-cpu'] + }] + } + assert role_utils.do_roles_differ(current, desired) is True + + def test_key_missing_in_desired(self): + current = { + 'rules': [{ + 'verbs': ['update', 'create'], + 'resources': ['hooks', 'assets'], + 'resource_names': ['check-cpu'] + }] + } + desired = { + 'rules': [{ + 'verbs': ['create', 'update'], + 'resources': ['hooks', 'assets'] + }] + } + assert role_utils.do_roles_differ(current, desired) is True + + def test_role_exists_but_with_additional_rules(self): + current = { + 'rules': [{ + 'verbs': ['get', 'list'], + 'resources': ['entities', 'check'] + }, { + 'verbs': ['create', 'update', 'delete'], + 'resources': ['assets', 'hooks'] + }] + } + desired = { + 'rules': [{ + 'verbs': ['list', 'get'], + 'resources': ['check', 'entities'] + }] + } + assert role_utils.do_roles_differ(current, desired) is True diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_utils.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_utils.py new file mode 100644 index 00000000..f737dce0 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_utils.py @@ -0,0 +1,491 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, http, utils, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSync: + def test_absent_no_current_object(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(404, "") + + changed, object = utils.sync("absent", client, "/path", {}, False) + + assert changed is False + assert object is None + + def test_absent_no_current_object_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(404, "") + + changed, object = utils.sync("absent", client, "/path", {}, True) + + assert changed is False + assert object is None + + def test_absent_current_object_present(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{}') + client.delete.return_value = http.Response(204, "") + + changed, object = utils.sync("absent", client, "/path", {}, False) + + assert changed is True + assert object is None + client.delete.assert_called_with("/path") + + def test_absent_current_object_present_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{}') + client.delete.return_value = http.Response(204, "") + + changed, object = utils.sync("absent", client, "/path", {}, True) + + assert changed is True + assert object is None + client.delete.assert_not_called() + + def test_present_no_current_object(self, mocker): + client = mocker.Mock() + client.get.side_effect = ( + http.Response(404, ""), + http.Response(200, '{"new": "data"}'), + ) + client.put.return_value = http.Response(201, "") + + changed, object = utils.sync( + "present", client, "/path", {"my": "data"}, False, + ) + + assert changed is True + assert {"new": "data"} == object + client.put.assert_called_once_with("/path", {"my": "data"}) + + def test_present_no_current_object_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(404, "") + + changed, object = utils.sync( + "present", client, "/path", {"my": "data"}, True, + ) + + assert changed is True + assert {"my": "data"} == object + client.put.assert_not_called() + + def test_present_current_object_differ(self, mocker): + client = mocker.Mock() + client.get.side_effect = ( + http.Response(200, '{"current": "data"}'), + http.Response(200, '{"new": "data"}'), + ) + client.put.return_value = http.Response(201, "") + + changed, object = utils.sync( + "present", client, "/path", {"my": "data"}, False, + ) + + assert changed is True + assert {"new": "data"} == object + client.put.assert_called_once_with("/path", {"my": "data"}) + + def test_present_current_object_differ_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"current": "data"}') + + changed, object = utils.sync( + "present", client, "/path", {"my": "data"}, True, + ) + + assert changed is True + assert {"my": "data"} == object + client.put.assert_not_called() + + def test_present_current_object_does_not_differ(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"my": "data"}') + + changed, object = utils.sync( + "present", client, "/path", {"my": "data"}, False, + ) + + assert changed is False + assert {"my": "data"} == object + client.put.assert_not_called() + + def test_present_current_object_does_not_differ_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"my": "data"}') + + changed, object = utils.sync( + "present", client, "/path", {"my": "data"}, True, + ) + + assert changed is False + assert {"my": "data"} == object + client.put.assert_not_called() + + +class TestSyncV1: + def test_parameter_passthrough(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.return_value = (True, { + "metadata": {"name": "test", "namespace": "space"}, + "spec": {"key": "value"}, + }) + + changed, object = utils.sync_v1("absent", "c", "/path", {}, False) + + assert changed is True + assert { + "metadata": {"name": "test", "namespace": "space"}, + "key": "value", + } + + +class TestDoDiffer: + def test_extra_keys_in_current_do_not_matter(self): + assert utils.do_differ({"a": "b", "c": 3}, {"a": "b"}) is False + + def test_detect_different_values(self): + assert utils.do_differ({"a": "b"}, {"a": "c"}) is True + + def test_detect_missing_keys_in_current(self): + assert utils.do_differ({"a": "b"}, {"c": "d"}) is True + + def test_desired_none_values_are_ignored(self): + assert utils.do_differ({"a": "b"}, {"c": None}) is False + + def test_metadata_ignores_created_by(self): + assert utils.do_differ( + dict(metadata=dict(a=1, created_by=2)), + dict(metadata=dict(a=1)), + ) is False + + def test_metadata_detects_change(self): + assert utils.do_differ( + dict(metadata=dict(a=1)), dict(metadata=dict(a=2)), + ) is True + + def test_metadata_detects_change_in_presence_of_created_by(self): + assert utils.do_differ( + dict(metadata=dict(a=1, created_by=2)), + dict(metadata=dict(a=2)), + ) is True + + def test_ignore_keys_do_not_affect_the_outcome(self): + assert utils.do_differ(dict(a=1), dict(a=2), "a") is False + + def test_ignore_keys_do_not_mask_other_differences(self): + assert utils.do_differ(dict(a=1, b=1), dict(a=2, b=2), "a") is True + + +class TestDoDifferV1: + def test_extra_keys_in_current_do_not_matter(self): + assert utils.do_differ_v1( + {"spec": {"a": "b", "c": 3}}, {"spec": {"a": "b"}}, + ) is False + + def test_detect_different_values(self): + assert utils.do_differ_v1( + {"spec": {"a": "b"}}, {"spec": {"a": "c"}}, + ) is True + + def test_detect_missing_keys_in_current(self): + assert utils.do_differ_v1( + {"spec": {"a": "b"}}, {"spec": {"c": "d"}}, + ) is True + + def test_desired_none_values_are_ignored(self): + assert utils.do_differ_v1( + {"spec": {"a": "b"}}, {"spec": {"c": None}}, + ) is False + + def test_metadata_ignores_created_by(self): + assert utils.do_differ_v1( + {"metadata": {"a": 1, "created_by": 2}}, + {"metadata": {"a": 1}}, + ) is False + + def test_metadata_detects_change(self): + assert utils.do_differ_v1( + {"metadata": {"a": 1}}, {"metadata": {"a": 2}}, + ) is True + + def test_metadata_detects_change_in_presence_of_created_by(self): + assert utils.do_differ_v1( + {"metadata": {"a": 1, "created_by": 2}}, + {"metadata": {"a": 2}}, + ) is True + + def test_ignore_keys_do_not_affect_the_outcome(self): + assert utils.do_differ_v1( + {"spec": {"a": 1}}, {"spec": {"a": 2}}, "a", + ) is False + + def test_ignore_keys_do_not_mask_other_differences(self): + assert utils.do_differ_v1( + {"spec": {"a": 1, "b": 1}}, {"spec": {"a": 2, "b": 2}}, "a", + ) is True + + +class TestGet: + @pytest.mark.parametrize( + "status", [100, 201, 202, 203, 204, 400, 401, 403, 500, 501], + ) + def test_abort_on_invalid_status(self, mocker, status): + client = mocker.Mock() + client.get.return_value = http.Response(status, "") + + with pytest.raises(errors.SyncError, match=str(status)): + utils.get(client, "/get") + client.get.assert_called_once_with("/get") + + def test_abort_on_invalid_json(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, "") + + with pytest.raises(errors.SyncError, match="JSON"): + utils.get(client, "/get") + client.get.assert_called_once_with("/get") + + def test_ignore_invalid_json_on_404(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(404, "") + + object = utils.get(client, "/get") + + assert object is None + client.get.assert_called_once_with("/get") + + def test_valid_json(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"get": "data"}') + + object = utils.get(client, "/get") + + assert {"get": "data"} == object + client.get.assert_called_once_with("/get") + + +class TestDelete: + @pytest.mark.parametrize( + "status", [100, 200, 201, 202, 203, 400, 401, 403, 500, 501], + ) + def test_abort_on_invalid_status(self, mocker, status): + client = mocker.Mock() + client.delete.return_value = http.Response(status, "") + + with pytest.raises(errors.SyncError, match=str(status)): + utils.delete(client, "/delete") + client.delete.assert_called_once_with("/delete") + + def test_valid_delete(self, mocker): + client = mocker.Mock() + client.delete.return_value = http.Response(204, "{}") + + object = utils.delete(client, "/delete") + + assert object is None + client.delete.assert_called_once_with("/delete") + + +class TestPut: + @pytest.mark.parametrize( + "status", [100, 202, 203, 204, 400, 401, 403, 500, 501], + ) + def test_abort_on_invalid_status(self, mocker, status): + client = mocker.Mock() + client.put.return_value = http.Response(status, "") + + with pytest.raises(errors.SyncError, match=str(status)): + utils.put(client, "/put", {"payload": "data"}) + client.put.assert_called_once_with("/put", {"payload": "data"}) + + @pytest.mark.parametrize("status", [200, 201]) + def test_valid_put(self, mocker, status): + client = mocker.Mock() + client.put.return_value = http.Response(status, '{"put": "data"}') + + object = utils.put(client, "/put", {"payload": "data"}) + + assert object is None + client.put.assert_called_once_with("/put", {"payload": "data"}) + + +class TestDictToSingleItemDicts: + def test_conversion(self): + result = utils.dict_to_single_item_dicts({"a": 0, 1: "b"}) + + assert 2 == len(result) + for item in ({"a": 0}, {1: "b"}): + assert item in result + + +class TestSingleItemDictsToDict: + def test_conversion(self): + assert dict(a=3, b=4, c=5) == utils.single_item_dicts_to_dict( + [dict(a=3), dict(b=4), dict(c=5)] + ) + + +class TestDictToKeyValueString: + def test_conversion(self): + result = utils.dict_to_key_value_strings({"a": 0, 1: "b"}) + + assert set(("a=0", "1=b")) == set(result) + + +class TestBuildUrlPath: + @pytest.mark.parametrize("parts,expectation", [ + ((), "/"), + ((None, None), "/"), + ((None, "a", "b", None, None, "c"), "/a/b/c"), + (("get/rid of+stuff",), "/get%2Frid%20of%2Bstuff"), + (("/", " ", "a"), "/%2F/%20/a"), + ]) + def test_build_url_path_no_namespace(self, parts, expectation): + path = "/api/enterprise/store/v1" + expectation + assert path == utils.build_url_path( + "enterprise/store", "v1", None, *parts + ) + + @pytest.mark.parametrize("parts,expectation", [ + ((), "/"), + ((None, None), "/"), + ((None, "a", "b", None, None, "c"), "/a/b/c"), + (("get/rid of+stuff",), "/get%2Frid%20of%2Bstuff"), + (("/", " ", "a"), "/%2F/%20/a"), + ]) + def test_build_url_path_with_namespace(self, parts, expectation): + path = "/api/core/v2/namespaces/default" + expectation + assert path == utils.build_url_path( + "core", "v2", "default", *parts + ) + + +class TestBuildCoreV2Path: + def test_build_path_no_namespace(self): + assert utils.build_core_v2_path(None, "a").startswith( + "/api/core/v2/", + ) + + def test_build_url_with_namespace(self): + assert utils.build_core_v2_path("default", "a").startswith( + "/api/core/v2/namespaces/default/", + ) + + +class TestPrepareResultList: + @pytest.mark.parametrize("input,output", [ + (None, []), # this is mosti likely result of a 404 status + ("a", ["a"]), + ([], []), + ([1, 2, 3], [1, 2, 3]), + ([None], [None]), # we leave lists intact, even if they contain None + ]) + def test_list_construction(self, input, output): + assert output == utils.prepare_result_list(input) + + +class TestConvertV1ToV2Response: + def test_none_passes_through(self): + assert utils.convert_v1_to_v2_response(None) is None + + def test_spec_only_if_metadata_is_missing(self): + assert utils.convert_v1_to_v2_response(dict( + spec=dict(a=1, b=2), + )) == dict(a=1, b=2) + + def test_add_metadata_from_toplevel(self): + assert utils.convert_v1_to_v2_response(dict( + metadata=dict(name="sample"), + spec=dict(a=1, b=2), + )) == dict(metadata=dict(name="sample"), a=1, b=2) + + +class TestDoSecretsDiffer: + @pytest.mark.parametrize("current,desired", [ + ( # All empty + [], [], + ), + ( # All is equal + [dict(name="a", secret="1"), dict(name="b", secret="2")], + [dict(name="a", secret="1"), dict(name="b", secret="2")], + ), + ( # Different order + [dict(name="a", secret="1"), dict(name="b", secret="2")], + [dict(name="b", secret="2"), dict(name="a", secret="1")], + ), + ]) + def test_no_difference(self, current, desired): + assert utils.do_secrets_differ( + dict(secrets=current), dict(secrets=desired), + ) is False + + @pytest.mark.parametrize("current,desired", [ + ( # Different source for variable b + [dict(name="b", secret="2")], [dict(name="b", secret="3")], + ), + ( # Different name + [dict(name="a", secret="1")], [dict(name="b", secret="1")], + ), + ( # Different number of secrets + [dict(name="a", secret="1"), dict(name="b", secret="2")], + [dict(name="a", secret="1")], + ), + ]) + def test_difference(self, current, desired): + assert utils.do_secrets_differ( + dict(secrets=current), dict(secrets=desired), + ) is True + + @pytest.mark.parametrize("secrets,diff", [ + # Missing secrets and empty list are the same + ([], False), + # None secrets are treated as empy list of secrets + (None, False), + # If anything is set, we have difference + ([dict(name="n", secret="s")], True), + ]) + def test_missing_secrets(self, secrets, diff): + assert utils.do_secrets_differ(dict(), dict(secrets=secrets)) is diff + assert utils.do_secrets_differ(dict(secrets=secrets), dict()) is diff + + +class TestDeprecate: + def test_ansible_lt_2_9_10(self, mocker): + module = mocker.MagicMock() + module.deprecate.side_effect = ( + TypeError("Simulating Ansible 2.9.9 and older"), + None, # Success, since no exception is raised + ) + + utils.deprecate(module, "Test msg", "3.2.1") + + assert module.deprecate.call_count == 2 + assert module.deprecate.called_once_with("Test msg", version="3.2.1") + + def test_ansible_ge_2_9_10(self, mocker): + module = mocker.MagicMock() + + utils.deprecate(module, "Test msg", "3.2.1") + + assert module.deprecate.called_once_with( + "Test msg", version="3.2.1", collection_name="sensu.sensu_go", + ) diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/common/utils.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/common/utils.py new file mode 100644 index 00000000..2287dbc9 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/common/utils.py @@ -0,0 +1,53 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import json + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes + +from mock import patch + + +def set_module_args(**args): + if '_ansible_remote_tmp' not in args: + args['_ansible_remote_tmp'] = '/tmp' + if '_ansible_keep_remote_files' not in args: + args['_ansible_keep_remote_files'] = False + + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) + + +class AnsibleExitJson(Exception): + pass + + +class AnsibleFailJson(Exception): + pass + + +def exit_json(*args, **kwargs): + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class ModuleTestCase: + def setup_method(self): + self.mock_module = patch.multiple( + basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json, + ) + self.mock_module.start() + + def teardown_method(self): + self.mock_module.stop() + + +def generate_name(test_case): + return test_case['name'] diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_ad_auth_provider.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_ad_auth_provider.py new file mode 100644 index 00000000..a7204bb5 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_ad_auth_provider.py @@ -0,0 +1,332 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, + utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import ad_auth_provider + +from .common.utils import ( + AnsibleExitJson, + AnsibleFailJson, + ModuleTestCase, + set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestDoDiffer: + def test_no_changes(self): + desired = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ), + metadata=dict(name="activedirectory"), + ) + current = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ), + metadata=dict( + name="activedirectory", + created_by="me", + ), + ) + + assert ad_auth_provider.do_differ(current, desired) is False + + def test_changes_are_detected(self): + desired = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + port=636, + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ), + metadata=dict(name="activedirectory"), + ) + current = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ), + metadata=dict( + name="activedirectory", + created_by="me", + ), + ) + assert ad_auth_provider.do_differ(current, desired) is True + + def test_changes_are_detected_diff_servers_len(self): + desired = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ), + dict( + host="127.0.0.2", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ), + ], + ), + metadata=dict(name="activedirectory"), + ) + current = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ), + metadata=dict( + name="activedirectory", + created_by="me", + ), + ) + assert ad_auth_provider.do_differ(current, desired) is True + + def test_changes_are_other_params(self): + desired = dict( + spec=dict( + servers=[], + groups_prefix="ad", + username_prefix="ad", + ), + metadata=dict(name="activedirectory"), + ) + current = dict( + spec=dict( + servers=[], + ), + metadata=dict( + name="activedirectory", + created_by="me", + ), + ) + assert ad_auth_provider.do_differ(current, desired) is True + + +class TestADAutProvider(ModuleTestCase): + def test_minimal_provider_parameters(self, mocker): + sync_v1_mock = mocker.patch.object(utils, "sync_v1") + sync_v1_mock.return_value = True, {} + set_module_args( + state="present", + name="activedirectory", + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ) + + with pytest.raises(AnsibleExitJson): + ad_auth_provider.main() + + state, _client, path, payload, check_mode, _do_differ = sync_v1_mock.call_args[ + 0 + ] + + assert state == "present" + assert path == "/api/enterprise/authentication/v2/authproviders/activedirectory" + assert payload == dict( + type="ad", + api_version="authentication/v2", + metadata=dict(name="activedirectory"), + spec=dict( + servers=[ + dict( + host="127.0.0.1", + port=None, + insecure=False, + security="tls", + trusted_ca_file=None, + client_cert_file=None, + client_key_file=None, + default_upn_domain=None, + include_nested_groups=None, + binding=None, + group_search=dict( + base_dn="dc=acme,dc=org", + attribute="member", + name_attribute="cn", + object_class="group", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + attribute="sAMAccountName", + name_attribute="displayName", + object_class="person", + ), + ) + ] + ), + ) + + assert check_mode is False + + def test_all_provider_parameters(self, mocker): + sync_v1_mock = mocker.patch.object(utils, "sync_v1") + sync_v1_mock.return_value = True, {} + set_module_args( + state="present", + name="activedirectory", + servers=[ + dict( + host="127.0.0.1", + port=636, + insecure=False, + security="tls", + trusted_ca_file="/path/to/trusted-certificate-authorities.pem", + client_cert_file="/path/to/ssl/cert.pem", + client_key_file="/path/to/ssl/key.pem", + default_upn_domain="example.org", + include_nested_groups=True, + binding=dict( + user_dn="cn=binder,dc=acme,dc=org", + password="YOUR_PASSWORD", + ), + group_search=dict( + base_dn="dc=acme,dc=org", + attribute="member", + name_attribute="cn", + object_class="group", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + attribute="sAMAccountName", + name_attribute="displayName", + object_class="person", + ), + ) + ], + groups_prefix="ad", + username_prefix="ad", + ) + + with pytest.raises(AnsibleExitJson): + ad_auth_provider.main() + + state, _client, path, payload, check_mode, _do_differ = sync_v1_mock.call_args[ + 0 + ] + assert state == "present" + assert path == "/api/enterprise/authentication/v2/authproviders/activedirectory" + assert payload == dict( + type="ad", + api_version="authentication/v2", + metadata=dict(name="activedirectory"), + spec=dict( + servers=[ + dict( + host="127.0.0.1", + port=636, + insecure=False, + security="tls", + trusted_ca_file="/path/to/trusted-certificate-authorities.pem", + client_cert_file="/path/to/ssl/cert.pem", + client_key_file="/path/to/ssl/key.pem", + default_upn_domain="example.org", + include_nested_groups=True, + binding=dict( + user_dn="cn=binder,dc=acme,dc=org", + password="YOUR_PASSWORD", + ), + group_search=dict( + base_dn="dc=acme,dc=org", + attribute="member", + name_attribute="cn", + object_class="group", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + attribute="sAMAccountName", + name_attribute="displayName", + object_class="person", + ), + ) + ], + groups_prefix="ad", + username_prefix="ad", + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, "sync_v1") + sync_mock.side_effect = errors.Error("Bad error") + set_module_args() + + with pytest.raises(AnsibleFailJson): + ad_auth_provider.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_asset.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_asset.py new file mode 100644 index 00000000..8e9298b2 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_asset.py @@ -0,0 +1,251 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import asset + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestDoDiffer: + def test_equal_assets_with_none_values(self): + assert asset.do_differ( + { + "name": "asset", + "builds": [ + { + "sha512": "a", + "url": "a", + "filters": None + }, + ], + }, + { + "name": "asset", + "builds": [ + { + "sha512": "a", + "url": "a", + "headers": None, + }, + ], + }, + ) is False + + def test_equal_assets_with_different_build_content(self): + assert asset.do_differ( + { + "name": "asset", + "builds": [ + { + "url": "http://abc.com", + "sha512": "abc", + "headers": { + "foo": "bar", + "bar": "foo", + } + }, + { + "url": "http://def.com", + "sha512": "def", + "filters": ["d == d", "e == e"], + }, + + ] + }, + { + "name": "asset", + "builds": [ + { + "url": "http://def.com", + "sha512": "def", + "filters": ["e == e", "d == d"], + }, + { + "url": "http://abc.com", + "sha512": "abc", + "headers": { + "bar": "foo", + "foo": "bar" + } + }, + ] + }, + ) is False + + def test_updated_asset(self): + assert asset.do_differ( + { + "name": "asset", + "builds": [ + { + "url": "http://abc.com", + "sha512": "abc", + } + ], + "annotations": { + "foo": "bar", + } + }, + { + "name": "asset", + "builds": [ + { + "url": "http://def.com", + "sha512": "abc", + }, + { + "url": "http://def.com", + "sha512": "abc", + "filters": ["abc == def"], + } + ], + }, + ) is True + + def test_different_assets_with_same_builds(self): + assert asset.do_differ( + { + "name": "a", + "builds": [ + { + "url": "http://abc.com", + "sha512": "abc", + } + ], + "annotations": { + "foo": "bar", + } + }, + { + "name": "b", + "builds": [ + { + "url": "http://abc.com", + "sha512": "abc", + } + ], + "annotations": { + "bar": "foo" + } + }, + ) is True + + +class TestAsset(ModuleTestCase): + def test_minimal_asset_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.return_value = True, {} + set_module_args( + name="test_asset", + builds=[ + dict( + url="http://example.com/asset.tar.gz", + sha512="sha512String", + ), + ] + ) + + with pytest.raises(AnsibleExitJson): + asset.main() + + state, _client, path, payload, check_mode, _do_differ = sync_mock.call_args[0] + assert state == "present" + assert path == "/api/core/v2/namespaces/default/assets/test_asset" + assert payload == dict( + builds=[ + dict( + url="http://example.com/asset.tar.gz", + sha512="sha512String", + ), + ], + metadata=dict( + name="test_asset", + namespace="default", + ), + ) + assert check_mode is False + + def test_all_asset_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.return_value = True, {} + set_module_args( + name="test_asset", + namespace="my", + state="present", + builds=[ + dict( + url="http://example.com/asset.tar.gz", + sha512="sha512String", + filters=["a", "b", "c"], + headers={"header": "h"}, + ), + ], + labels={"region": "us-west-1"}, + annotations={"playbook": 12345}, + ) + + with pytest.raises(AnsibleExitJson): + asset.main() + + state, _client, path, payload, check_mode, _do_differ = sync_mock.call_args[0] + assert state == "present" + assert path == "/api/core/v2/namespaces/my/assets/test_asset" + assert payload == dict( + builds=[ + dict( + url="http://example.com/asset.tar.gz", + sha512="sha512String", + filters=["a", "b", "c"], + headers={"header": "h"}, + ) + ], + metadata=dict( + name="test_asset", + namespace="my", + labels={"region": "us-west-1"}, + annotations={"playbook": "12345"}, + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.side_effect = errors.Error("Bad error") + set_module_args( + name="test_asset", + builds=[ + dict( + url="http://example.com/asset.tar.gz", + sha512="sha512String", + ) + ] + + ) + + with pytest.raises(AnsibleFailJson): + asset.main() + + def test_failure_empty_builds(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = Exception("Validation should fail but didn't") + set_module_args( + name="test_asset", + builds=[], + ) + + with pytest.raises(AnsibleFailJson): + asset.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_asset_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_asset_info.py new file mode 100644 index 00000000..aab09b57 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_asset_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import asset_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestAssetInfo(ModuleTestCase): + def test_get_all_assets(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + asset_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/my/assets" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_asset(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 4 + set_module_args(name="sample-asset") + + with pytest.raises(AnsibleExitJson) as context: + asset_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/default/assets/sample-asset" + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_asset(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-asset") + + with pytest.raises(AnsibleExitJson) as context: + asset_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-asset") + + with pytest.raises(AnsibleFailJson): + asset_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_auth_provider_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_auth_provider_info.py new file mode 100644 index 00000000..bd19bceb --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_auth_provider_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import auth_provider_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestAuthProviderInfo(ModuleTestCase): + def test_get_all_auth_providers(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [dict(spec=dict(a=1)), dict(spec=dict(b=2))] + set_module_args() + + with pytest.raises(AnsibleExitJson) as context: + auth_provider_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/authentication/v2/authproviders" + assert context.value.args[0]["objects"] == [dict(a=1), dict(b=2)] + + def test_get_single_auth_provider(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = dict(spec=dict(a=1)) + set_module_args(name="sample-auth-provider") + + with pytest.raises(AnsibleExitJson) as context: + auth_provider_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/authentication/v2/authproviders/sample-auth-provider" + assert context.value.args[0]["objects"] == [dict(a=1)] + + def test_missing_single_auth_provider(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-auth-provider") + + with pytest.raises(AnsibleExitJson) as context: + auth_provider_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-auth-provider") + + with pytest.raises(AnsibleFailJson): + auth_provider_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_bonsai_asset.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_bonsai_asset.py new file mode 100644 index 00000000..e935a7a9 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_bonsai_asset.py @@ -0,0 +1,47 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import bonsai, errors +from ansible_collections.sensu.sensu_go.plugins.modules import bonsai_asset + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestBonsaiAsset(ModuleTestCase): + def test_success(self, mocker): + bonsai_params = mocker.patch.object(bonsai, "get_asset_parameters") + bonsai_params.return_value = dict(sample="value") + + set_module_args(name="name", version="version") + + with pytest.raises(AnsibleExitJson): + bonsai_asset.main() + + def test_bonsai_failure(self, mocker): + bonsai_params = mocker.patch.object(bonsai, "get_asset_parameters") + bonsai_params.side_effect = errors.BonsaiError("Bonsai bad") + + set_module_args(name="name", version="version") + + with pytest.raises(AnsibleFailJson): + bonsai_asset.main() + + def test_validation_failure(self, mocker): + bonsai_params = mocker.patch.object(bonsai, "get_asset_parameters") + bonsai_params.return_value = dict(sample="value") + + set_module_args(version="version") + + with pytest.raises(AnsibleFailJson): + bonsai_asset.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_check.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_check.py new file mode 100644 index 00000000..8ea6ecb9 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_check.py @@ -0,0 +1,327 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import check + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestDoSetsDiffer: + @pytest.mark.parametrize("current,desired,diff", [ + ([1, 2, 3], [1, 2, 3], False), + ([1, 2, 3], [3, 2, 1], False), + ([1, 2], [1, 2, 3], True), + ([1, 3], [2, 4], True), + ]) + def test_comparison(self, current, desired, diff): + c = dict(k=current) + d = dict(k=desired) + + assert check.do_sets_differ(c, d, "k") is diff + + def test_missing_keys_are_treated_as_empty_sets(self): + current = dict(a=[]) + desired = dict() + + assert check.do_sets_differ(current, desired, "a") is False + assert check.do_sets_differ(desired, current, "a") is False + + def test_nulls_are_treated_as_empty_sets(self): + current = dict(a=None) + desired = dict(a=[]) + + assert check.do_sets_differ(current, desired, "a") is False + assert check.do_sets_differ(desired, current, "a") is False + + +class TestDoProxyRequestsDiffer: + def test_missing_proxy_requests_in_desired_is_ignored(self): + current = dict(proxy_requests=dict(entity_attributes=["a", "b"])) + desired = dict() + + assert check.do_proxy_requests_differ(current, desired) is False + + @pytest.mark.parametrize("current,desired,diff", [ + (["a", "b"], ["a", "b"], False), + (["a", "b"], ["b", "a"], False), + (None, [], False), + (["a", "b"], ["c", "a"], True), + (["a", "b"], ["a", "b", "c"], True), + ]) + def test_treat_entity_attributes_as_a_set(self, current, desired, diff): + c = dict(proxy_requests=dict(entity_attributes=current)) + d = dict(proxy_requests=dict(entity_attributes=desired)) + + assert check.do_proxy_requests_differ(c, d) is diff + + def test_ignore_missing_entity_attributes_in_desired(self): + current = dict(proxy_requests=dict(entity_attributes=["a", "b"])) + desired = dict(proxy_requests=dict()) + + assert check.do_proxy_requests_differ(current, desired) is False + + @pytest.mark.parametrize("current,desired,diff", [ + (dict(splay=False), dict(splay=False), False), + (dict(splay=False), dict(), False), + (dict(splay=False), dict(splay=True), True), + (dict(), dict(splay=True), True), + ]) + def test_other_stuff_is_compared_as_usual(self, current, desired, diff): + c = dict(proxy_requests=current) + d = dict(proxy_requests=desired) + + assert check.do_proxy_requests_differ(c, d) is diff + + +class TestDoCheckHooksDiffer: + def test_missing_check_hooks_in_desired_is_ignored(self): + current = dict(check_hooks=[dict(warning=["a"])]) + desired = dict() + + assert check.do_check_hooks_differ(current, desired) is False + + @pytest.mark.parametrize("current,desired,diff", [ + (["a", "b"], ["a", "b"], False), + (["a", "b"], ["b", "a"], False), + (["a", "b"], ["c", "a"], True), + (["a", "b"], ["a", "b", "c"], True), + ]) + def test_treat_hooks_as_a_set(self, current, desired, diff): + c = dict(check_hooks=[dict(warning=current)]) + d = dict(check_hooks=[dict(warning=desired)]) + + assert check.do_check_hooks_differ(c, d) is diff + + +class TestDoDiffer: + def test_no_difference(self): + assert not check.do_differ( + dict( + command="sleep", + subscriptions=["sub1", "sub2"], + handlers=["ha1", "ha2", "ha3"], + interval=123, + cron="* * * 3 2", + publish=False, + timeout=30, + ttl=60, + stdin=True, + low_flap_threshold=2, + high_flap_threshold=10, + runtime_assets=["asset1", "asset2"], + check_hooks=[ + dict(warning=["hook0-1", "hook0-2"]), + dict(critical=["hook1-1", "hook1-2"]), + ], + proxy_entity_name="name", + proxy_requests=dict( + entity_attributes=["a1", "a2", "a3"], + splay=True, + splay_coverage=10, + ), + output_metric_format="influxdb_line", + output_metric_handlers=["mhandler1", "mhandler2"], + round_robin=False, + env_vars=["k1=v1", "k2=v2"], + ), + dict( + command="sleep", + subscriptions=["sub2", "sub1"], + handlers=["ha3", "ha1", "ha2"], + interval=123, + cron="* * * 3 2", + publish=False, + timeout=30, + ttl=60, + stdin=True, + low_flap_threshold=2, + high_flap_threshold=10, + runtime_assets=["asset2", "asset1"], + check_hooks=[ + dict(critical=["hook1-2", "hook1-1"]), + dict(warning=["hook0-2", "hook0-1"]), + ], + proxy_entity_name="name", + proxy_requests=dict( + splay=True, + entity_attributes=["a3", "a2", "a1"], + splay_coverage=10, + ), + output_metric_format="influxdb_line", + output_metric_handlers=["mhandler2", "mhandler1"], + round_robin=False, + env_vars=["k2=v2", "k1=v1"], + ) + ) + + @pytest.mark.parametrize("current,desired", [ + ( # No diff in params, no secrets + dict(name="demo"), + dict(name="demo"), + ), + ( # No diff in params, no diff in secrets + dict(name="demo", secrets=[ + dict(name="n1", secret="s1"), dict(name="n2", secret="s2"), + ]), + dict(name="demo", secrets=[ + dict(name="n2", secret="s2"), dict(name="n1", secret="s1"), + ]), + ), + ]) + def test_no_difference_secrets(self, current, desired): + assert check.do_differ(current, desired) is False + + @pytest.mark.parametrize("current,desired", [ + ( # Diff in params, no diff in secrets + dict(name="demo", secrets=[dict(name="a", secret="1")]), + dict(name="prod", secrets=[dict(name="a", secret="1")]), + ), + ( # No diff in params, missing and set secrets + dict(name="demo", secrets=[dict(name="a", secret="1")]), + dict(name="demo", secrets=[dict(name="b", secret="2")]), + ), + ( # Diff in params, missing and set secrets + dict(name="demo", secrets=[dict(name="a", secret="1")]), + dict(name="prod", secrets=[dict(name="b", secret="2")]), + ), + ]) + def test_difference_secrets(self, current, desired): + assert check.do_differ(current, desired) is True + + +class TestSensuGoCheck(ModuleTestCase): + def test_minimal_check_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.return_value = True, {} + set_module_args( + name="test_check", + command='echo "test"', + subscriptions=['switches'], + interval=60 + ) + + with pytest.raises(AnsibleExitJson): + check.main() + + state, _client, path, payload, check_mode, _d = sync_mock.call_args[0] + assert state == "present" + assert path == "/api/core/v2/namespaces/default/checks/test_check" + assert payload == dict( + command='echo "test"', + subscriptions=['switches'], + interval=60, + metadata=dict( + name="test_check", + namespace="default", + ), + ) + assert check_mode is False + + def test_all_check_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.return_value = True, {} + set_module_args( + name='test_check', + namespace='my', + state='absent', + command='/bin/true', + subscriptions=['checks', 'also_checks'], + handlers=['default', 'not_default'], + interval=30, + publish=True, + timeout=30, + ttl=100, + stdin=False, + low_flap_threshold=20, + high_flap_threshold=60, + proxy_entity_name='switch-dc-01', + proxy_requests=dict( + entity_attributes=['entity.entity_class == "proxy"'], + splay=True, + splay_coverage=90 + ), + output_metric_format='nagios_perfdata', + output_metric_handlers=['influx-db'], + round_robin=True, + env_vars=dict(foo='bar'), + runtime_assets='awesomeness', + secrets=[dict(name="a", secret="b")], + ) + + with pytest.raises(AnsibleExitJson): + check.main() + + state, _client, path, payload, check_mode, _d = sync_mock.call_args[0] + assert state == "absent" + assert path == "/api/core/v2/namespaces/my/checks/test_check" + assert payload == dict( + command='/bin/true', + subscriptions=['checks', 'also_checks'], + interval=30, + timeout=30, + publish=True, + handlers=['default', 'not_default'], + env_vars=['foo=bar'], + output_metric_handlers=['influx-db'], + ttl=100, + output_metric_format='nagios_perfdata', + proxy_entity_name='switch-dc-01', + proxy_requests=dict(entity_attributes=['entity.entity_class == "proxy"'], + splay=True, + splay_coverage=90), + high_flap_threshold=60, + low_flap_threshold=20, + round_robin=True, + stdin=False, + runtime_assets=['awesomeness'], + metadata=dict( + name="test_check", + namespace="my", + ), + secrets=[dict(name="a", secret="b")], + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.side_effect = errors.Error("Bad error") + set_module_args( + name='test_check', + command='/bin/true', + subscriptions=['checks', 'also_checks'], + handlers=['default', 'not_default'], + interval=30, + publish=True, + timeout=30, + ttl=100, + stdin=False, + low_flap_threshold=20, + high_flap_threshold=60, + proxy_entity_name='switch-dc-01', + proxy_requests=dict( + entity_attributes=['entity.entity_class == "proxy"'], + splay=True, + splay_coverage=90 + ), + output_metric_format='nagios_perfdata', + output_metric_handlers=['influx-db'], + round_robin=True, + env_vars=dict(foo='bar'), + runtime_assets='awesomeness' + ) + + with pytest.raises(AnsibleFailJson): + check.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_check_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_check_info.py new file mode 100644 index 00000000..53ed5b7c --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_check_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import check_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSensuGoCheckInfo(ModuleTestCase): + def test_get_all_checks(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + check_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/my/checks" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_check(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 4 + set_module_args(name="sample-check") + + with pytest.raises(AnsibleExitJson) as context: + check_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/default/checks/sample-check" + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_check(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-check") + + with pytest.raises(AnsibleExitJson) as context: + check_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-check") + + with pytest.raises(AnsibleFailJson): + check_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster.py new file mode 100644 index 00000000..28993364 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster.py @@ -0,0 +1,83 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import cluster + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestCluster(ModuleTestCase): + @pytest.mark.parametrize("params", [ + {"name": "demo", "state": "absent"}, + {"name": "demo", "api_urls": "url"}, + ]) + def test_minimal_parameters(self, mocker, params): + mocker.patch.object(utils, "sync_v1").return_value = True, {} + set_module_args(**params) + + with pytest.raises(AnsibleExitJson): + cluster.main() + + def test_all_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync_v1") + sync_mock.return_value = True, {} + set_module_args( + auth=dict( + user="user", + password="pass", + url="http://127.0.0.1:1234", + api_key="123-key", + verify=False, + ca_path="/tmp/ca.bundle", + ), + state="present", + name="demo", + api_urls=["a", "b"], + ) + + with pytest.raises(AnsibleExitJson): + cluster.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == "present" + assert path == "/api/enterprise/federation/v1/clusters/demo" + assert payload == dict( + type="Cluster", + api_version="federation/v1", + metadata=dict(name="demo"), + spec=dict(api_urls=["a", "b"]), + ) + assert check_mode is False + + @pytest.mark.parametrize("skip", ["api_urls"]) + def test_missing_required_param_present(self, mocker, skip): + sync_mock = mocker.patch.object(utils, "sync_v1") + all_args = dict(name="demo", api_urls="url") + set_module_args(**dict((k, v) for k, v in all_args.items() if k != skip)) + + with pytest.raises(AnsibleFailJson): + cluster.main() + + sync_mock.assert_not_called() + + def test_failure(self, mocker): + mocker.patch.object(utils, "sync_v1").side_effect = ( + errors.Error("Bad error") + ) + set_module_args(name="demo", state="absent") + + with pytest.raises(AnsibleFailJson): + cluster.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_info.py new file mode 100644 index 00000000..45158dcd --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_info.py @@ -0,0 +1,85 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import cluster_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestClusterInfo(ModuleTestCase): + def test_all_parameters(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = {"spec": {"k1": "v1"}} + set_module_args( + auth=dict( + user="user", + password="pass", + url="http://127.0.0.1:1234", + api_key="123-key", + verify=False, + ca_path="/tmp/ca.bundle", + ), + name="demo", + ) + + with pytest.raises(AnsibleExitJson): + cluster_info.main() + + def test_get_all_clusters(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [ + {"spec": {"k1": "v1"}}, {"spec": {"k2": "v2"}}, + ] + set_module_args() + + with pytest.raises(AnsibleExitJson) as context: + cluster_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/federation/v1/clusters" + assert context.value.args[0]["objects"] == [ + {"k1": "v1"}, {"k2": "v2"}, + ] + + def test_get_single_cluster(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = {"spec": {"k3": "v3"}} + set_module_args(name="demo") + + with pytest.raises(AnsibleExitJson) as context: + cluster_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/federation/v1/clusters/demo" + assert context.value.args[0]["objects"] == [{"k3": "v3"}] + + def test_missing_single_cluster(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="demo") + + with pytest.raises(AnsibleExitJson) as context: + cluster_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="demo") + + with pytest.raises(AnsibleFailJson): + cluster_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role.py new file mode 100644 index 00000000..48e939a0 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role.py @@ -0,0 +1,128 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils +) +from ansible_collections.sensu.sensu_go.plugins.modules import cluster_role + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestClusterRole(ModuleTestCase): + def test_minimal_cluster_role_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_cluster_role', + rules=[ + dict( + verbs=[], + resources=[] + ), + ] + ) + + with pytest.raises(AnsibleExitJson): + cluster_role.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/clusterroles/test_cluster_role' + assert payload == dict( + rules=[ + dict( + verbs=[], + resources=[], + resource_names=None, + ) + ], + metadata=dict( + name='test_cluster_role', + ), + ) + assert check_mode is False + + def test_all_cluster_role_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_cluster_role', + state='present', + rules=[ + dict( + verbs=['get', 'list', 'create'], + resources=['assets', 'entities'], + ), + dict( + verbs=['list'], + resources=['check'], + resource_names=['my-check'], + ) + ], + ) + + with pytest.raises(AnsibleExitJson): + cluster_role.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/clusterroles/test_cluster_role' + assert payload == dict( + metadata=dict( + name='test_cluster_role', + ), + rules=[ + dict( + verbs=['get', 'list', 'create'], + resources=['assets', 'entities'], + resource_names=None, + ), + dict( + verbs=['list'], + resources=['check'], + resource_names=['my-check'], + ) + ], + ) + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = errors.Error("Bad error") + set_module_args( + name='test_cluster_role', + rules=[ + dict( + verbs=[], + resources=[], + ) + ], + ) + with pytest.raises(AnsibleFailJson): + cluster_role.main() + + def test_failure_invalid_verb(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = Exception("Validation should fail but didn't") + set_module_args( + name='test_cluster_role', + rules=[ + dict( + verbs=['list', 'invalid'], + resources=[], + ), + ] + ) + + with pytest.raises(AnsibleFailJson): + cluster_role.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role_binding.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role_binding.py new file mode 100644 index 00000000..b18c2d22 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role_binding.py @@ -0,0 +1,152 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import cluster_role_binding + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestClusterRoleBinding(ModuleTestCase): + def test_minimal_cluster_role_binding_parameters_users(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_cluster_role_binding', + cluster_role='test_cluster_role', + users=['test_user'], + ) + + with pytest.raises(AnsibleExitJson): + cluster_role_binding.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/clusterrolebindings/test_cluster_role_binding' + assert payload == dict( + role_ref=dict( + name='test_cluster_role', + type='ClusterRole', + ), + subjects=[ + dict( + name='test_user', + type='User', + ), + ], + metadata=dict( + name='test_cluster_role_binding', + ), + ) + assert check_mode is False + + def test_minimal_cluster_role_binding_parameters_groups(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_cluster_role_binding', + cluster_role='test_cluster_role', + groups=['test_group'], + ) + + with pytest.raises(AnsibleExitJson): + cluster_role_binding.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/clusterrolebindings/test_cluster_role_binding' + assert payload == dict( + role_ref=dict( + name='test_cluster_role', + type='ClusterRole', + ), + subjects=[ + dict( + name='test_group', + type='Group', + ), + ], + metadata=dict( + name='test_cluster_role_binding', + ), + ) + assert check_mode is False + + def test_all_cluster_role_binding_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_cluster_role_binding', + cluster_role='test_cluster_role', + users=['user_1', 'user_2'], + groups=['group_1', 'group_2'], + ) + + with pytest.raises(AnsibleExitJson): + cluster_role_binding.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/clusterrolebindings/test_cluster_role_binding' + assert payload == dict( + metadata=dict( + name='test_cluster_role_binding', + ), + role_ref=dict( + name='test_cluster_role', + type='ClusterRole', + ), + subjects=[ + dict( + name='group_1', + type='Group', + ), + dict( + name='group_2', + type='Group', + ), + dict( + name='user_1', + type='User', + ), + dict( + name='user_2', + type='User', + ), + ] + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = errors.Error('Bad error') + set_module_args( + name='test_cluster_role_binding', + cluster_role='test_cluster_role', + users=['test_user'], + ) + with pytest.raises(AnsibleFailJson): + cluster_role_binding.main() + + def test_failure_missing_groups_or_users(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = Exception("Validation should fail but didn't") + set_module_args( + name='test_cluster_role_binding', + cluster_role='test_cluster_role', + ) + + with pytest.raises(AnsibleFailJson): + cluster_role_binding.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role_binding_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role_binding_info.py new file mode 100644 index 00000000..34e58451 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role_binding_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import cluster_role_binding_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestClusterRoleBindingInfo(ModuleTestCase): + def test_get_all_cluster_role_bindings(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args() + + with pytest.raises(AnsibleExitJson) as context: + cluster_role_binding_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/clusterrolebindings" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_cluster_role_binding(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 1 + set_module_args(name="test-cluster-role-binding") + + with pytest.raises(AnsibleExitJson) as context: + cluster_role_binding_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/clusterrolebindings/test-cluster-role-binding" + assert context.value.args[0]["objects"] == [1] + + def test_missing_single_cluster_role_binding(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-cluster-role-binding") + + with pytest.raises(AnsibleExitJson) as context: + cluster_role_binding_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-cluster-role-binding") + + with pytest.raises(AnsibleFailJson): + cluster_role_binding_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role_info.py new file mode 100644 index 00000000..11eadb6a --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import cluster_role_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestClusterRoleInfo(ModuleTestCase): + def test_get_all_cluster_roles(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args() + + with pytest.raises(AnsibleExitJson) as context: + cluster_role_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/clusterroles" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_cluster_role(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 1 + set_module_args(name="test-cluster-role") + + with pytest.raises(AnsibleExitJson) as context: + cluster_role_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/clusterroles/test-cluster-role" + assert context.value.args[0]["objects"] == [1] + + def test_missing_single_cluster_role(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-cluster-role") + + with pytest.raises(AnsibleExitJson) as context: + cluster_role_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-cluster-role") + + with pytest.raises(AnsibleFailJson): + cluster_role_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_datastore.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_datastore.py new file mode 100644 index 00000000..665b29ab --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_datastore.py @@ -0,0 +1,269 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, http, +) +from ansible_collections.sensu.sensu_go.plugins.modules import datastore + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSync(ModuleTestCase): + def test_absent_no_current_object(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(404, "") + + changed, object = datastore.sync( + "absent", client, "/list", "/resource", {}, False, + ) + + assert changed is False + assert object is None + + def test_absent_no_current_object_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(404, "") + + changed, object = datastore.sync( + "absent", client, "/list", "/resource", {}, True, + ) + + assert changed is False + assert object is None + + def test_absent_current_object_present(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{}') + client.delete.return_value = http.Response(204, "") + + changed, object = datastore.sync( + "absent", client, "/list", "/resource", {}, False, + ) + + assert changed is True + assert object is None + client.delete.assert_called_with("/resource") + + def test_absent_current_object_present_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{}') + client.delete.return_value = http.Response(204, "") + + changed, object = datastore.sync( + "absent", client, "/list", "/resource", {}, True, + ) + + assert changed is True + assert object is None + client.delete.assert_not_called() + + def test_present_current_object_differ(self, mocker): + client = mocker.Mock() + client.get.side_effect = ( + http.Response(200, '{"spec": {"current": "data"}}'), + http.Response(200, '{"spec": {"new": "data"}}'), + ) + client.put.return_value = http.Response(201, "") + + changed, object = datastore.sync( + "present", client, "/list", "/resource", {"spec": {"my": "data"}}, + False, + ) + + assert changed is True + assert {"new": "data"} == object + client.put.assert_called_once_with( + "/resource", {"spec": {"my": "data"}}, + ) + + def test_present_current_object_differ_check(self, mocker): + client = mocker.Mock() + client.get.return_value = ( + http.Response(200, '{"spec": {"current": "data"}}') + ) + + changed, object = datastore.sync( + "present", client, "/list", "/resource", {"spec": {"my": "data"}}, + True, + ) + + assert changed is True + assert {"my": "data"} == object + client.put.assert_not_called() + + def test_present_current_object_does_not_differ(self, mocker): + client = mocker.Mock() + client.get.return_value = ( + http.Response(200, '{"spec": {"my": "data"}}') + ) + + changed, object = datastore.sync( + "present", client, "/list", "/resource", {"spec": {"my": "data"}}, + False, + ) + + assert changed is False + assert {"my": "data"} == object + client.put.assert_not_called() + + def test_present_current_object_does_not_differ_check(self, mocker): + client = mocker.Mock() + client.get.return_value = ( + http.Response(200, '{"spec": {"my": "data"}}') + ) + + changed, object = datastore.sync( + "present", client, "/list", "/resource", {"spec": {"my": "data"}}, + True, + ) + + assert changed is False + assert {"my": "data"} == object + client.put.assert_not_called() + + def test_present_no_current_object_empty_backend(self, mocker): + client = mocker.Mock() + client.get.side_effect = ( + http.Response(404, ""), + http.Response(200, "[]"), + http.Response(200, '{"spec": {"new": "data"}}'), + ) + client.put.return_value = http.Response(201, "") + + changed, object = datastore.sync( + "present", client, "/list", "/resource", {"spec": {"my": "data"}}, + False, + ) + + assert changed is True + assert {"new": "data"} == object + client.put.assert_called_once_with( + "/resource", {"spec": {"my": "data"}}, + ) + + def test_present_no_current_object_empty_backend_check(self, mocker): + client = mocker.Mock() + client.get.side_effect = ( + http.Response(404, ""), + http.Response(200, "[]"), + ) + + changed, object = datastore.sync( + "present", client, "/list", "/resource", {"spec": {"my": "data"}}, + True, + ) + + assert changed is True + assert {"my": "data"} == object + client.put.assert_not_called() + + @pytest.mark.parametrize("check", [False, True]) + def test_present_no_current_object_non_empty_backend(self, mocker, check): + client = mocker.Mock() + client.get.side_effect = ( + http.Response(404, ""), + http.Response(200, "[{}]"), + ) + + with pytest.raises(errors.Error, match="already active"): + datastore.sync( + "present", client, "/list", "/resource", + {"spec": {"my": "data"}}, check, + ) + + client.put.assert_not_called() + + +class TestDatastore(ModuleTestCase): + def test_minimal_datastore_parameters_present(self, mocker): + sync_mock = mocker.patch.object(datastore, "sync") + sync_mock.return_value = True, {} + set_module_args( + name="test_datastore", + dsn="my-dsn", + ) + + with pytest.raises(AnsibleExitJson): + datastore.main() + + state, _client, list_path, resource_path, payload, check_mode = ( + sync_mock.call_args[0] + ) + assert state == "present" + assert resource_path == "/api/enterprise/store/v1/provider/test_datastore" + assert list_path == "/api/enterprise/store/v1/provider" + assert payload == dict( + type="PostgresConfig", + api_version="store/v1", + metadata=dict(name="test_datastore"), + spec=dict(dsn="my-dsn"), + ) + assert check_mode is False + + def test_minimal_datastore_parameters_absent(self, mocker): + sync_mock = mocker.patch.object(datastore, "sync") + sync_mock.return_value = True, {} + set_module_args( + name="test_datastore", + state="absent", + ) + + with pytest.raises(AnsibleExitJson): + datastore.main() + + state, _client, list_path, resource_path, _payload, check_mode = ( + sync_mock.call_args[0] + ) + assert state == "absent" + assert resource_path == "/api/enterprise/store/v1/provider/test_datastore" + assert list_path == "/api/enterprise/store/v1/provider" + assert check_mode is False + + def test_all_datastore_parameters(self, mocker): + sync_mock = mocker.patch.object(datastore, "sync") + sync_mock.return_value = True, {} + set_module_args( + name="test_datastore", + dsn="my-dsn", + pool_size=543, + ) + + with pytest.raises(AnsibleExitJson): + datastore.main() + + state, _client, list_path, resource_path, payload, check_mode = ( + sync_mock.call_args[0] + ) + assert state == "present" + assert resource_path == "/api/enterprise/store/v1/provider/test_datastore" + assert list_path == "/api/enterprise/store/v1/provider" + assert payload == dict( + type="PostgresConfig", + api_version="store/v1", + metadata=dict(name="test_datastore"), + spec=dict(dsn="my-dsn", pool_size=543), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(datastore, "sync") + sync_mock.side_effect = errors.Error("Bad error") + set_module_args( + name="test_datastore", + dsn="my-dsn", + ) + + with pytest.raises(AnsibleFailJson): + datastore.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_datastore_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_datastore_info.py new file mode 100644 index 00000000..4a1efc9c --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_datastore_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import datastore_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestDatastoreInfo(ModuleTestCase): + def test_get_all_datastores(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [dict(spec=1), dict(spec=2)] + set_module_args() + + with pytest.raises(AnsibleExitJson) as context: + datastore_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/store/v1/provider" + assert context.value.args[0]["objects"] == [1, 2] + + def test_get_single_datastore(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = dict(spec=4) + set_module_args(name="sample-datastore") + + with pytest.raises(AnsibleExitJson) as context: + datastore_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/store/v1/provider/sample-datastore" + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_datastore(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-datastore") + + with pytest.raises(AnsibleExitJson) as context: + datastore_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-datastore") + + with pytest.raises(AnsibleFailJson): + datastore_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_entity.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_entity.py new file mode 100644 index 00000000..fb098ed8 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_entity.py @@ -0,0 +1,199 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import entity + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestDoDiffer: + @pytest.mark.parametrize('current', [ + dict(no=dict(system="here")), + dict(system=dict(here="is")), + ]) + def test_no_system_in_desired(self, current): + assert entity.do_differ(current, {}) is False + + def test_system_keys_not_in_current_are_ignored(self): + assert entity.do_differ( + dict(system=dict(a=1, b=2)), + dict(system=dict(a=1)), + ) is False + + def test_actual_changes_are_detected(self): + assert entity.do_differ( + dict(system=dict(a=1, b=2)), + dict(system=dict(a=2)), + ) is True + + def test_missing_keys_are_detected(self): + assert entity.do_differ( + dict(system=dict(b=2)), + dict(system=dict(a=2)), + ) is True + + @pytest.mark.parametrize("current,desired", [ + ([], None), ([], []), + (["a"], ["a"]), + (["a", "b"], ["b", "a"]), + ]) + def test_no_diff_in_subscriptions(self, current, desired): + assert entity.do_differ( + dict(subscriptions=current), dict(subscriptions=desired), + ) is False + + @pytest.mark.parametrize("current,desired", [ + ([], ["a"]), (["a"], []), + (["a"], ["b"]), + (["a", "b"], ["a", "c"]), + ]) + def test_diff_in_subscriptions(self, current, desired): + print((current, desired)) + assert entity.do_differ( + dict(subscriptions=current), dict(subscriptions=desired), + ) is True + + +class TestEntity(ModuleTestCase): + def test_minimal_entity_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_entity', + entity_class='proxy', + ) + + with pytest.raises(AnsibleExitJson): + entity.main() + + state, _c, path, payload, check_mode, _d = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/entities/test_entity' + assert payload == dict( + entity_class='proxy', + metadata=dict( + name='test_entity', + namespace='default', + ), + ) + assert check_mode is False + + def test_minimal_entity_parameters_agent_class(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_entity', + entity_class='agent', + ) + + with pytest.raises(AnsibleExitJson): + entity.main() + + state, _c, path, payload, check_mode, _d = sync_mock.call_args[0] + print(payload) + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/entities/test_entity' + assert payload == dict( + entity_class='agent', + metadata=dict( + name='test_entity', + namespace='default', + ), + subscriptions=['entity:test_entity'], + ) + assert check_mode is False + + def test_all_entity_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_entity', + namespace='my', + state='absent', + entity_class='proxy', + subscriptions=['web', 'prod'], + system=dict( + hostname='test-entity', + os='linux', + platform='ubuntu', + network=dict( + interfaces=[ + dict( + name='lo', + addresses=['127.0.0.1/8', '::1/128'] + ), + dict( + name='eth0', + mac='52:54:00:20:1b:3c', + addresses=['93.184.216.34/24'] + ) + ]) + ), + last_seen=1522798317, + deregister=True, + deregistration_handler='email-handler', + redact=['password', 'pass', 'api_key'], + user='agent' + ) + + with pytest.raises(AnsibleExitJson): + entity.main() + + state, _c, path, payload, check_mode, _d = sync_mock.call_args[0] + assert state == 'absent' + assert path == '/api/core/v2/namespaces/my/entities/test_entity' + assert payload == dict( + entity_class='proxy', + subscriptions=['web', 'prod'], + system=dict( + hostname='test-entity', + os='linux', + platform='ubuntu', + network=dict( + interfaces=[ + dict( + name='lo', + addresses=['127.0.0.1/8', '::1/128'] + ), + dict( + name='eth0', + mac='52:54:00:20:1b:3c', + addresses=['93.184.216.34/24'] + ) + ]) + ), + last_seen=1522798317, + deregister=True, + deregistration=dict(handler='email-handler'), + redact=['password', 'pass', 'api_key'], + user='agent', + metadata=dict( + name='test_entity', + namespace='my' + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = errors.Error('Bad error') + set_module_args( + name='test_entity', + entity_class='proxy' + ) + + with pytest.raises(AnsibleFailJson): + entity.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_entity_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_entity_info.py new file mode 100644 index 00000000..964dcaed --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_entity_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import entity_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestEntityInfo(ModuleTestCase): + def test_get_all_entities(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + entity_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/my/entities" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_entity(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 4 + set_module_args(name="sample-entity") + + with pytest.raises(AnsibleExitJson) as context: + entity_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/default/entities/sample-entity" + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_entity(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-entity") + + with pytest.raises(AnsibleExitJson) as context: + entity_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-entity") + + with pytest.raises(AnsibleFailJson): + entity_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_etcd_replicator.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_etcd_replicator.py new file mode 100644 index 00000000..982c97c5 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_etcd_replicator.py @@ -0,0 +1,132 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import etcd_replicator + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestEtcdReplicator(ModuleTestCase): + @pytest.mark.parametrize("params", [ + {"name": "demo", "state": "absent"}, + {"name": "demo", "insecure": True, "url": "url", "resource": "resource"}, + { + "name": "demo", + "url": "url", + "resource": "resource", + "ca_cert": "ca_cert", + "cert": "cert", + "key": "key", + }, + ]) + def test_minimal_parameters(self, mocker, params): + mocker.patch.object(utils, "sync_v1").return_value = True, {} + set_module_args(**params) + + with pytest.raises(AnsibleExitJson): + etcd_replicator.main() + + def test_all_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync_v1") + sync_mock.return_value = True, {} + set_module_args( + auth=dict( + user="user", + password="pass", + url="http://127.0.0.1:1234", + api_key="123-key", + verify=False, + ca_path="/tmp/ca.bundle", + ), + state="present", + name="demo", + ca_cert="ca_cert", + cert="cert", + key="key", + insecure=True, + url=["a", "b"], + api_version="api_version", + resource="resource", + namespace="namespace", + replication_interval=30, + ) + + with pytest.raises(AnsibleExitJson): + etcd_replicator.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == "present" + assert path == "/api/enterprise/federation/v1/etcd-replicators/demo" + assert payload == dict( + type="EtcdReplicator", + api_version="federation/v1", + metadata=dict(name="demo"), + spec=dict( + ca_cert="ca_cert", + cert="cert", + key="key", + insecure=True, + url="a,b", + api_version="api_version", + resource="resource", + namespace="namespace", + replication_interval_seconds=30, + ), + ) + assert check_mode is False + + @pytest.mark.parametrize("skip", ["ca_cert", "cert", "key", "url", "resource"]) + def test_missing_required_param_present_secure(self, mocker, skip): + sync_mock = mocker.patch.object(utils, "sync_v1") + all_args = dict( + name="demo", + ca_cert="ca_cert", + cert="cert", + key="key", + url="url", + resource="resource", + ) + set_module_args(**dict((k, v) for k, v in all_args.items() if k != skip)) + + with pytest.raises(AnsibleFailJson): + etcd_replicator.main() + + sync_mock.assert_not_called() + + @pytest.mark.parametrize("skip", ["url", "resource"]) + def test_missing_required_param_present_insecure(self, mocker, skip): + sync_mock = mocker.patch.object(utils, "sync_v1") + all_args = dict( + name="demo", + insecure=True, + url="url", + resource="resource", + ) + set_module_args(**dict((k, v) for k, v in all_args.items() if k != skip)) + + with pytest.raises(AnsibleFailJson): + etcd_replicator.main() + + sync_mock.assert_not_called() + + def test_failure(self, mocker): + mocker.patch.object(utils, "sync_v1").side_effect = ( + errors.Error("Bad error") + ) + set_module_args(name="demo", state="absent") + + with pytest.raises(AnsibleFailJson): + etcd_replicator.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_etcd_replicator_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_etcd_replicator_info.py new file mode 100644 index 00000000..e651dab7 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_etcd_replicator_info.py @@ -0,0 +1,85 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import etcd_replicator_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestEtcdReplicatorInfo(ModuleTestCase): + def test_all_parameters(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = {"spec": {"k1": "v1"}} + set_module_args( + auth=dict( + user="user", + password="pass", + url="http://127.0.0.1:1234", + api_key="123-key", + verify=False, + ca_path="/tmp/ca.bundle", + ), + name="demo", + ) + + with pytest.raises(AnsibleExitJson): + etcd_replicator_info.main() + + def test_get_all_secrets(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [ + {"spec": {"k1": "v1"}}, {"spec": {"k2": "v2"}}, + ] + set_module_args() + + with pytest.raises(AnsibleExitJson) as context: + etcd_replicator_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/federation/v1/etcd-replicators" + assert context.value.args[0]["objects"] == [ + {"k1": "v1"}, {"k2": "v2"}, + ] + + def test_get_single_replicator(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = {"spec": {"k3": "v3"}} + set_module_args(name="demo") + + with pytest.raises(AnsibleExitJson) as context: + etcd_replicator_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/federation/v1/etcd-replicators/demo" + assert context.value.args[0]["objects"] == [{"k3": "v3"}] + + def test_missing_single_replicator(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="demo") + + with pytest.raises(AnsibleExitJson) as context: + etcd_replicator_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="demo") + + with pytest.raises(AnsibleFailJson): + etcd_replicator_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_event.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_event.py new file mode 100644 index 00000000..8808ec2c --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_event.py @@ -0,0 +1,286 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, http, +) +from ansible_collections.sensu.sensu_go.plugins.modules import event + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestGetObjects: + def test_get_entity(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"entity": "entity"}') + resp = event.get_entity(client, 'default', 'entity') + + assert resp == {'entity': 'entity'} + + def test_get_entity_404(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(404, '') + + with pytest.raises(errors.SyncError, + match="Entity with name 'entity' does not exist on remote."): + event.get_entity(client, 'default', 'entity') + + def test_get_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"check": "check"}') + resp = event.get_check(client, 'default', 'check') + + assert resp == {'check': 'check'} + + def test_get_check_404(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(404, '') + + with pytest.raises(errors.SyncError, + match="Check with name 'check' does not exist on remote."): + event.get_check(client, 'default', 'check') + + +class TestEvent(ModuleTestCase): + def test_missing_entity_on_remote(self, mocker): + get_entity_mock = mocker.patch.object(event, 'get_entity') + get_entity_mock.side_effect = errors.SyncError('Error') + + set_module_args( + entity='awesome_entity', + check='awesome_check', + ) + + with pytest.raises(AnsibleFailJson, match='Error'): + event.main() + + def test_missing_check_on_remote(self, mocker): + mocker.patch.object(event, 'get_entity') + get_check_mock = mocker.patch.object(event, 'get_check') + get_check_mock.side_effect = errors.SyncError('Error') + + set_module_args( + entity='awesome_entity', + check='awesome_check', + ) + + with pytest.raises(AnsibleFailJson, match='Error'): + event.main() + + def test_minimal_event_parameters(self, mocker): + send_event_mock = mocker.patch.object(event, 'send_event') + send_event_mock.return_value = True, {} + get_entity_mock = mocker.patch.object(event, 'get_entity') + get_entity_mock.return_value = dict( + metadata=dict( + name='awesome_entity', + namespace='default' + ), + entity_class='proxy' + ) + get_check_mock = mocker.patch.object(event, 'get_check') + get_check_mock.return_value = dict( + metadata=dict( + name='awesome_check', + namespace='default' + ) + ) + + set_module_args( + entity='awesome_entity', + check='awesome_check', + ) + + with pytest.raises(AnsibleExitJson): + event.main() + + _client, path, payload, check_mode = send_event_mock.call_args[0] + assert path == '/api/core/v2/namespaces/default/events/awesome_entity/awesome_check' + assert payload == dict( + metadata=dict( + namespace='default' + ), + entity=dict( + metadata=dict( + name='awesome_entity', + namespace='default' + ), + entity_class='proxy' + ), + check=dict( + metadata=dict( + name='awesome_check', + namespace='default' + ) + ) + ) + assert check_mode is False + + def test_all_event_parameters(self, mocker): + entity_object = dict( + metadata=dict( + name='awesome_entity', + namespace='default' + ), + entity_class='proxy' + ) + check_object = dict( + metadata=dict( + name='awesome_check', + namespace='default' + ), + command="check-cpu.sh -w 75 -c 90", + handlers=["slack"], + interval=60, + publish=True, + subscriptions=["linux"], + ) + send_event_mock = mocker.patch.object(event, 'send_event') + send_event_mock.return_value = True, {} + get_entity_mock = mocker.patch.object(event, 'get_entity') + get_entity_mock.return_value = entity_object + get_check_mock = mocker.patch.object(event, 'get_check') + get_check_mock.return_value = check_object + + set_module_args( + namespace='my', + timestamp=1234567, + entity='awesome_entity', + check='awesome_check', + check_attributes=dict( + duration=1.945, + executed=1522100915, + history=[ + dict( + executed=1552505193, + status=1 + ), + dict( + executed=1552505293, + status=0 + ), + dict( + executed=1552505393, + status=0 + ), + dict( + executed=1552505493, + status=0 + ) + ], + issued=1552506033, + last_ok=1552506033, + output='10', + state='passing', + status='ok', + total_state_change=0 + ), + metric_attributes=dict( + handlers=['handler1', 'handler2'], + points=[{ + 'name': 'sensu-go-sandbox.curl_timings.time_total', + 'tags': [], + 'timestamp': 1552506033, + 'value': 0.005 + }, { + 'name': 'sensu-go-sandbox.curl_timings.time_namelookup', + 'tags': [], + 'timestamp': 1552506033, + 'value': 0.004 + }] + ) + ) + + with pytest.raises(AnsibleExitJson): + event.main() + + _client, path, payload, check_mode = send_event_mock.call_args[0] + assert path == '/api/core/v2/namespaces/my/events/awesome_entity/awesome_check' + assert payload == dict( + metadata=dict( + namespace='my' + ), + timestamp=1234567, + entity=dict( + metadata=dict( + name='awesome_entity', + namespace='default' + ), + entity_class='proxy' + ), + check=dict( + metadata=dict( + name='awesome_check', + namespace='default' + ), + command="check-cpu.sh -w 75 -c 90", + handlers=["slack"], + interval=60, + publish=True, + subscriptions=["linux"], + duration=1.945, + executed=1522100915, + history=[ + dict( + executed=1552505193, + status=1 + ), + dict( + executed=1552505293, + status=0 + ), + dict( + executed=1552505393, + status=0 + ), + dict( + executed=1552505493, + status=0 + ) + ], + issued=1552506033, + last_ok=1552506033, + output='10', + state='passing', + status=0, + total_state_change=0 + ), + metrics=dict( + handlers=['handler1', 'handler2'], + points=[{ + 'name': 'sensu-go-sandbox.curl_timings.time_total', + 'tags': [], + 'timestamp': 1552506033, + 'value': 0.005 + }, { + 'name': 'sensu-go-sandbox.curl_timings.time_namelookup', + 'tags': [], + 'timestamp': 1552506033, + 'value': 0.004 + }] + ) + ) + assert check_mode is False + + def test_failure(self, mocker): + get_entity_mock = mocker.patch.object(event, 'get_entity') + get_entity_mock.side_effect = errors.Error('Bad error') + set_module_args( + entity='awesome_entity', + check=dict( + name='awesome_check' + ) + ) + + with pytest.raises(AnsibleFailJson): + event.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_event_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_event_info.py new file mode 100644 index 00000000..c9270892 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_event_info.py @@ -0,0 +1,94 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import event_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestEventInfo(ModuleTestCase): + def test_get_all_events(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + event_info.main() + + _client, path = get_mock.call_args[0] + assert path == '/api/core/v2/namespaces/my/events' + assert context.value.args[0]['objects'] == [1, 2, 3] + + def test_get_events_by_entity(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.return_value = [1, 2] + set_module_args( + entity='simple-entity' + ) + + with pytest.raises(AnsibleExitJson) as context: + event_info.main() + + _client, path = get_mock.call_args[0] + assert path == '/api/core/v2/namespaces/default/events/simple-entity' + assert context.value.args[0]['objects'] == [1, 2] + + def test_get_events_by_check(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.return_value = [1, 2] + set_module_args( + check='simple-check' + ) + + with pytest.raises(AnsibleFailJson, + match=r"missing parameter\(s\) required by 'check': entity"): + event_info.main() + + def test_get_single_event_by_entity_and_check(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.return_value = 4 + set_module_args( + entity='simple-entity', + check='simple-check' + ) + + with pytest.raises(AnsibleExitJson) as context: + event_info.main() + + _client, path = get_mock.call_args[0] + assert path == '/api/core/v2/namespaces/default/events/simple-entity/simple-check' + assert context.value.args[0]['objects'] == [4] + + def test_no_event_by_entity_and_check(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args( + entity='simple-entity', + check='simple-check' + ) + + with pytest.raises(AnsibleExitJson) as context: + event_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.side_effect = errors.Error('Bad error') + set_module_args(entity='simple-entity') + + with pytest.raises(AnsibleFailJson): + event_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_filter.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_filter.py new file mode 100644 index 00000000..f92a5262 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_filter.py @@ -0,0 +1,91 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import filter + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestFilter(ModuleTestCase): + def test_minimal_filter_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_filter', + action='allow', + expressions='event.check.occurences == 1', + ) + + with pytest.raises(AnsibleExitJson): + filter.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/filters/test_filter' + assert payload == dict( + action='allow', + expressions=['event.check.occurences == 1'], + metadata=dict( + name='test_filter', + namespace='default', + ), + ) + assert check_mode is False + + def test_all_filter_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_filter', + namespace='my', + state='absent', + action='allow', + expressions='event.check.occurences == 1', + runtime_assets='awesomeness', + labels={'region': 'us-west-1'}, + annotations={'playbook': 12345}, + ) + + with pytest.raises(AnsibleExitJson): + filter.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == 'absent' + assert path == '/api/core/v2/namespaces/my/filters/test_filter' + assert payload == dict( + action='allow', + expressions=['event.check.occurences == 1'], + runtime_assets=['awesomeness'], + metadata=dict( + name='test_filter', + namespace='my', + labels={'region': 'us-west-1'}, + annotations={'playbook': '12345'}, + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = errors.Error('Bad error') + set_module_args( + name='test_filter', + action='deny', + expressions='event.check.occurences == 1', + ) + + with pytest.raises(AnsibleFailJson): + filter.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_filter_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_filter_info.py new file mode 100644 index 00000000..d8bbcf41 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_filter_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import filter_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestFilterInfo(ModuleTestCase): + def test_get_all_filters(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + filter_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/my/filters" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_filter(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 4 + set_module_args(name="sample-filter") + + with pytest.raises(AnsibleExitJson) as context: + filter_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/default/filters/sample-filter" + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_filter(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-filter") + + with pytest.raises(AnsibleExitJson) as context: + filter_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-filter") + + with pytest.raises(AnsibleFailJson): + filter_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_handler_set.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_handler_set.py new file mode 100644 index 00000000..bd6aa805 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_handler_set.py @@ -0,0 +1,59 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import handler_set + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestHandlerSet(ModuleTestCase): + def test_all_handler_set_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.return_value = True, {} + set_module_args( + name='test_handler', + namespace='my', + state='absent', + handlers=['tcp_handler', 'udp_handler'] + ) + + with pytest.raises(AnsibleExitJson): + handler_set.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == "absent" + assert path == "/api/core/v2/namespaces/my/handlers/test_handler" + assert payload == dict( + type='set', + handlers=['tcp_handler', 'udp_handler'], + metadata=dict( + name="test_handler", + namespace="my", + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.side_effect = errors.Error("Bad error") + set_module_args( + name='test_handler', + state='absent', + handlers=['tcp_handler', 'udp_handler'] + ) + + with pytest.raises(AnsibleFailJson): + handler_set.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_hook.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_hook.py new file mode 100644 index 00000000..3b6e6da8 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_hook.py @@ -0,0 +1,93 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import hook + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestHook(ModuleTestCase): + def test_minimal_hook_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_hook', + command='/bin/true', + timeout=10, + ) + + with pytest.raises(AnsibleExitJson): + hook.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/hooks/test_hook' + assert payload == dict( + command='/bin/true', + timeout=10, + metadata=dict( + name='test_hook', + namespace='default', + ), + ) + assert check_mode is False + + def test_all_hook_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_hook', + namespace='my', + state='absent', + command='/bin/true', + timeout=30, + stdin=True, + runtime_assets='awesomeness', + labels={'region': 'us-west-1'}, + annotations={'playbook': 12345}, + ) + + with pytest.raises(AnsibleExitJson): + hook.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == 'absent' + assert path == '/api/core/v2/namespaces/my/hooks/test_hook' + assert payload == dict( + command='/bin/true', + timeout=30, + stdin=True, + runtime_assets=['awesomeness'], + metadata=dict( + name='test_hook', + namespace='my', + labels={'region': 'us-west-1'}, + annotations={'playbook': '12345'}, + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = errors.Error('Bad error') + set_module_args( + name='test_hook', + command='/bin/true', + timeout=10 + ) + + with pytest.raises(AnsibleFailJson): + hook.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_hook_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_hook_info.py new file mode 100644 index 00000000..82269840 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_hook_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import hook_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestHookInfo(ModuleTestCase): + def test_get_all_hooks(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + hook_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/my/hooks" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_hook(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 4 + set_module_args(name="sample-hook") + + with pytest.raises(AnsibleExitJson) as context: + hook_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/default/hooks/sample-hook" + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_hook(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-hook") + + with pytest.raises(AnsibleExitJson) as context: + hook_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-hook") + + with pytest.raises(AnsibleFailJson): + hook_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_ldap_auth_provider.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_ldap_auth_provider.py new file mode 100644 index 00000000..4b9e1c46 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_ldap_auth_provider.py @@ -0,0 +1,326 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, + utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import ldap_auth_provider + +from .common.utils import ( + AnsibleExitJson, + AnsibleFailJson, + ModuleTestCase, + set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestDoDiffer: + def test_no_changes(self): + desired = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ), + metadata=dict(name="openldap"), + ) + current = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ), + metadata=dict( + name="openldap", + created_by="me", + ), + ) + + assert ldap_auth_provider.do_differ(current, desired) is False + + def test_changes_are_detected(self): + desired = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + port=636, + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ), + metadata=dict(name="openldap"), + ) + current = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ), + metadata=dict( + name="openldap", + created_by="me", + ), + ) + assert ldap_auth_provider.do_differ(current, desired) is True + + def test_changes_are_detected_diff_servers_len(self): + desired = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ), + dict( + host="127.0.0.2", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ), + ], + ), + metadata=dict(name="openldap"), + ) + current = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ), + metadata=dict( + name="openldap", + created_by="me", + ), + ) + assert ldap_auth_provider.do_differ(current, desired) is True + + def test_changes_are_other_params(self): + desired = dict( + spec=dict( + servers=[], + groups_prefix="ldap", + username_prefix="ldap", + ), + metadata=dict(name="openldap"), + ) + current = dict( + spec=dict( + servers=[], + ), + metadata=dict( + name="openldap", + created_by="me", + ), + ) + assert ldap_auth_provider.do_differ(current, desired) is True + + +class TestLDAPAutProvider(ModuleTestCase): + def test_minimal_provider_parameters(self, mocker): + sync_v1_mock = mocker.patch.object(utils, "sync_v1") + sync_v1_mock.return_value = True, {} + set_module_args( + state="present", + name="openldap", + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ) + + with pytest.raises(AnsibleExitJson): + ldap_auth_provider.main() + + state, _client, path, payload, check_mode, _do_differ = sync_v1_mock.call_args[ + 0 + ] + + assert state == "present" + assert path == "/api/enterprise/authentication/v2/authproviders/openldap" + assert payload == dict( + type="ldap", + api_version="authentication/v2", + metadata=dict(name="openldap"), + spec=dict( + servers=[ + dict( + host="127.0.0.1", + port=None, + insecure=False, + security="tls", + trusted_ca_file=None, + client_cert_file=None, + client_key_file=None, + binding=None, + group_search=dict( + base_dn="dc=acme,dc=org", + attribute="member", + name_attribute="cn", + object_class="groupOfNames", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + attribute="uid", + name_attribute="cn", + object_class="person", + ), + ) + ] + ), + ) + + assert check_mode is False + + def test_all_provider_parameters(self, mocker): + sync_v1_mock = mocker.patch.object(utils, "sync_v1") + sync_v1_mock.return_value = True, {} + set_module_args( + state="present", + name="openldap", + servers=[ + dict( + host="127.0.0.1", + port=636, + insecure=False, + security="tls", + trusted_ca_file="/path/to/trusted-certificate-authorities.pem", + client_cert_file="/path/to/ssl/cert.pem", + client_key_file="/path/to/ssl/key.pem", + binding=dict( + user_dn="cn=binder,dc=acme,dc=org", + password="YOUR_PASSWORD", + ), + group_search=dict( + base_dn="dc=acme,dc=org", + attribute="member", + name_attribute="cn", + object_class="groupOfNames", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + attribute="uid", + name_attribute="cn", + object_class="person", + ), + ) + ], + groups_prefix="ldap", + username_prefix="ldap", + ) + + with pytest.raises(AnsibleExitJson): + ldap_auth_provider.main() + + state, _client, path, payload, check_mode, _do_differ = sync_v1_mock.call_args[ + 0 + ] + assert state == "present" + assert path == "/api/enterprise/authentication/v2/authproviders/openldap" + assert payload == dict( + type="ldap", + api_version="authentication/v2", + metadata=dict(name="openldap"), + spec=dict( + servers=[ + dict( + host="127.0.0.1", + port=636, + insecure=False, + security="tls", + trusted_ca_file="/path/to/trusted-certificate-authorities.pem", + client_cert_file="/path/to/ssl/cert.pem", + client_key_file="/path/to/ssl/key.pem", + binding=dict( + user_dn="cn=binder,dc=acme,dc=org", + password="YOUR_PASSWORD", + ), + group_search=dict( + base_dn="dc=acme,dc=org", + attribute="member", + name_attribute="cn", + object_class="groupOfNames", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + attribute="uid", + name_attribute="cn", + object_class="person", + ), + ) + ], + groups_prefix="ldap", + username_prefix="ldap", + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, "sync_v1") + sync_mock.side_effect = errors.Error("Bad error") + set_module_args() + + with pytest.raises(AnsibleFailJson): + ldap_auth_provider.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_mutator.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_mutator.py new file mode 100644 index 00000000..9cef32e7 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_mutator.py @@ -0,0 +1,126 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import mutator + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestDoDiffer: + @pytest.mark.parametrize("current,desired", [ + ( # No diff in params, no secrets + dict(name="demo"), + dict(name="demo"), + ), + ( # No diff in params, no diff in secrets + dict(name="demo", secrets=[ + dict(name="n1", secret="s1"), dict(name="n2", secret="s2"), + ]), + dict(name="demo", secrets=[ + dict(name="n2", secret="s2"), dict(name="n1", secret="s1"), + ]), + ), + ]) + def test_no_difference(self, current, desired): + assert mutator.do_differ(current, desired) is False + + @pytest.mark.parametrize("current,desired", [ + ( # Diff in params, no diff in secrets + dict(name="demo", secrets=[dict(name="a", secret="1")]), + dict(name="prod", secrets=[dict(name="a", secret="1")]), + ), + ( # No diff in params, missing and set secrets + dict(name="demo", secrets=[dict(name="a", secret="1")]), + dict(name="demo", secrets=[dict(name="b", secret="2")]), + ), + ( # Diff in params, missing and set secrets + dict(name="demo", secrets=[dict(name="a", secret="1")]), + dict(name="prod", secrets=[dict(name="b", secret="2")]), + ), + ]) + def test_difference(self, current, desired): + assert mutator.do_differ(current, desired) is True + + +class TestMutator(ModuleTestCase): + def test_minimal_mutator_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_mutator', + command='/bin/true', + ) + + with pytest.raises(AnsibleExitJson): + mutator.main() + + state, _client, path, payload, check_mode, _d = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/mutators/test_mutator' + assert payload == dict( + command='/bin/true', + metadata=dict( + name='test_mutator', + namespace='default', + ), + ) + assert check_mode is False + + def test_all_mutator_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_mutator', + namespace='my', + state='absent', + command='/bin/true', + timeout=30, + runtime_assets='awesomeness', + labels={'region': 'us-west-1'}, + annotations={'playbook': 12345}, + secrets=[dict(name="a", secret="b")], + ) + + with pytest.raises(AnsibleExitJson): + mutator.main() + + state, _client, path, payload, check_mode, _d = sync_mock.call_args[0] + assert state == 'absent' + assert path == '/api/core/v2/namespaces/my/mutators/test_mutator' + assert payload == dict( + command='/bin/true', + timeout=30, + runtime_assets=['awesomeness'], + metadata=dict( + name='test_mutator', + namespace='my', + labels={'region': 'us-west-1'}, + annotations={'playbook': '12345'}, + ), + secrets=[dict(name="a", secret="b")], + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = errors.Error('Bad error') + set_module_args( + name='test_mutator', + command='/bion/true' + ) + + with pytest.raises(AnsibleFailJson): + mutator.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_mutator_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_mutator_info.py new file mode 100644 index 00000000..b1456c8f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_mutator_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import mutator_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestMutatorInfo(ModuleTestCase): + def test_get_all_mutators(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + mutator_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/my/mutators" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_mutator(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 4 + set_module_args(name="sample-mutator") + + with pytest.raises(AnsibleExitJson) as context: + mutator_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/default/mutators/sample-mutator" + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_mutator(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-mutator") + + with pytest.raises(AnsibleExitJson) as context: + mutator_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-mutator") + + with pytest.raises(AnsibleFailJson): + mutator_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_namespace.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_namespace.py new file mode 100644 index 00000000..e51a63af --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_namespace.py @@ -0,0 +1,49 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import namespace + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestNamespace(ModuleTestCase): + def test_namespace(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='dev' + ) + + with pytest.raises(AnsibleExitJson): + namespace.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/dev' + assert payload == dict( + name='dev' + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = errors.Error('Bad error') + set_module_args( + name='dev', + ) + + with pytest.raises(AnsibleFailJson): + namespace.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_namespace_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_namespace_info.py new file mode 100644 index 00000000..431b6463 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_namespace_info.py @@ -0,0 +1,41 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import namespace_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestNamespaceInfo(ModuleTestCase): + def test_get_namespaces(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args() + + with pytest.raises(AnsibleExitJson) as context: + namespace_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args() + + with pytest.raises(AnsibleFailJson): + namespace_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_oidc_auth_provider.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_oidc_auth_provider.py new file mode 100644 index 00000000..0780cdc2 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_oidc_auth_provider.py @@ -0,0 +1,118 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, + utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import oidc_auth_provider + +from .common.utils import ( + AnsibleExitJson, + AnsibleFailJson, + ModuleTestCase, + set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestADAutProvider(ModuleTestCase): + def test_minimal_provider_parameters(self, mocker): + sync_v1_mock = mocker.patch.object(utils, "sync_v1") + sync_v1_mock.return_value = True, {} + set_module_args( + state="present", + name="oidc_name", + additional_scopes=["openid"], + client_id="a8e43af034e7f2608780", + client_secret="b63968394be6ed2edb61c93847ee792f31bf6216", + disable_offline_access=False, + server="https://oidc.example.com:9031", + username_claim="email", + ) + + with pytest.raises(AnsibleExitJson): + oidc_auth_provider.main() + + state, _client, path, payload, check_mode = sync_v1_mock.call_args[ + 0 + ] + + assert state == "present" + assert path == "/api/enterprise/authentication/v2/authproviders/oidc_name" + assert payload == dict( + type="oidc", + api_version="authentication/v2", + metadata=dict(name="oidc_name"), + spec=dict( + additional_scopes=["openid"], + client_id="a8e43af034e7f2608780", + client_secret="b63968394be6ed2edb61c93847ee792f31bf6216", + disable_offline_access=False, + server="https://oidc.example.com:9031", + username_claim="email", + ), + ) + + assert check_mode is False + + def test_all_provider_parameters(self, mocker): + sync_v1_mock = mocker.patch.object(utils, "sync_v1") + sync_v1_mock.return_value = True, {} + set_module_args( + state="present", + name="oidc_name", + additional_scopes=["groups", "email", "username"], + client_id="a8e43af034e7f2608780", + client_secret="b63968394be6ed2edb61c93847ee792f31bf6216", + disable_offline_access=False, + redirect_uri="http://127.0.0.1:8080/api/enterprise/authentication/v2/oidc/callback", + server="https://oidc.example.com:9031", + groups_claim="groups", + groups_prefix="oidc:", + username_claim="email", + username_prefix="oidc:", + ) + + with pytest.raises(AnsibleExitJson): + oidc_auth_provider.main() + + state, _client, path, payload, check_mode = sync_v1_mock.call_args[ + 0 + ] + assert state == "present" + assert path == "/api/enterprise/authentication/v2/authproviders/oidc_name" + assert payload == dict( + type="oidc", + api_version="authentication/v2", + metadata=dict(name="oidc_name"), + spec=dict( + additional_scopes=["groups", "email", "username"], + client_id="a8e43af034e7f2608780", + client_secret="b63968394be6ed2edb61c93847ee792f31bf6216", + disable_offline_access=False, + redirect_uri="http://127.0.0.1:8080/api/enterprise/authentication/v2/oidc/callback", + server="https://oidc.example.com:9031", + groups_claim="groups", + groups_prefix="oidc:", + username_claim="email", + username_prefix="oidc:", + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, "sync_v1") + sync_mock.side_effect = errors.Error("Bad error") + set_module_args() + + with pytest.raises(AnsibleFailJson): + oidc_auth_provider.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_pipe_handler.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_pipe_handler.py new file mode 100644 index 00000000..69c3254a --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_pipe_handler.py @@ -0,0 +1,136 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import pipe_handler + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestDoDiffer: + @pytest.mark.parametrize("current,desired", [ + ( # No diff in params, no secrets + dict(name="demo"), + dict(name="demo"), + ), + ( # No diff in params, no diff in secrets + dict(name="demo", secrets=[ + dict(name="n1", secret="s1"), dict(name="n2", secret="s2"), + ]), + dict(name="demo", secrets=[ + dict(name="n2", secret="s2"), dict(name="n1", secret="s1"), + ]), + ), + ]) + def test_no_difference(self, current, desired): + assert pipe_handler.do_differ(current, desired) is False + + @pytest.mark.parametrize("current,desired", [ + ( # Diff in params, no diff in secrets + dict(name="demo", secrets=[dict(name="a", secret="1")]), + dict(name="prod", secrets=[dict(name="a", secret="1")]), + ), + ( # No diff in params, missing and set secrets + dict(name="demo", secrets=[dict(name="a", secret="1")]), + dict(name="demo", secrets=[dict(name="b", secret="2")]), + ), + ( # Diff in params, missing and set secrets + dict(name="demo", secrets=[dict(name="a", secret="1")]), + dict(name="prod", secrets=[dict(name="b", secret="2")]), + ), + ]) + def test_difference(self, current, desired): + assert pipe_handler.do_differ(current, desired) is True + + +class TestPipeHandler(ModuleTestCase): + def test_minimal_pipe_handler_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.return_value = True, {} + set_module_args( + name="test_handler", + command='echo "test"' + ) + + with pytest.raises(AnsibleExitJson): + pipe_handler.main() + + state, _client, path, payload, check_mode, _d = sync_mock.call_args[0] + assert state == "present" + assert path == "/api/core/v2/namespaces/default/handlers/test_handler" + assert payload == dict( + command='echo "test"', + type='pipe', + metadata=dict( + name="test_handler", + namespace="default", + ), + ) + assert check_mode is False + + def test_all_pipe_handler_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.return_value = True, {} + set_module_args( + name='test_handler', + namespace='my', + state='absent', + command='/bin/true', + filters=['occurrences', 'production'], + mutator='only_check_output', + timeout=30, + env_vars=dict(foo='bar'), + runtime_assets='awesomeness', + secrets=[dict(name="a", secret="b")], + ) + + with pytest.raises(AnsibleExitJson): + pipe_handler.main() + + state, _client, path, payload, check_mode, _d = sync_mock.call_args[0] + assert state == "absent" + assert path == "/api/core/v2/namespaces/my/handlers/test_handler" + assert payload == dict( + command='/bin/true', + type='pipe', + filters=['occurrences', 'production'], + mutator='only_check_output', + timeout=30, + env_vars=['foo=bar'], + runtime_assets=['awesomeness'], + metadata=dict( + name="test_handler", + namespace="my", + ), + secrets=[dict(name="a", secret="b")], + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.side_effect = errors.Error("Bad error") + set_module_args( + name='test_handler', + state='absent', + command='/bin/true', + filters=['occurrences', 'production'], + mutator='only_check_output', + timeout=30, + env_vars=dict(foo='bar'), + runtime_assets='awesomeness' + ) + + with pytest.raises(AnsibleFailJson): + pipe_handler.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_pipe_handler_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_pipe_handler_info.py new file mode 100644 index 00000000..2339e855 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_pipe_handler_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import handler_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestHandlerInfo(ModuleTestCase): + def test_get_all_handlers(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + handler_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/my/handlers" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_handler(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 4 + set_module_args(name="sample-handler") + + with pytest.raises(AnsibleExitJson) as context: + handler_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/default/handlers/sample-handler" + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_handler(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-handler") + + with pytest.raises(AnsibleExitJson) as context: + handler_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-handler") + + with pytest.raises(AnsibleFailJson): + handler_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role.py new file mode 100644 index 00000000..cd1ce4fb --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role.py @@ -0,0 +1,142 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import role + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestRole(ModuleTestCase): + def test_minimal_role_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_role', + rules=[ + dict( + verbs=[], + resources=[] + ), + ] + ) + + with pytest.raises(AnsibleExitJson): + role.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/roles/test_role' + assert payload == dict( + rules=[ + dict( + verbs=[], + resources=[], + resource_names=None, + ) + ], + metadata=dict( + name='test_role', + namespace='default', + ), + ) + assert check_mode is False + + def test_all_role_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_role', + namespace='my', + state='present', + rules=[ + dict( + verbs=['get', 'list', 'create'], + resources=['assets', 'entities'], + ), + dict( + verbs=['list'], + resources=['check'], + resource_names=['my-check'], + ) + ], + ) + + with pytest.raises(AnsibleExitJson): + role.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/my/roles/test_role' + assert payload == dict( + metadata=dict( + name='test_role', + namespace='my', + ), + rules=[ + dict( + verbs=['get', 'list', 'create'], + resources=['assets', 'entities'], + resource_names=None, + ), + dict( + verbs=['list'], + resources=['check'], + resource_names=['my-check'], + ) + ], + ) + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = errors.Error("Bad error") + set_module_args( + name='test_role', + rules=[ + dict( + verbs=[], + resources=[], + ) + ], + ) + with pytest.raises(AnsibleFailJson): + role.main() + + def test_failure_invalid_verb(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = Exception("Validation should fail but didn't") + set_module_args( + name='test_role', + rules=[ + dict( + verbs=['list', 'invalid'], + resources=[], + ), + ] + ) + + with pytest.raises(AnsibleFailJson): + role.main() + + def test_failure_empty_rules(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = Exception("Validation should fail but didn't") + set_module_args( + name='test_role', + rules=[] + ) + + with pytest.raises(AnsibleFailJson): + role.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role_binding.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role_binding.py new file mode 100644 index 00000000..b22eaa7e --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role_binding.py @@ -0,0 +1,214 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import role_binding + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestRoleBinding(ModuleTestCase): + def test_minimal_role_binding_parameters_users(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_role_binding', + role='test_role', + users=['test_user'], + ) + + with pytest.raises(AnsibleExitJson): + role_binding.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/rolebindings/test_role_binding' + assert payload == dict( + role_ref=dict( + name='test_role', + type='Role', + ), + subjects=[ + dict( + name='test_user', + type='User', + ), + ], + metadata=dict( + name='test_role_binding', + namespace='default', + ), + ) + assert check_mode is False + + def test_minimal_role_binding_parameters_groups(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_role_binding', + role='test_role', + groups=['test_group'], + ) + + with pytest.raises(AnsibleExitJson): + role_binding.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/rolebindings/test_role_binding' + assert payload == dict( + role_ref=dict( + name='test_role', + type='Role', + ), + subjects=[ + dict( + name='test_group', + type='Group', + ), + ], + metadata=dict( + name='test_role_binding', + namespace='default', + ), + ) + assert check_mode is False + + def test_all_role_binding_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_role_binding', + namespace='my', + role='test_role', + users=['user_1', 'user_2'], + groups=['group_1', 'group_2'], + ) + + with pytest.raises(AnsibleExitJson): + role_binding.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/my/rolebindings/test_role_binding' + assert payload == dict( + metadata=dict( + name='test_role_binding', + namespace='my', + ), + role_ref=dict( + name='test_role', + type='Role', + ), + subjects=[ + dict( + name='group_1', + type='Group', + ), + dict( + name='group_2', + type='Group', + ), + dict( + name='user_1', + type='User', + ), + dict( + name='user_2', + type='User', + ), + ] + ) + assert check_mode is False + + def test_role_binding_with_cluster_role(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_role_binding', + cluster_role='test_cluster_role', + users=['test_user'], + ) + + with pytest.raises(AnsibleExitJson): + role_binding.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/rolebindings/test_role_binding' + assert payload == dict( + role_ref=dict( + name='test_cluster_role', + type='ClusterRole', + ), + subjects=[ + dict( + name='test_user', + type='User', + ), + ], + metadata=dict( + name='test_role_binding', + namespace='default', + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = errors.Error('Bad error') + set_module_args( + name='test_role_binding', + role='test_role', + users=['test_user'], + ) + with pytest.raises(AnsibleFailJson): + role_binding.main() + + def test_failure_role_and_cluster_role(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = Exception("Validation should fail but didn't") + set_module_args( + name='test_role_binding', + role='test_role', + cluster_role='test_cluster_role', + ) + + with pytest.raises(AnsibleFailJson): + role_binding.main() + + def test_failure_missing_groups_or_users(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = Exception("Validation should fail but didn't") + set_module_args( + name='test_role_binding', + role='test_role', + ) + + with pytest.raises(AnsibleFailJson): + role_binding.main() + + @pytest.mark.parametrize("params,result", [ + ( + dict(role="test-role", cluster_role=None), + ("Role", "test-role"), + ), + ( + dict(cluster_role="test-cluster-role", role=None), + ("ClusterRole", "test-cluster-role"), + ), + ]) + def test_infer_role(self, params, result): + assert result == role_binding.infer_role(params) diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role_binding_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role_binding_info.py new file mode 100644 index 00000000..2f772516 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role_binding_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import role_binding_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestRoleBindingInfo(ModuleTestCase): + def test_get_all_role_bindings(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + role_binding_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/my/rolebindings" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_role_binding(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 1 + set_module_args(name="test-role-binding") + + with pytest.raises(AnsibleExitJson) as context: + role_binding_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/default/rolebindings/test-role-binding" + assert context.value.args[0]["objects"] == [1] + + def test_missing_single_role_binding(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-role-binding") + + with pytest.raises(AnsibleExitJson) as context: + role_binding_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-role-binding") + + with pytest.raises(AnsibleFailJson): + role_binding_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role_info.py new file mode 100644 index 00000000..a2bec291 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import role_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestRoleInfo(ModuleTestCase): + def test_get_all_roles(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + role_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/my/roles" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_role(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 1 + set_module_args(name="test-role") + + with pytest.raises(AnsibleExitJson) as context: + role_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/default/roles/test-role" + assert context.value.args[0]["objects"] == [1] + + def test_missing_single_role(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-role") + + with pytest.raises(AnsibleExitJson) as context: + role_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-role") + + with pytest.raises(AnsibleFailJson): + role_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secret.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secret.py new file mode 100644 index 00000000..5912ae73 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secret.py @@ -0,0 +1,85 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import secret + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSecret(ModuleTestCase): + @pytest.mark.parametrize("params", [ + {"name": "demo", "provider": "env", "id": "MY_VAR"}, + {"name": "demo", "state": "absent"}, + ]) + def test_minimal_parameters(self, mocker, params): + mocker.patch.object(utils, "sync_v1").return_value = True, {} + set_module_args(**params) + + with pytest.raises(AnsibleExitJson): + secret.main() + + def test_all_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync_v1") + sync_mock.return_value = True, {} + set_module_args( + auth=dict( + user="user", + password="pass", + url="http://127.0.0.1:1234", + api_key="123-key", + verify=False, + ca_path="/tmp/ca.bundle", + ), + state="present", + name="demo", + namespace="ns", + provider="env", + id="MY_ENV_VAR", + ) + + with pytest.raises(AnsibleExitJson): + secret.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == "present" + assert path == "/api/enterprise/secrets/v1/namespaces/ns/secrets/demo" + assert payload == dict( + type="Secret", + api_version="secrets/v1", + metadata=dict(name="demo", namespace="ns"), + spec=dict(provider="env", id="MY_ENV_VAR"), + ) + assert check_mode is False + + @pytest.mark.parametrize("skip", ["provider", "id"]) + def test_missing_required_param_present(self, mocker, skip): + sync_mock = mocker.patch.object(utils, "sync_v1") + all_args = dict(name="demo", provider="env", id="X") + set_module_args(**dict((k, v) for k, v in all_args.items() if k != skip)) + + with pytest.raises(AnsibleFailJson): + secret.main() + + sync_mock.assert_not_called() + + def test_failure(self, mocker): + mocker.patch.object(utils, "sync_v1").side_effect = ( + errors.Error("Bad error") + ) + set_module_args(name="demo", state="absent") + + with pytest.raises(AnsibleFailJson): + secret.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secret_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secret_info.py new file mode 100644 index 00000000..161204f2 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secret_info.py @@ -0,0 +1,86 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import secret_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSecretInfo(ModuleTestCase): + def test_all_parameters(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = {"spec": {"k1": "v1"}} + set_module_args( + auth=dict( + user="user", + password="pass", + url="http://127.0.0.1:1234", + api_key="123-key", + verify=False, + ca_path="/tmp/ca.bundle", + ), + name="demo", + namespace="ns", + ) + + with pytest.raises(AnsibleExitJson): + secret_info.main() + + def test_get_all_secrets(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [ + {"spec": {"k1": "v1"}}, {"spec": {"k2": "v2"}}, + ] + set_module_args() + + with pytest.raises(AnsibleExitJson) as context: + secret_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/secrets/v1/namespaces/default/secrets" + assert context.value.args[0]["objects"] == [ + {"k1": "v1"}, {"k2": "v2"}, + ] + + def test_get_single_secret(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = {"spec": {"k3": "v3"}} + set_module_args(name="demo", namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + secret_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/secrets/v1/namespaces/my/secrets/demo" + assert context.value.args[0]["objects"] == [{"k3": "v3"}] + + def test_missing_single_secret(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="demo") + + with pytest.raises(AnsibleExitJson) as context: + secret_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="demo") + + with pytest.raises(AnsibleFailJson): + secret_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secrets_provider_env.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secrets_provider_env.py new file mode 100644 index 00000000..cce081bf --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secrets_provider_env.py @@ -0,0 +1,69 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import secrets_provider_env + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSecretsProviderEnv(ModuleTestCase): + def test_no_provider_parameters(self, mocker): + sync_v1_mock = mocker.patch.object(utils, 'sync_v1') + sync_v1_mock.return_value = True, {} + set_module_args() + + with pytest.raises(AnsibleExitJson): + secrets_provider_env.main() + + state, _client, path, payload, check_mode = sync_v1_mock.call_args[0] + assert state == 'present' + assert path == '/api/enterprise/secrets/v1/providers/env' + assert payload == dict( + type='Env', + api_version="secrets/v1", + metadata=dict(name='env'), + spec={} + ) + assert check_mode is False + + def test_all_provider_parameters(self, mocker): + sync_v1_mock = mocker.patch.object(utils, 'sync_v1') + sync_v1_mock.return_value = True, {} + set_module_args( + state='present', + ) + + with pytest.raises(AnsibleExitJson): + secrets_provider_env.main() + + state, _client, path, payload, check_mode = sync_v1_mock.call_args[0] + assert state == 'present' + assert path == '/api/enterprise/secrets/v1/providers/env' + assert payload == dict( + type='Env', + api_version="secrets/v1", + metadata=dict(name='env'), + spec={} + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_v1_mock = mocker.patch.object(utils, 'sync_v1') + sync_v1_mock.side_effect = errors.Error("Bad error") + set_module_args() + + with pytest.raises(AnsibleFailJson): + secrets_provider_env.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secrets_provider_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secrets_provider_info.py new file mode 100644 index 00000000..69515249 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secrets_provider_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import secrets_provider_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSecretsProviderInfo(ModuleTestCase): + def test_get_all_secrets_providers(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [dict(spec=1), dict(spec=2)] + set_module_args() + + with pytest.raises(AnsibleExitJson) as context: + secrets_provider_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/secrets/v1/providers" + assert context.value.args[0]["objects"] == [1, 2] + + def test_get_single_secrets_provider(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = dict(spec=4) + set_module_args(name="sample-secrets-provider") + + with pytest.raises(AnsibleExitJson) as context: + secrets_provider_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/secrets/v1/providers/sample-secrets-provider" + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_secrets_provider(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-secrets-provider") + + with pytest.raises(AnsibleExitJson) as context: + secrets_provider_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-secrets-provider") + + with pytest.raises(AnsibleFailJson): + secrets_provider_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secrets_provider_vault.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secrets_provider_vault.py new file mode 100644 index 00000000..fc4c17a8 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secrets_provider_vault.py @@ -0,0 +1,182 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import secrets_provider_vault + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestDoDiffer: + def test_fields_are_ignored(self): + desired = dict( + spec=dict( + client=dict( + address="https://my-vault.com", + tls=dict( + ca_cert="cert" + ), + ), + ), + metadata=dict( + name="my-vault" + ) + ) + current = dict( + spec=dict( + client=dict( + address="https://my-vault.com", + agent_address="", # extra field + tls=dict( + ca_cert="cert", + # extra fields + insecure=False, + ca_path="path", + tls_server_name="server", + ) + ), + ), + metadata=dict( + name="my-vault", + created_by="me", + ) + ) + + assert secrets_provider_vault.do_differ(current, desired) is False + + def test_changes_are_detected(self): + desired = dict( + spec=dict( + client=dict( + address="https://my-vault.com", + tls=dict( + ca_cert="cert" + ), + ), + ), + metadata=dict( + name="my-vault" + ) + ) + current = dict( + spec=dict( + client=dict( + address="https://my-vault.com", + tls=dict( + ca_cert="new-cert", + cname="server", + ), + timeout='15s', + ), + ), + metadata=dict( + name="my-vault", + ) + ) + assert secrets_provider_vault.do_differ(current, desired) is True + + +class TestSecretsProviderVault(ModuleTestCase): + def test_minimal_provider_parameters(self, mocker): + sync_v1_mock = mocker.patch.object(utils, 'sync_v1') + sync_v1_mock.return_value = True, {} + set_module_args( + name='my-vault', + state='present', + address='https://my-vault.com', + token='AUTH_TOKEN', + version='v1', + ) + + with pytest.raises(AnsibleExitJson): + secrets_provider_vault.main() + + state, _client, path, payload, check_mode, _do_differ = sync_v1_mock.call_args[0] + assert state == 'present' + assert path == '/api/enterprise/secrets/v1/providers/my-vault' + assert payload == dict( + type='VaultProvider', + api_version="secrets/v1", + metadata=dict(name='my-vault'), + spec=dict( + client=dict( + address='https://my-vault.com', + token='AUTH_TOKEN', + version='v1', + ), + ), + ) + assert check_mode is False + + def test_all_provider_parameters(self, mocker): + sync_v1_mock = mocker.patch.object(utils, 'sync_v1') + sync_v1_mock.return_value = True, {} + set_module_args( + name='my-vault', + state='present', + address='https://my-vault.com', + token='AUTH_TOKEN', + version='v1', + tls=dict( + ca_cert='/etc/ssl/ca.crt', + client_cert='/etc/ssl/client.crt', + client_key='/etc/ssl/client.key', + cname='my-vault.com', + ), + timeout=1, + max_retries=2, + rate_limit=3, + burst_limit=4, + ) + + with pytest.raises(AnsibleExitJson): + secrets_provider_vault.main() + + state, _client, path, payload, check_mode, _do_differ = sync_v1_mock.call_args[0] + assert state == 'present' + assert path == '/api/enterprise/secrets/v1/providers/my-vault' + assert payload == dict( + type='VaultProvider', + api_version="secrets/v1", + metadata=dict(name='my-vault'), + spec=dict( + client=dict( + address='https://my-vault.com', + token='AUTH_TOKEN', + version='v1', + tls=dict( + ca_cert='/etc/ssl/ca.crt', + client_cert='/etc/ssl/client.crt', + client_key='/etc/ssl/client.key', + cname='my-vault.com', + ), + timeout="1s", + max_retries=2, + rate_limiter=dict( + limit=3, + burst=4, + ), + ), + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync_v1') + sync_mock.side_effect = errors.Error("Bad error") + set_module_args() + + with pytest.raises(AnsibleFailJson): + secrets_provider_vault.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_silence.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_silence.py new file mode 100644 index 00000000..ad064271 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_silence.py @@ -0,0 +1,120 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import silence + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSilence(ModuleTestCase): + def test_minimal_silence_parameters_check(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + check='check' + ) + + with pytest.raises(AnsibleExitJson): + silence.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/silenced/%2A%3Acheck' # %2A = *, %3A = : + assert payload == dict( + check='check', + metadata=dict( + name='*:check', + namespace='default', + ), + ) + assert check_mode is False + + def test_minimal_silence_parameters_subscription(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + subscription='subscription' + ) + + with pytest.raises(AnsibleExitJson): + silence.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/silenced/subscription%3A%2A' # %3A = :, %2A = * + assert payload == dict( + subscription='subscription', + metadata=dict( + name='subscription:*', + namespace='default', + ), + ) + assert check_mode is False + + def test_all_silence_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + namespace='my', + subscription='entity:test-entity', + check='check', + state='absent', + begin=1542671205, + expire=1542771205, + expire_on_resolve=True, + reason='because', + labels={'region': 'us-west-1'}, + annotations={'playbook': 12345}, + ) + + with pytest.raises(AnsibleExitJson): + silence.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == 'absent' + assert path == '/api/core/v2/namespaces/my/silenced/entity%3Atest-entity%3Acheck' # %3A = : + assert payload == dict( + subscription='entity:test-entity', + check='check', + begin=1542671205, + expire=1542771205, + expire_on_resolve=True, + reason='because', + metadata=dict( + name='entity:test-entity:check', + namespace='my', + labels={'region': 'us-west-1'}, + annotations={'playbook': '12345'}, + ), + ) + assert check_mode is False + + def test_failure_when_both_params_are_missing(self): + set_module_args() + + with pytest.raises(AnsibleFailJson, + match='one of the following is required: subscription, check'): + silence.main() + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = errors.Error('Bad error') + set_module_args( + subscription='subscription' + ) + + with pytest.raises(AnsibleFailJson): + silence.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_silence_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_silence_info.py new file mode 100644 index 00000000..7a221541 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_silence_info.py @@ -0,0 +1,66 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import silence_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSilenceInfo(ModuleTestCase): + def test_get_all_silences(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + silence_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/my/silenced" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_silence(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 4 + set_module_args(subscription="subscription") + + with pytest.raises(AnsibleExitJson) as context: + silence_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/default/silenced/subscription%3A%2A" # %3A = :, %2A = * + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_silence(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args( + subscription="missing", + check="missing", + ) + + with pytest.raises(AnsibleExitJson) as context: + silence_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(check="check") + + with pytest.raises(AnsibleFailJson): + silence_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_socket_handler.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_socket_handler.py new file mode 100644 index 00000000..b4c72f19 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_socket_handler.py @@ -0,0 +1,101 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import socket_handler + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSocketHandler(ModuleTestCase): + def test_minimal_socket_handler_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.return_value = True, {} + set_module_args( + name="test_handler", + type='tcp', + host='10.0.1.99', + port=4444 + ) + + with pytest.raises(AnsibleExitJson): + socket_handler.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == "present" + assert path == "/api/core/v2/namespaces/default/handlers/test_handler" + assert payload == dict( + type='tcp', + socket=dict( + host='10.0.1.99', + port=4444 + ), + metadata=dict( + name="test_handler", + namespace="default", + ), + ) + assert check_mode is False + + def test_all_socket_handler_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.return_value = True, {} + set_module_args( + name='test_handler', + namespace='my', + state='absent', + type='udp', + filters=['occurrences', 'production'], + mutator='only_check_output', + timeout=30, + host='10.0.1.99', + port=4444 + ) + + with pytest.raises(AnsibleExitJson): + socket_handler.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == "absent" + assert path == "/api/core/v2/namespaces/my/handlers/test_handler" + assert payload == dict( + type='udp', + filters=['occurrences', 'production'], + mutator='only_check_output', + timeout=30, + socket=dict( + host='10.0.1.99', + port=4444 + ), + metadata=dict( + name="test_handler", + namespace="my", + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.side_effect = errors.Error("Bad error") + set_module_args( + name='test_handler', + state='absent', + type='udp', + host='10.0.1.99', + port=4444 + ) + + with pytest.raises(AnsibleFailJson): + socket_handler.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_tessen.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_tessen.py new file mode 100644 index 00000000..3eb2796b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_tessen.py @@ -0,0 +1,105 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, http, +) +from ansible_collections.sensu.sensu_go.plugins.modules import tessen + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSync: + def test_remote_and_desired_equal(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{}') + changed, object = tessen.sync(client, "/path", {}, False) + + assert changed is False + assert object == {} + + def test_remote_and_desired_not_equal(self, mocker): + client = mocker.Mock() + client.get.side_effect = ( + http.Response(200, '{"opt_out": "false"}'), + http.Response(200, '{"opt_out": "true"}'), + ) + client.put.return_value = http.Response(200, "") + changed, object = tessen.sync(client, "/path", {'opt_out': True}, False) + + assert changed is True + assert object == {'opt_out': 'true'} + client.put.assert_called_once_with("/path", {'opt_out': True}) + + def test_remote_and_desired_equal_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{}') + changed, object = tessen.sync(client, "/path", {}, True) + + assert changed is False + assert object == {} + + def test_remote_and_desired_not_equal_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"opt_out": "false"}') + changed, object = tessen.sync(client, "/path", {'opt_out': True}, True) + + assert changed is True + assert object == {'opt_out': True} + client.put.assert_not_called() + + +class TestTessen(ModuleTestCase): + def test_enabled(self, mocker): + sync_mock = mocker.patch.object(tessen, 'sync') + sync_mock.return_value = True, {} + set_module_args( + state='enabled' + ) + + with pytest.raises(AnsibleExitJson): + tessen.main() + + _client, path, payload, check_mode = sync_mock.call_args[0] + assert path == '/api/core/v2/tessen' + assert payload == dict( + opt_out=False + ) + assert check_mode is False + + def test_disabled(self, mocker): + sync_mock = mocker.patch.object(tessen, 'sync') + sync_mock.return_value = True, {} + set_module_args( + state='disabled' + ) + + with pytest.raises(AnsibleExitJson): + tessen.main() + + _client, path, payload, check_mode = sync_mock.call_args[0] + assert path == '/api/core/v2/tessen' + assert payload == dict( + opt_out=True + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(tessen, 'sync') + sync_mock.side_effect = errors.Error('Bad error') + set_module_args( + state='enabled' + ) + + with pytest.raises(AnsibleFailJson): + tessen.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_user.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_user.py new file mode 100644 index 00000000..52e4a769 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_user.py @@ -0,0 +1,519 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +from distutils import version + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + arguments, errors, http, utils +) +from ansible_collections.sensu.sensu_go.plugins.modules import user + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestUpdatePassword: + @pytest.mark.parametrize('check', [False, True]) + def test_password_is_valid(self, mocker, check): + client = mocker.Mock() + client.validate_auth_data.return_value = True + + changed = user.update_password(client, '/path', 'user', 'pass', check) + + assert changed is False + client.validate_auth_data.assert_called_once_with('user', 'pass') + client.put.assert_not_called() + + def test_password_is_invalid_older_than_5_21_0(self, mocker): + client = mocker.Mock() + client.validate_auth_data.return_value = False + client.version = version.StrictVersion("5.20.2") + client.put.return_value = http.Response(201, '') + + changed = user.update_password(client, '/path', 'user', 'pass', False) + + assert changed is True + client.validate_auth_data.assert_called_once_with('user', 'pass') + client.put.assert_called_once_with('/path/password', dict( + username='user', password='pass', + )) + + def test_password_is_invalid_5_21_0_or_newer(self, mocker): + client = mocker.Mock() + client.validate_auth_data.return_value = False + client.version = version.StrictVersion("5.21.0") + client.put.return_value = http.Response(201, '') + + changed = user.update_password(client, '/path', 'user', 'pass', False) + + assert changed is True + client.validate_auth_data.assert_called_once_with('user', 'pass') + client.put.assert_called_once() + + path, payload = client.put.call_args[0] + assert path == '/path/reset_password' + assert payload['username'] == 'user' + + # (tadeboro): We cannot validate the value without mocking the bcrypt. + # And I would rather see that our code gets tested by actually using + # the bcrypt rather than mocking it out. This way, the message + # encode/decode stuff gets put through its paces. + assert 'password_hash' in payload + + def test_password_is_invalid_check_mode(self, mocker): + client = mocker.Mock() + client.validate_auth_data.return_value = False + + changed = user.update_password(client, '/path', 'user', 'pass', True) + + assert changed is True + client.validate_auth_data.assert_called_once_with('user', 'pass') + client.put.assert_not_called() + + +class TestUpdatePasswordHash: + @pytest.mark.parametrize('check', [False, True]) + def test_sensu_go_older_than_5_21_0(self, mocker, check): + client = mocker.Mock() + client.version = version.StrictVersion("5.20.0") + + with pytest.raises(errors.SensuError): + user.update_password_hash(client, '/path', 'user', 'hash', check) + + client.put.assert_not_called() + + def test_sensu_go_newer_than_5_21_0(self, mocker): + client = mocker.Mock() + client.version = version.StrictVersion("5.21.0") + client.put.return_value = http.Response(201, '') + + changed = user.update_password_hash( + client, '/path', 'user', 'hash', False, + ) + + assert changed is True + client.put.assert_called_once() + + path, payload = client.put.call_args[0] + assert path == '/path/reset_password' + assert payload['username'] == 'user' + assert payload['password_hash'] == 'hash' + + def test_sensu_go_newer_than_5_21_0_check_mode(self, mocker): + client = mocker.Mock() + client.version = version.StrictVersion("5.21.0") + + changed = user.update_password_hash( + client, '/path', 'user', 'pass', True, + ) + + assert changed is True + client.put.assert_not_called() + + +class TestUpdateGroups: + @pytest.mark.parametrize('check', [False, True]) + def test_update_groups_no_change(self, mocker, check): + client = mocker.Mock() + + result = user.update_groups( + client, '/path', ['a', 'b'], ['b', 'a'], check, + ) + + assert result is False + client.put.assert_not_called() + client.delete.assert_not_called() + + def test_update_groups(self, mocker): + client = mocker.Mock() + client.put.side_effect = [ + http.Response(201, ''), http.Response(201, ''), + ] + client.delete.side_effect = [ + http.Response(204, ''), http.Response(204, ''), + ] + + result = user.update_groups( + client, '/path', ['a', 'b', 'c'], ['e', 'd', 'c'], False, + ) + + assert result is True + client.put.assert_has_calls([ + mocker.call('/path/groups/d', None), + mocker.call('/path/groups/e', None), + ], any_order=True) + client.delete.assert_has_calls([ + mocker.call('/path/groups/a'), + mocker.call('/path/groups/b'), + ], any_order=True) + + def test_update_groups_check_mode(self, mocker): + client = mocker.Mock() + + result = user.update_groups( + client, '/path', ['a', 'b', 'c'], ['e', 'd', 'c'], True, + ) + + assert result is True + client.put.assert_not_called() + client.delete.assert_not_called() + + +class TestUpdateState: + @pytest.mark.parametrize('check', [False, True]) + @pytest.mark.parametrize('state', [False, True]) + def test_update_state_no_change(self, mocker, check, state): + client = mocker.Mock() + + result = user.update_state(client, '/path', state, state, check) + + assert result is False + client.put.assert_not_called() + client.delete.assert_not_called() + + def test_disable_user(self, mocker): + client = mocker.Mock() + client.delete.return_value = http.Response(204, '') + + # Go from disabled == False to disabled == True + result = user.update_state(client, '/path', False, True, False) + + assert result is True + client.put.assert_not_called() + client.delete.assert_called_once_with('/path') + + def test_disable_user_check_mode(self, mocker): + client = mocker.Mock() + + # Go from disabled == False to disabled == True + result = user.update_state(client, '/path', False, True, True) + + assert result is True + client.put.assert_not_called() + client.delete.assert_not_called() + + def test_enable_user(self, mocker): + client = mocker.Mock() + client.put.return_value = http.Response(201, '') + + # Go from disabled == True to disabled == False + result = user.update_state(client, '/path', True, False, False) + + assert result is True + client.put.assert_called_once_with('/path/reinstate', None) + client.delete.assert_not_called() + + def test_enable_user_check_mode(self, mocker): + client = mocker.Mock() + + # Go from disabled == True to disabled == False + result = user.update_state(client, '/path', True, False, True) + + assert result is True + client.put.assert_not_called() + client.delete.assert_not_called() + + +class TestSync: + def test_no_current_object(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + client.put.return_value = http.Response(201, '') + + changed, result = user.sync( + None, client, '/path', {'password': 'data'}, False + ) + + assert changed is True + assert {'new': 'data'} == result + client.put.assert_called_once_with('/path', {'password': 'data'}) + + def test_no_current_object_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + + changed, result = user.sync( + None, client, '/path', {'password_hash': 'data'}, True + ) + + assert changed is True + assert {} == result + client.put.assert_not_called() + + def test_password_update(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + p_mock = mocker.patch.object(user, 'update_password') + p_mock.return_value = True + g_mock = mocker.patch.object(user, 'update_groups') + s_mock = mocker.patch.object(user, 'update_state') + + changed, result = user.sync( + dict(old='data'), client, '/path', + dict(username='user', password='pass'), False + ) + + assert changed is True + assert dict(new='data') == result + p_mock.assert_called_once() + g_mock.assert_not_called() + s_mock.assert_not_called() + + def test_password_update_check_mode(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + p_mock = mocker.patch.object(user, 'update_password') + p_mock.return_value = False + g_mock = mocker.patch.object(user, 'update_groups') + s_mock = mocker.patch.object(user, 'update_state') + + changed, result = user.sync( + dict(old='data'), client, '/path', + dict(username='user', password='pass'), True + ) + + assert changed is False + assert dict(old='data', username='user') == result + p_mock.assert_called_once() + g_mock.assert_not_called() + s_mock.assert_not_called() + + def test_password_hash_update(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + mock = mocker.patch.object(user, 'update_password_hash') + mock.return_value = True + + changed, result = user.sync( + dict(old='data'), client, '/path', + dict(username='user', password_hash='pass'), False + ) + + assert changed is True + assert dict(new='data') == result + mock.assert_called_once() + + def test_password_hash_update_check_mode(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + mock = mocker.patch.object(user, 'update_password_hash') + mock.return_value = True + + changed, result = user.sync( + dict(old='data'), client, '/path', + dict(username='user', password_hash='pass'), True + ) + + assert changed is True + assert dict(old='data', username='user') == result + mock.assert_called_once() + + def test_when_password_is_set_we_ignore_hash(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + p_mock = mocker.patch.object(user, 'update_password') + p_mock.return_value = True + h_mock = mocker.patch.object(user, 'update_password_hash') + + user.sync( + dict(old='data'), client, '/path', + dict(username='user', password='pass', password_hash='hash'), + False + ) + + p_mock.assert_called_once() + h_mock.assert_not_called() + + def test_groups_update(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + p_mock = mocker.patch.object(user, 'update_password') + g_mock = mocker.patch.object(user, 'update_groups') + g_mock.return_value = False + s_mock = mocker.patch.object(user, 'update_state') + + changed, result = user.sync( + dict(groups=['a']), client, '/path', dict(groups=['b']), False + ) + + assert changed is False + assert dict(new='data') == result + p_mock.assert_not_called() + g_mock.assert_called_once() + s_mock.assert_not_called() + + def test_groups_update_check_mode(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + p_mock = mocker.patch.object(user, 'update_password') + g_mock = mocker.patch.object(user, 'update_groups') + g_mock.return_value = True + s_mock = mocker.patch.object(user, 'update_state') + + changed, result = user.sync( + dict(x=3, groups=['a']), client, '/path', dict(groups=['b']), True + ) + + assert changed is True + assert dict(x=3, groups=['b']) == result + p_mock.assert_not_called() + g_mock.assert_called_once() + s_mock.assert_not_called() + + def test_state_update(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + p_mock = mocker.patch.object(user, 'update_password') + g_mock = mocker.patch.object(user, 'update_groups') + s_mock = mocker.patch.object(user, 'update_state') + s_mock.return_value = False + + changed, result = user.sync( + dict(disabled=True), client, '/path', dict(disabled=False), False + ) + + assert changed is False + assert dict(new='data') == result + p_mock.assert_not_called() + g_mock.assert_not_called() + s_mock.assert_called_once() + + def test_state_update_check_mode(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + p_mock = mocker.patch.object(user, 'update_password') + g_mock = mocker.patch.object(user, 'update_groups') + s_mock = mocker.patch.object(user, 'update_state') + s_mock.return_value = True + + changed, result = user.sync( + dict(disabled=True), client, '/path', dict(disabled=False), True + ) + + assert changed is True + assert dict(disabled=False) == result + p_mock.assert_not_called() + g_mock.assert_not_called() + s_mock.assert_called_once() + + +class TestUser(ModuleTestCase): + def test_minimal_user_parameters(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.return_value = None + sync_mock = mocker.patch.object(user, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='alice', + password='alice!?pass', + ) + + with pytest.raises(AnsibleExitJson): + user.main() + + result, _client, path, payload, check_mode = sync_mock.call_args[0] + assert path == '/api/core/v2/users/alice' + assert payload == dict( + username='alice', + password='alice!?pass', + disabled=False + ) + assert check_mode is False + + def test_minimal_parameters_on_existing_user(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.return_value = dict(username='alice') + sync_mock = mocker.patch.object(user, 'sync') + sync_mock.return_value = True, {} + set_module_args(name='alice') + + with pytest.raises(AnsibleExitJson): + user.main() + + result, _client, path, payload, check_mode = sync_mock.call_args[0] + assert path == '/api/core/v2/users/alice' + assert payload == dict(username='alice', disabled=False) + assert check_mode is False + + def test_all_user_parameters(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.return_value = None + sync_mock = mocker.patch.object(user, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_user', + state='disabled', + password='password', + groups=['dev', 'ops'], + ) + + with pytest.raises(AnsibleExitJson): + user.main() + + result, _client, path, payload, check_mode = sync_mock.call_args[0] + assert path == '/api/core/v2/users/test_user' + assert payload == dict( + username='test_user', + password='password', + groups=['dev', 'ops'], + disabled=True + ) + assert check_mode is False + + def test_cannot_create_user_without_password(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.return_value = None + set_module_args( + name='test_user', + state='disabled', + groups=['dev', 'ops'], + ) + + with pytest.raises(AnsibleFailJson, match='without a password'): + user.main() + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.return_value = None + sync_mock = mocker.patch.object(user, 'sync') + sync_mock.side_effect = errors.Error('Bad error') + set_module_args( + name='test_user', + password='password' + ) + + with pytest.raises(AnsibleFailJson): + user.main() + + def test_failure_on_initial_get(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.side_effect = errors.Error('Bad error') + set_module_args( + name='test_user', + password='password' + ) + + with pytest.raises(AnsibleFailJson): + user.main() + + def test_failure_on_missing_bcrypt_5_21_0_or_newer(self, mocker): + mocker.patch.object(arguments, 'get_sensu_client').return_value = ( + mocker.MagicMock(version='5.22.3') + ) + mocker.patch.object(user, 'HAS_BCRYPT', False) + set_module_args( + name='test_user', + password='password' + ) + + with pytest.raises(AnsibleFailJson, match='bcrypt'): + user.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_user_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_user_info.py new file mode 100644 index 00000000..09fc337f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_user_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import user_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestUserInfo(ModuleTestCase): + def test_get_all_users(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args() + + with pytest.raises(AnsibleExitJson) as context: + user_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/users" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_user(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 4 + set_module_args(name="sample-user") + + with pytest.raises(AnsibleExitJson) as context: + user_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/users/sample-user" + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_user(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-user") + + with pytest.raises(AnsibleExitJson) as context: + user_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-user") + + with pytest.raises(AnsibleFailJson): + user_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/requirements.txt b/ansible_collections/sensu/sensu_go/tests/unit/requirements.txt new file mode 100644 index 00000000..7f0b6e75 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/requirements.txt @@ -0,0 +1 @@ +bcrypt diff --git a/ansible_collections/sensu/sensu_go/tools/windows-versions.py b/ansible_collections/sensu/sensu_go/tools/windows-versions.py new file mode 100755 index 00000000..1706a0a0 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tools/windows-versions.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import argparse +import gzip +import pathlib +import shutil +import subprocess +import sys + +from urllib import request +from xml.etree import ElementTree + +import yaml + + +BASE_REPO_URL = "https://packagecloud.io/sensu/stable/el/8/x86_64/" +FILENAME_TEMPLATE = "sensu-go-agent_{0}.{1}.{2}.{3}_en-US.{arch}.msi" +DOWNLOAD_URL_TEMPLATE = ( + "https://s3-us-west-2.amazonaws.com/sensu.io/sensu-go/{0}.{1}.{2}/" + + FILENAME_TEMPLATE +) +MINIMAL_VERSION = (5, 20, 0) + + +class ArgParser(argparse.ArgumentParser): + """An argument parser that displays help on error.""" + + def error(self, message): + sys.stderr.write("error: {0}\n".format(message)) + self.print_help() + sys.exit(2) + + def add_subparsers(self, **kwargs): + # Workaround for http://bugs.python.org/issue9253 + subparsers = super(ArgParser, self).add_subparsers() + subparsers.required = True + subparsers.dest = "command" + return subparsers + + +def _fetch_available_versions(): + available_versions = set() + + response = request.urlopen(BASE_REPO_URL + "repodata/repomd.xml", timeout=30) + root = ElementTree.parse(response).getroot() + for data in root.iter("{http://linux.duke.edu/metadata/repo}data"): + if data.get("type") == "primary": + break + else: + return available_versions + + location = next(data.iter("{http://linux.duke.edu/metadata/repo}location")) + path = location.attrib["href"] + + response = request.urlopen(BASE_REPO_URL + path, timeout=30) + root = ElementTree.fromstring(gzip.decompress(response.read())) + for package in root.iter("{http://linux.duke.edu/metadata/common}package"): + name = next(package.iter("{http://linux.duke.edu/metadata/common}name")) + if name.text != "sensu-go-agent": + continue + + version = next(package.iter("{http://linux.duke.edu/metadata/common}version")) + version_tuple = tuple(int(c) for c in version.get("ver").split(".")) + if version_tuple < MINIMAL_VERSION: + continue + + available_versions.add(version_tuple + (int(version.get("rel")), )) + + return available_versions + + +def _load_versions_from_vars(vars): + return set( + (tuple(int(c) for c in item["version"].split(".")) + (item["build"],)) + for item in vars["_msi_lookup"].values() + ) + + +def _sync_versions(vars, available_versions, cache_dir): + new_vars = dict(vars, _msi_lookup={}) + + old_msis = vars["_msi_lookup"] + new_msis = new_vars["_msi_lookup"] + + cache = pathlib.Path(cache_dir) + + for version in sorted(available_versions): + version_str = ".".join(map(str, version[:3])) + + if version_str in old_msis: + # Happy path: we already have this version sorted + new_msis[version_str] = old_msis[version_str] + continue + + # Sad path: we need to download packages and extract product codes + product_codes = {} + for arch in ("x86", "x64"): + url = DOWNLOAD_URL_TEMPLATE.format(*version, arch=arch) + filename = FILENAME_TEMPLATE.format(*version, arch=arch) + file = cache / filename + + if not file.is_file(): + print("Downloading " + filename) + with open(file, "wb") as fp: + response = request.urlopen(url) + shutil.copyfileobj(response, fp) + else: + print("Reusing " + filename) + + process = subprocess.run( + ("msiinfo", "export", str(file), "Property"), capture_output=True, + check=True + ) + for line in process.stdout.splitlines(): + field, value = line.split(b"\t") + if field == b"ProductCode": + product_codes[arch] = value.decode("ascii") + + new_msis[version_str] = dict( + version=version_str, build=version[-1], product_codes=product_codes + ) + + new_msis["latest"] = new_msis[version_str] + + return new_vars + + +def _load_windows_vars_file(filename): + with open(filename, "r") as fd: + return yaml.safe_load(fd) + + +def _save_windows_vars_file(filename, vars): + with open(filename, "w") as fd: + yaml.safe_dump(vars, fd) + + +def _check(args): + vars_data = _load_windows_vars_file(args.vars) + current = _load_versions_from_vars(vars_data) + available = _fetch_available_versions() + + missing = available - current + obsolete = current - available + + if missing: + print("The following versions are missing: {0}".format( + ", ".join(".".join(map(str, v)) for v in missing) + )) + if obsolete: + print("The following versions are obsolete: {0}".format( + ", ".join(".".join(map(str, v)) for v in obsolete) + )) + + return len(missing) + len(obsolete) + + +def _update(args): + vars_data = _load_windows_vars_file(args.vars) + current = _load_versions_from_vars(vars_data) + available = _fetch_available_versions() + + if current == available: + return 0 + + new_vars_data = _sync_versions(vars_data, available, args.cache) + _save_windows_vars_file(args.vars, new_vars_data) + + return 0 + + +def main(): + parser = ArgParser( + description="Windows agent version updater", + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + subparsers = parser.add_subparsers() + + check = subparsers.add_parser("check", help="Check for version updates") + check.add_argument("vars", help="Variable file with Windows lookup table") + check.set_defaults(func=_check) + + update = subparsers.add_parser("update", help="Update lookup table") + update.add_argument("vars", help="Variable file with Windows lookup table") + update.add_argument( + "--cache", help="Directory used for caching downloads", default="/tmp" + ) + update.set_defaults(func=_update) + + args = parser.parse_args() + + return args.func(args) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/ansible_collections/sensu/sensu_go/vagrant/windows/Vagrantfile b/ansible_collections/sensu/sensu_go/vagrant/windows/Vagrantfile new file mode 100644 index 00000000..ebc89336 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/vagrant/windows/Vagrantfile @@ -0,0 +1,15 @@ +Vagrant.configure("2") do |config| + config.vm.define "backend" do |backend| + backend.vm.box = "centos/8" + backend.vm.network "private_network", ip: "192.168.50.91" + backend.vm.provision "ansible" do |ansible| + ansible.playbook = "back.yaml" + end + end + + config.vm.define "agent" do |agent| + agent.vm.box = "gusztavvargadr/windows-10" + agent.vm.hostname = "host-win" + agent.vm.network "private_network", ip: "192.168.50.90" + end +end diff --git a/ansible_collections/sensu/sensu_go/vagrant/windows/back.yaml b/ansible_collections/sensu/sensu_go/vagrant/windows/back.yaml new file mode 100644 index 00000000..8ede29ba --- /dev/null +++ b/ansible_collections/sensu/sensu_go/vagrant/windows/back.yaml @@ -0,0 +1,18 @@ +--- +- name: Install backend + hosts: all + become: true + + tasks: + - name: Install cli + include_role: + name: sensu.sensu_go.install + vars: + version: latest + components: [ sensu-go-cli ] + + - name: Install backend + include_role: + name: sensu.sensu_go.backend + vars: + version: latest diff --git a/ansible_collections/sensu/sensu_go/vagrant/windows/inventory.yaml b/ansible_collections/sensu/sensu_go/vagrant/windows/inventory.yaml new file mode 100644 index 00000000..8cee9953 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/vagrant/windows/inventory.yaml @@ -0,0 +1,10 @@ +--- +all: + hosts: + windows: + ansible_host: 192.168.50.90 + ansible_port: 5985 + ansible_user: vagrant + ansible_password: vagrant + ansible_connection: winrm + ansible_winrm_transport: basic diff --git a/ansible_collections/sensu/sensu_go/vagrant/windows/play.yaml b/ansible_collections/sensu/sensu_go/vagrant/windows/play.yaml new file mode 100644 index 00000000..fcf2137d --- /dev/null +++ b/ansible_collections/sensu/sensu_go/vagrant/windows/play.yaml @@ -0,0 +1,14 @@ +--- +- name: Install windows agent + hosts: windows + + tasks: + - name: Install agent + include_role: + name: sensu.sensu_go.agent + vars: + version: latest + agent_config: + name: win + backend-url: + - ws://192.168.50.91:8081 |